<?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>r4yyy.eth</title>
        <link>https://paragraph.com/@r4yyy</link>
        <description>undefined</description>
        <lastBuildDate>Mon, 08 Jun 2026 03:54:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>r4yyy.eth</title>
            <url>https://storage.googleapis.com/papyrus_images/759c6c327a723ef0054e3572106f44df021a46ea5a2821ae34f04eaea5ba9d85.png</url>
            <link>https://paragraph.com/@r4yyy</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Netflix NFT 漏洞Mint思路分析]]></title>
            <link>https://paragraph.com/@r4yyy/netflix-nft-mint</link>
            <guid>kJGY6h0fifzEq4pDXIX4</guid>
            <pubDate>Sat, 21 May 2022 08:04:50 GMT</pubDate>
            <description><![CDATA[前Netflix 出了最新的一集的 《爱，死亡，机器人》，这一季是送NFT 的一季。Nine Love, Death + Robots QR-Coded Artworks (艺术品) have been strewn across the digital and physical world. Each piece of special, limited edition imagery (意象) reflects Love, Death + Robots’ unique collective of visual perspectives and creative storytelling (弹词) from Volume 3. To collect them all, you’ll have to be vigilant (警惕) . Look out for Love, Death + Robots QR codes to scan in order to unlock (解锁) the art. Mint (薄荷) the art as an NFT, or right-cl...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前</h1><p>Netflix 出了最新的一集的 《爱，死亡，机器人》，这一季是送NFT 的一季。</p><blockquote><p>Nine Love, Death + Robots QR-Coded Artworks (艺术品) have been strewn across the digital and physical world. Each piece of special, limited edition imagery (意象) reflects Love, Death + Robots’ unique collective of visual perspectives and creative storytelling (弹词) from Volume 3. To collect them all, you’ll have to be vigilant (警惕) . Look out for Love, Death + Robots QR codes to scan in order to unlock (解锁) the art. Mint (薄荷) the art as an NFT, or right-click and save it the old-fashioned way. The choice is yours, human.</p></blockquote><p>虽然活动是好的，但是出了问题，对于mint的签名没有任何的限制，任何人都可以通过接口来进行批量的签名和mint。下面已经有大佬写好了批量mint 的工具</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://codesandbox.io/embed/ldrclaimer-lmeudq?expanddevtools=1&amp;fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview">批量Mint工具 By BOX</a></p></li></ul><p>呢吗这篇文章的主要目的，就是模拟一下 发现这个 漏洞的思路，以及对这个工具的原理的剖析，毕竟是多学习模仿才能进步。</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">正文</h1><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">漏洞发崛部分</h2><p>官方在剧集里面讲，会出现NFT 的Qrcode，扫描之后进入Mint页面</p><blockquote><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://lovedeathandart.com/kj2sp8">https://lovedeathandart.com/kj2sp8</a></p></blockquote><p>!img</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">构建签名</h3><p>这里直接点击mint me，可以在浏览器的开发者模式看到 sign 的接口调用，右键复制 curl</p><pre data-type="codeBlock" text="curl &apos;https://us-central1-ldr-prod.cloudfunctions.net/api/sign&apos; \
  -H &apos;authority: us-central1-ldr-prod.cloudfunctions.net&apos; \
  -H &apos;accept: application/json, text/plain, */*&apos; \
  -H &apos;accept-language: zh,zh-CN;q=0.9&apos; \
  -H &apos;cache-control: no-cache&apos; \
  -H &apos;content-type: application/json&apos; \
  -H &apos;dnt: 1&apos; \
  -H &apos;origin: https://lovedeathandart.com&apos; \
  -H &apos;pragma: no-cache&apos; \
  -H &apos;referer: https://lovedeathandart.com/&apos; \
  -H &apos;sec-fetch-dest: empty&apos; \
  -H &apos;sec-fetch-mode: cors&apos; \
  -H &apos;sec-fetch-site: cross-site&apos; \
  -H &apos;user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1&apos; \
  --data-raw &apos;{&quot;address&quot;:&quot;0x000&quot;,&quot;category&quot;:&quot;9&quot;}&apos; \
  --compressed
"><code>curl <span class="hljs-string">'https://us-central1-ldr-prod.cloudfunctions.net/api/sign'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'authority: us-central1-ldr-prod.cloudfunctions.net'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'accept: application/json, text/plain, */*'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'accept-language: zh,zh-CN;q=0.9'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'cache-control: no-cache'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'content-type: application/json'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'dnt: 1'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'origin: https://lovedeathandart.com'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'pragma: no-cache'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'referer: https://lovedeathandart.com/'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'sec-fetch-dest: empty'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'sec-fetch-mode: cors'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'sec-fetch-site: cross-site'</span> \
  <span class="hljs-operator">-</span>H <span class="hljs-string">'user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'</span> \
  <span class="hljs-operator">-</span><span class="hljs-operator">-</span>data<span class="hljs-operator">-</span>raw <span class="hljs-string">'{"address":"0x000","category":"9"}'</span> \
  <span class="hljs-operator">-</span><span class="hljs-operator">-</span>compressed
</code></pre><p>得到上面的 Curl 请求， 这里需要注意的是 address 需要替换为自己的地址，Category 需要换成对应的选集。请求之后得到下面的内容：这里的 vrs 就立即让大家联想到 secp256k1 算法。</p><pre data-type="codeBlock" text="{&quot;message&quot;:&quot;0x87b5cc68157bf098...a1428b0723919c5dc87536f4af06c&quot;,&quot;messageHash&quot;:&quot;0x506c883be0c095b1512aa679...c378309ce0e1&quot;,&quot;v&quot;:&quot;0x1c&quot;,&quot;r&quot;:&quot;0x1d957d3f6a0f53e261d7e1001f6...e440e370ca6812227634&quot;,&quot;s&quot;:&quot;0x58dc6d11201f072972564cff3...57156c3fc0ca4d7dea902&quot;,&quot;signature&quot;:&quot;0x1d957d3f6a0f53e261d7e1001f60e94f55df...021c&quot;}
"><code><span class="hljs-punctuation">{</span><span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"0x87b5cc68157bf098...a1428b0723919c5dc87536f4af06c"</span><span class="hljs-punctuation">,</span><span class="hljs-attr">"messageHash"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"0x506c883be0c095b1512aa679...c378309ce0e1"</span><span class="hljs-punctuation">,</span><span class="hljs-attr">"v"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"0x1c"</span><span class="hljs-punctuation">,</span><span class="hljs-attr">"r"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"0x1d957d3f6a0f53e261d7e1001f6...e440e370ca6812227634"</span><span class="hljs-punctuation">,</span><span class="hljs-attr">"s"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"0x58dc6d11201f072972564cff3...57156c3fc0ca4d7dea902"</span><span class="hljs-punctuation">,</span><span class="hljs-attr">"signature"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"0x1d957d3f6a0f53e261d7e1001f60e94f55df...021c"</span><span class="hljs-punctuation">}</span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">发送交易</h3><p>通过os搜索，或者各大地方来获取 NFT 对应的地址，打开区块浏览器</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/token/0xfd43d1da000558473822302e1d44d81da2e4cc0d">https://etherscan.io/token/0xfd43d1da000558473822302e1d44d81da2e4cc0d</a></p></li></ul><p>到Transaction 里面找到大家调用的交易，看大家调用的最多的是哪个，以及其参数。可以看到参数如下</p><ol><li><p>_category uint256 1</p></li><li><p>_data bytes 0x1b</p></li><li><p>_signature bytes 0xb10b4f29f576f3...</p></li></ol><p>这里的 category 和 signature，在签名拿到sign 结果已经可以拿到了。还有一个就是这个 _data ， 这个参数在看历史交易里面 发现全都是 0x1b 所以应该是一个常数值。构建好之后就可以点击 write contract。完成mint 国产。</p><h2 id="h-dapp" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">dApp 分析</h2><p>对这里的批量mint 工具来作分析，学习一下相关的思路和方法</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://codesandbox.io/embed/ldrclaimer-lmeudq?expanddevtools=1&amp;fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview">批量Mint工具 By BOX</a></p></li></ul><p>是使用 react 编写，用到下面两个web3相关的lib</p><ul><li><p>ethers</p></li><li><p>web3modal</p></li></ul><p>ethers 这个之前有使用过，Web3modal 这个研究了下，是包装了web3钱包的 API，可以统一代码模式。</p><p>代码的主要逻辑也是签名的接口调用，模拟Curl 的请求。</p><p>点击连接之后调用<code>web3Modal.connect()</code> 之后来设置 Instance 。有了Instance 之后，进行了条件渲染。</p><p>到了 <code>claimAllWrapper</code> 函数，设置 running 状态，await 阻塞，等待 claimAll，在下面是 这里的核心代码。</p><pre data-type="codeBlock" text="const contract = new ethers.Contract(
    &quot;0xFD43D1dA000558473822302e1d44D81dA2e4cC0d&quot;,
    // 这里来导入了ABI，也就是我们要用的接口
    [&quot;function mint ( uint256 _category, bytes _data, bytes _signature )&quot;],
    provider.getSigner()
);
const address = await provider.getSigner().getAddress();
for (let i = 1; i &lt;= 9; i++) {
    const signature = await requestSign(address, i);
    await contract
    .mint(i, signature[&quot;v&quot;], signature[&quot;signature&quot;])
    .catch(() =&gt; {});
    console.log(`Success mint #${i}`);
}
"><code>const <span class="hljs-class"><span class="hljs-keyword">contract</span> = <span class="hljs-title"><span class="hljs-keyword">new</span></span> <span class="hljs-title">ethers</span>.<span class="hljs-title">Contract</span>(<span class="hljs-params">
    <span class="hljs-string">"0xFD43D1dA000558473822302e1d44D81dA2e4cC0d"</span>,
    <span class="hljs-comment">// 这里来导入了ABI，也就是我们要用的接口</span>
    [<span class="hljs-string">"function mint ( uint256 _category, bytes _data, bytes _signature )"</span>],
    provider.getSigner(<span class="hljs-params"></span>)
</span>);
<span class="hljs-title">const</span> <span class="hljs-title"><span class="hljs-keyword">address</span></span> = <span class="hljs-title">await</span> <span class="hljs-title">provider</span>.<span class="hljs-title">getSigner</span>(<span class="hljs-params"></span>).<span class="hljs-title">getAddress</span>(<span class="hljs-params"></span>);
<span class="hljs-title"><span class="hljs-keyword">for</span></span> (<span class="hljs-params">let i = <span class="hljs-number">1</span>; i &#x3C;= <span class="hljs-number">9</span>; i++</span>) </span>{
    const signature <span class="hljs-operator">=</span> await requestSign(<span class="hljs-keyword">address</span>, i);
    await <span class="hljs-class"><span class="hljs-keyword">contract</span>
    .<span class="hljs-title">mint</span>(<span class="hljs-params">i, signature[<span class="hljs-string">"v"</span>], signature[<span class="hljs-string">"signature"</span>]</span>)
    .<span class="hljs-title"><span class="hljs-keyword">catch</span></span>(<span class="hljs-params">(<span class="hljs-params"></span>) => {}</span>);
    <span class="hljs-title">console</span>.<span class="hljs-title">log</span>(<span class="hljs-params">`Success mint #${i}`</span>);
}
</span></code></pre><p>去构建合约的 ABI，并且使用 requestSign 来获得签名，之后调用 合约方法，来实现mint ，这里的data 可以看到 是 secp256k1 的 v 字段。</p><blockquote><p>recovery id 值，但在以太坊中用<code>V</code>表示</p></blockquote><p>之后对合约进行直接调用，完成mint 的操作。</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">后</h1><p>从上面的分析可以看到，这种漏洞利用实际上的过程是非常简单的。但是这些往往就是机会，发现的早带来的信息差的收益就是机会。 这个 nft 在刚开始是 0.05E ，现在因为在被大量的mint 已经跌到了 0.003E 。</p><p>分析这个过程，让自己在保持敏锐，能抓到潜在的机会。</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[“做空”比特币]]></title>
            <link>https://paragraph.com/@r4yyy/4AxAVKTjQyxV9RIAs2EP</link>
            <guid>4AxAVKTjQyxV9RIAs2EP</guid>
            <pubDate>Thu, 12 May 2022 10:52:11 GMT</pubDate>
            <description><![CDATA[前听到了一个相当新颖的观点，比特币系统是不可持续的。“做空”比特币矿工过低的Fee收入曾经的btc被微软等公司支持支付，那是最好的时代。这里是最主要的问题，因为BTC 的区块大小太小了，一秒钟只能有7笔交易。 TX的低直接导致的就是影响链上转账的过程。目前的爆块的收益水平是下面的情况。Block Reward 6.25000000 BTCBTC Fee Reward 0.23313970 BTC随着一次又一次的减半，区块奖励越来越少，交易费用受txs的影响没有太大提升，挖矿可能逐步变得无利可图。 因为挖出的增量市值 单价*爆块数量 和 本身的存量市值 单价*存量BTC数量 的比值将会变得非常的小。那么从矿工的角度来说，就是获得非常小的比例的奖励，来保护一个市值的系统。那么整个矿工的算力水平会随着收益的减小而逐步减小，也就是说维护网络安全的成本逐渐减小。那么进行51%攻击的成本在下降，收益在增加（btc 的存量市值）。安全事件迟早会发生爆块时间导致的矿池偏袒ETH 矿池很多五花八门，但是BTC很少，为什么呢BTC的爆块时间在10分钟，而ETH的时间在15秒。这样就拓展出一个问题，虽...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前</h1><p>听到了一个相当新颖的观点，比特币系统是不可持续的。</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">“做空”比特币</h1><h2 id="h-fee" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">矿工过低的Fee收入</h2><blockquote><p>曾经的btc被微软等公司支持支付，那是最好的时代。</p></blockquote><p>这里是最主要的问题，因为BTC 的区块大小太小了，一秒钟只能有7笔交易。</p><p>TX的低直接导致的就是影响链上转账的过程。目前的爆块的收益水平是下面的情况。</p><ul><li><p>Block Reward 6.25000000 BTC</p></li><li><p>BTC Fee Reward 0.23313970 BTC</p></li></ul><p>随着一次又一次的减半，区块奖励越来越少，交易费用受txs的影响没有太大提升，挖矿可能逐步变得无利可图。</p><p>因为挖出的增量市值 <code>单价*爆块数量</code> 和 本身的存量市值 <code>单价*存量BTC数量</code> 的比值将会变得非常的小。那么从矿工的角度来说，就是获得非常小的比例的奖励，来保护一个市值的系统。那么整个矿工的算力水平会随着收益的减小而逐步减小，也就是说维护网络安全的成本逐渐减小。那么进行51%攻击的成本在下降，收益在增加（btc 的存量市值）。<strong>安全事件迟早会发生</strong></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">爆块时间导致的矿池偏袒</h2><blockquote><p>ETH 矿池很多五花八门，但是BTC很少，为什么呢</p></blockquote><p>BTC的爆块时间在10分钟，而ETH的时间在15秒。这样就拓展出一个问题，虽然参与挖矿的游戏，每个人挖到块的概率是一定的。但是由于BTC的10分钟的区块时间导致每天只会出块144块，而eth有5760个块，一样掷硬币的游戏，掷的速度慢，意味着我们得到的结果的方差会非常非常的大。而矿工本身的加入矿池的信条就是：<strong>稳定</strong></p><p>所以在这种情况下，大算力的矿池，在144次中或者多数，方差低，每天可以获得近似稳定的收入。</p><p>而小矿池，只能获取144次中的少数，方差大，收入波动厉害。如果连续几天不出块，那么可以直接饿死了。</p><p>所以在10分钟的出块机制下，对大矿池的存在是有偏袒的。而小矿池的加入会变得非常困难。</p><p>因此<strong>降低了去中心化的程度。</strong></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">商品市场的缺失</h2><blockquote><p>BTC 没有商品市场，不能老靠着信仰走一辈子</p></blockquote><p>市场上存在两批人，消费者和投资者。就按腾讯这个公司来讲，股票是他的投资者市场，而他的游戏，娱乐等等，为他带来了消费者市场，也就是商品市场。这样才会引领腾讯这家公司走向成功。</p><p>使用ETH的生态举例，上面有 NFT 和 GameFi ，这些都是商品生态的一部分，也就是说从一个无聊的链上符号变成了一个对外售卖的东西。而反观BTC 是没有商品市场的，本身机制对日常消费等等场景非常不友好。除了链上的无聊符号，没有能获取消费者市场的能力。</p><p>综上，也就是说，目前BTC是一个只有投资者，没有消费者的东西。一个<strong>单单靠信仰支撑的东西</strong></p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">我的看法</h1><p>首先，上面描述的观点，是我听到的很独特的，且的确引发了我从另一些层面的思考。我的看法主要是从BTC的价值储蓄出发。类比国库中的黄金一样，是一种价值的存储。（虽然，的确失去了黄金的商品市场，首饰）。</p><ol><li><p>全球的黄金（資源）分布也是不均匀的，有限的，石油等等这些的产量也是受制于OPEC组织，所以矿池的集中化和这点对等</p></li><li><p>矿工的收入，实际上最根本来讲是取决于能源的消耗，节能是发展路上的一个礼貌但是缺无知的问题。能源的成本在人类文明的进化中，势必越来越低。由此，矿工的收入与网络的算力平衡有了平衡的等式</p></li><li><p>商品市场，是成功的地方，危险的地方。我对ETH的看法，就像我们的网络，有用不贵。想象在40年前上网的成本，以及现在的网络成本的对比，随着基础设施的发展，网络的费用会越来越低。eth 也许会随着这种设施的进步，成本也越发低。所以在 eth的生态上，使用 ETH 来作为价值的锚定并不合理：本身的Defi/商品 的价值，会随着寄托他们的基础网络而变化。</p><p>所以我想以后可能会是 BTC 价值为锚，在eth 网络上快速奔跑。WBTC 类似的形式来存在。</p></li></ol>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[ERC20的Permit]]></title>
            <link>https://paragraph.com/@r4yyy/erc20-permit</link>
            <guid>qbvcLQZf2P210iYNY86p</guid>
            <pubDate>Sun, 08 May 2022 16:56:33 GMT</pubDate>
            <description><![CDATA[前之前学习的时候遇到了 permit 这个ERC20 方法，可以实现无gas 转账和离线交易。 听起来很神奇，但是实际上的原理并不复杂，在这里来进行一个总结正文什么是 Permitpermit 在 EIP-2612 中被引入到 ERC20 的协议中。子标题是 signed approvals ，另外从字面意思上看permit 在文字含义上是许可的意思，也正是与经典的erc20 协议里面 allowance 有关。 其功能从简单来讲，就是A在需要给B进行授权的时候，不需要主动的去调用 approval函数来给B进行授权。而是给这个approval 函数一个合法的签名，得到的签名提供给B，B用这个签名来调用 permit来获得对应额度的 allowance，从而可以对指定金额来进行消费或者转账。 这里用伪代码的approve函数以及 permit 函数进行对比，可见其核心的功能就是set一个对应的allowancefunction approve(address usr, uint wad) external returns (bool) { allowance[msg.sender...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前</h1><p>之前学习的时候遇到了 <code>permit</code> 这个ERC20 方法，可以实现无gas 转账和离线交易。</p><p>听起来很神奇，但是实际上的原理并不复杂，在这里来进行一个总结</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">正文</h1><h2 id="h-permit" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">什么是 Permit</h2><p>permit 在 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-2612">EIP-2612</a> 中被引入到 ERC20 的协议中。子标题是 <strong>signed approvals</strong> ，另外从字面意思上看<code>permit</code> 在文字含义上是许可的意思，也正是与经典的erc20 协议里面 <code>allowance</code> 有关。</p><p>其功能从简单来讲，就是A在需要给B进行授权的时候，不需要主动的去调用 <code>approval</code>函数来给B进行授权。而是给这个approval 函数一个合法的签名，得到的签名提供给B，B用这个签名来调用 <code>permit</code>来获得对应额度的 allowance，从而可以对指定金额来进行消费或者转账。</p><p>这里用伪代码的approve函数以及 permit 函数进行对比，可见其核心的功能就是set一个对应的allowance</p><pre data-type="codeBlock" text="function approve(address usr, uint wad) external returns (bool)
{
  allowance[msg.sender][usr] = wad;
  …
}
function permit(
  address holder, address spender,
  uint256 nonce, uint256 expiry, bool allowed,
  uint8 v, bytes32 r, bytes32 s
) external {
  …
  allowance[holder][spender] = wad;
  …
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">approve</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> usr, <span class="hljs-keyword">uint</span> wad</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>)
</span>{
  allowance[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>][usr] <span class="hljs-operator">=</span> wad;
  …
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">permit</span>(<span class="hljs-params">
  <span class="hljs-keyword">address</span> holder, <span class="hljs-keyword">address</span> spender,
  <span class="hljs-keyword">uint256</span> nonce, <span class="hljs-keyword">uint256</span> expiry, <span class="hljs-keyword">bool</span> allowed,
  <span class="hljs-keyword">uint8</span> v, <span class="hljs-keyword">bytes32</span> r, <span class="hljs-keyword">bytes32</span> s
</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
  …
  allowance[holder][spender] <span class="hljs-operator">=</span> wad;
  …
}
</code></pre><h2 id="h-permit" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">permit应用场景</h2><p>前面讲到permit，使用对approval的调用来进行签名，给到收款方来获得一个预先设置的 allowance的额度。</p><p>那么由此过程我们可以衍生出下面的用途</p><ol><li><p>离线支付</p><p>场景是A在离线的情况下用钱包签名 approval的方法，在签名的时候，带有 token/数量/收款人/deadline。签名之后把签出的内容给到B（可公开），B在有网络的情况下使用 <code>permit</code>的方法获取对应额度完成转账操作。</p><p>但是值得注意的是：这里的转账过程不是可靠的，因为你拿到的只是对应的 allowence 的额度，实际上需要B进行permit 获得 allowance之后，再进行transferFrom 才可以得到对应的数量token。如果A在B执行这个权利之前把账号资产转走，那么B只是得到这个授权额度但是无法获得任何资产。这里可以类比于开出了一张<strong>空头支票</strong>。</p></li><li><p>无gas转账</p><p>不过需要提前明确的是，这里的无gas 不是指的没有gas 消耗，而是A方不需要为授权和转账来付出gas。一般的ERC20 的转账形式是，调用方调用 approval 和 transfer 来进行转账。在使用了 permit 的情况下，A 可以签名 Approval 函数，之后发送给B，b在获得签名之后去调用 Permit 来获得 allowance 使用 transferFrom 来进行转账。全部过程中不需要A支付任何GAS</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">实现原理</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">合约验签逻辑</h3><p>在实现这里，直接找到 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/files/f410734f41f632b42ab4fc4e8695f47d800de709#diff-184791c357255d1de23b57e5660cecb2d01409238f14b76f87de3c5d89bfee77">EIP-2612的commit</a> 这里实现了 permit 函数</p><p>这里把参数分开</p><ul><li><p>address owner // A地址</p></li><li><p>address spender // B地址</p></li><li><p>uint256 amount // 总额度</p></li><li><p>uint256 deadline // 过期时间</p></li><li><p>uint8 v, bytes32 r, bytes32 s // secp256k1（ECDSA） 的恢复ID 以及 RS 的签名输出。</p></li></ul><blockquote><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.stackexchange.com/questions/15766/what-does-v-r-s-in-eth-gettransactionbyhash-mean">What does v, r, s in eth_getTransactionByHash mean?</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/books/geth/part3/sign-and-valid.html">签名与校验</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://soliditydeveloper.com/ecrecover">Solidity 中的 ecrecover 是什么？</a></p></li></ul></blockquote><pre data-type="codeBlock" text="    function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override {
        // solhint-disable-next-line not-rely-on-time
        require(block.timestamp &lt;= deadline, &quot;ERC20Permit: expired deadline&quot;);
        // 对全部提供的内容按格式进行打包之后进行hash
        bytes32 structHash = keccak256(
            abi.encode(
                    // 这里限制了签名的类型，避免任意签名。
                _PERMIT_TYPEHASH,
                owner,
                spender,
                amount,
                _nonces[owner].current(),
                deadline
            )
        );
           // 这里进行hash结构的转化
        bytes32 hash = _hashTypedDataV4(structHash);
                // 恢复对原始的签名数据来进行验签，看此内容验签之后的 signer 是不是传入数据的 owner
        address signer = ECDSA.recover(hash, v, r, s);
        require(signer == owner, &quot;ERC20Permit: invalid signature&quot;);
                // 如果是
        _nonces[owner].increment();
        // 给permit调用者对应的授权额度
        _approve(owner, spender, amount);
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">permit</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">address</span> spender, <span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">uint256</span> deadline, <span class="hljs-keyword">uint8</span> v, <span class="hljs-keyword">bytes32</span> r, <span class="hljs-keyword">bytes32</span> s</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> </span>{
        <span class="hljs-comment">// solhint-disable-next-line not-rely-on-time</span>
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> deadline, <span class="hljs-string">"ERC20Permit: expired deadline"</span>);
        <span class="hljs-comment">// 对全部提供的内容按格式进行打包之后进行hash</span>
        <span class="hljs-keyword">bytes32</span> structHash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(
            <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(
                    <span class="hljs-comment">// 这里限制了签名的类型，避免任意签名。</span>
                _PERMIT_TYPEHASH,
                owner,
                spender,
                amount,
                _nonces[owner].current(),
                deadline
            )
        );
           <span class="hljs-comment">// 这里进行hash结构的转化</span>
        <span class="hljs-keyword">bytes32</span> hash <span class="hljs-operator">=</span> _hashTypedDataV4(structHash);
                <span class="hljs-comment">// 恢复对原始的签名数据来进行验签，看此内容验签之后的 signer 是不是传入数据的 owner</span>
        <span class="hljs-keyword">address</span> signer <span class="hljs-operator">=</span> ECDSA.recover(hash, v, r, s);
        <span class="hljs-built_in">require</span>(signer <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner, <span class="hljs-string">"ERC20Permit: invalid signature"</span>);
                <span class="hljs-comment">// 如果是</span>
        _nonces[owner].increment();
        <span class="hljs-comment">// 给permit调用者对应的授权额度</span>
        _approve(owner, spender, amount);
    }
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">客户端签名逻辑</h3><p>参考链接： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.csdn.net/weixin_43840202/article/details/122957126">Permit-712签名</a></p><p>这里有两个点</p><ul><li><p>DOMAIN_SEPARATOR // 定义域分隔符</p></li><li><p>_PERMIT_TYPEHASH // Permit 的参数格式的hash</p></li></ul><p>_PERMIT_TYPEHASH 定义见下：</p><pre data-type="codeBlock" text="
bytes32 private immutable _PERMIT_TYPEHASH = keccak256(&quot;Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)&quot;);
"><code>
<span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">immutable</span> _PERMIT_TYPEHASH <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"</span>);
</code></pre><hr><p>在客户端中使用，下面代码来获取合约的PERMIT_TYPEHASH 和 DOMAIN_SEPARATOR</p><pre data-type="codeBlock" text="const contract = new ClientContract(abi, &apos;0x6b175474e89094c44da98b954eedeac495271d0f&apos;, 1)
    const calls = [
        contract.PERMIT_TYPEHASH(),
        contract.DOMAIN_SEPARATOR(),
    ]
    const [PERMIT_TYPEHASH, DOMAIN_SEPARATOR] = await multicallClient(calls)
    //DOMAIN_SEPARATOR: 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7
    //PERMIT_TYPEHASH: 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb
"><code>const <span class="hljs-class"><span class="hljs-keyword">contract</span> = <span class="hljs-title"><span class="hljs-keyword">new</span></span> <span class="hljs-title">ClientContract</span>(<span class="hljs-params"><span class="hljs-built_in">abi</span>, <span class="hljs-string">'0x6b175474e89094c44da98b954eedeac495271d0f'</span>, <span class="hljs-number">1</span></span>)
    <span class="hljs-title">const</span> <span class="hljs-title">calls</span> = [
        <span class="hljs-title"><span class="hljs-keyword">contract</span></span>.<span class="hljs-title">PERMIT_TYPEHASH</span>(<span class="hljs-params"></span>),
        <span class="hljs-title"><span class="hljs-keyword">contract</span></span>.<span class="hljs-title">DOMAIN_SEPARATOR</span>(<span class="hljs-params"></span>),
    ]
    <span class="hljs-title">const</span> [<span class="hljs-title">PERMIT_TYPEHASH</span>, <span class="hljs-title">DOMAIN_SEPARATOR</span>] = <span class="hljs-title">await</span> <span class="hljs-title">multicallClient</span>(<span class="hljs-params">calls</span>)
    <span class="hljs-comment">//DOMAIN_SEPARATOR: 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7</span>
    <span class="hljs-comment">//PERMIT_TYPEHASH: 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb</span>
</span></code></pre><p>之后构造签名合约里用到的structureHash</p><pre data-type="codeBlock" text="const digestHash = web3.utils.keccak256(web3.eth.abi.encodeParameters([&apos;bytes32&apos;, &apos;address&apos;, &apos;address&apos;, &apos;uint256&apos;, &apos;uint256&apos;, &apos;uint256&apos;], [
          PERMIT_TYPEHASH,
          holder,//你的地址
          spender,//授权给目标地址
              amount,//你要授权的数量
          nonce,//你在DAI里面的nonce
          expiry,//授权到期时间
          ]
      ))
"><code>const digestHash <span class="hljs-operator">=</span> web3.utils.keccak256(web3.eth.abi.encodeParameters([<span class="hljs-string">'bytes32'</span>, <span class="hljs-string">'address'</span>, <span class="hljs-string">'address'</span>, <span class="hljs-string">'uint256'</span>, <span class="hljs-string">'uint256'</span>, <span class="hljs-string">'uint256'</span>], [
          PERMIT_TYPEHASH,
          holder,<span class="hljs-comment">//你的地址</span>
          spender,<span class="hljs-comment">//授权给目标地址</span>
              amount,<span class="hljs-comment">//你要授权的数量</span>
          nonce,<span class="hljs-comment">//你在DAI里面的nonce</span>
          expiry,<span class="hljs-comment">//授权到期时间</span>
          ]
      ))
</code></pre><p>对这个结构进行签名，之后发送给B，就完成了这个 permit 的签发。</p><pre data-type="codeBlock" text="const signatureHash = await web3.eth.sign(digest, account);
"><code>const <span class="hljs-attr">signatureHash</span> = await web3.eth.sign(digest, account)<span class="hljs-comment">;</span>
</code></pre><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">后</h1><p>对Permit 原理上有大概的理解，些许有些复杂，不过好在应用上 openzeppelin 做了很大程度的封装。</p><p>在调用前端有 EIP712 的helper 直接引用即可，合约部分默认的 ERC20的合约已经包含了Permit 的方法。</p><p>研究底层的实现还算有趣。</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/ab4484939f9f83108fc90fd8c2e64bbc46bad886278ed6181d16b44297ab90c2.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[multi_claim 合约分析]]></title>
            <link>https://paragraph.com/@r4yyy/multi-claim</link>
            <guid>SRJOFuOyOFREqo8qKKGw</guid>
            <pubDate>Sun, 08 May 2022 16:52:34 GMT</pubDate>
            <description><![CDATA[前囤了很久的一个任务，来做一个简单的代码解析。 一个用于合约批量Claim 的操作。 比如之前的$rnd，就可以使用这套代码来进行批量的Claim 的交互操作。代码仓库正文代码思路对代码思路来进行一个简单梳理。先定义一个ERC20 的 interface 用于来进行合约来进行交互。之后new claim 合约来进行对token的mint操作，在完成mint之后批量的转回sender。 这里的前提是代码没有EOA(Externally Owned Accounts)机制也就是说没有，对合约账户CA还是外部账户EOA的检测。 另外这里还需要说明的是，这种mint的Token是没有验证机制的，就是有claim接口之后就可以随便领取的。代码分析栈大体按照调用栈来逐步分析callAddresstoabi.encodePackedNew claimerclaimtransferCall这里是这个合约的主调函数，函数体如下。这里主要是进行 循环并且自增nonce，计算出即将new 出来的合约的地址，用于后面的转账操作。之后吧nonce +1 之后进行接下来的操作。 function call(...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前</h1><p>囤了很久的一个任务，来做一个简单的代码解析。 一个用于合约批量Claim 的操作。</p><p>比如之前的$rnd，就可以使用这套代码来进行批量的Claim 的交互操作。</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GGCCCC/airdrop_multi_claim/blob/main/contracts/multi_claim.sol">代码仓库</a></p></li></ul><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">正文</h1><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">代码思路</h2><p>对代码思路来进行一个简单梳理。先定义一个ERC20 的 interface 用于来进行合约来进行交互。之后new claim 合约来进行对token的mint操作，在完成mint之后批量的转回sender。</p><p>这里的前提是代码没有EOA(Externally Owned Accounts)机制也就是说没有，对合约账户CA还是外部账户EOA的检测。</p><p>另外这里还需要说明的是，这种mint的Token是没有验证机制的，就是有claim接口之后就可以随便领取的。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">代码分析</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">栈</h3><p>大体按照调用栈来逐步分析</p><ol><li><p>call</p></li><li><p>Addressto</p><ol><li><p>abi.encodePacked</p></li></ol></li><li><p>New claimer</p><ol><li><p>claim</p></li><li><p>transfer</p></li></ol></li></ol><h3 id="h-call" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Call</h3><p>这里是这个合约的主调函数，函数体如下。这里主要是进行 循环并且自增nonce，计算出即将new 出来的合约的地址，用于后面的转账操作。之后吧nonce +1 之后进行接下来的操作。</p><pre data-type="codeBlock" text="    function call(uint256 times) public {
        for(uint i=0;i&lt;times;++i){
            address to = addressto(address(this), nonce);
            new claimer(to, address(msg.sender));
            nonce+=1;
        }
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">call</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> times</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">uint</span> i<span class="hljs-operator">=</span><span class="hljs-number">0</span>;i<span class="hljs-operator">&#x3C;</span>times;<span class="hljs-operator">+</span><span class="hljs-operator">+</span>i){
            <span class="hljs-keyword">address</span> to <span class="hljs-operator">=</span> addressto(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), nonce);
            <span class="hljs-keyword">new</span> claimer(to, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>));
            nonce<span class="hljs-operator">+</span><span class="hljs-operator">=</span><span class="hljs-number">1</span>;
        }
    }
</code></pre><h3 id="h-addressto" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">addressto</h3><p>这个是比较有意思的地方，在一个合约创建之前能否知道这个的地址？</p><p>答案是可以的，这个也被用于一些LP添加前的预判，也就是说可能LP还没创建，你就可以拿到LP的地址，早早冲进去。</p><p>这里的 addressto 就是这个算法的实现。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">合约地址生成</h4><p>资料参考：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/2019/06/10/address-compute">以太坊合约地址是怎么计算出来的？</a></p><blockquote><p>以太坊合约的地址是根据创建者（sender）的地址以及创建者发送过的交易数量（nonce）来计算确定的。 <code>sender</code>和<code>nonce</code> 进行<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/2019/05/20/geth-rlp-encode/">RLP编码</a>，然后用<code>Keccak-256</code> 进行hash计算。</p></blockquote><pre data-type="codeBlock" text="nonce0 = address(keccak256(0xd6, 0x94, address, 0x80))
nonce1 = address(keccak256(0xd6, 0x94, address, 0x01))
"><code>nonce0 <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">keccak256</span>(<span class="hljs-number">0xd6</span>, <span class="hljs-number">0x94</span>, <span class="hljs-keyword">address</span>, <span class="hljs-number">0x80</span>))
nonce1 <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">keccak256</span>(<span class="hljs-number">0xd6</span>, <span class="hljs-number">0x94</span>, <span class="hljs-keyword">address</span>, <span class="hljs-number">0x01</span>))
</code></pre><p>所以在这个合约里面可以看到也是按这个形式来进行了字节码的组装。这里来对组装过程进行分析。</p><p>这里第一个点是 <code>0x80</code> 在nonce 为0 的时候是它，但是在 nonce 为1的情况下却变成了 <code>0x01</code> 在。</p><hr><h4 id="h-encodepacked" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">encodePacked</h4><p>encodePacked 是solidity 中的紧打包的方式，用于构造原始的字节串。</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://solidity-cn.readthedocs.io/zh/develop/abi-spec.html#abi-function-selector">函数选择器</a> 不进行编码，</p></li><li><p>长度低于 32 字节的类型，既不会进行补 0 操作，也不会进行符号扩展</p></li><li><p>动态类型会直接进行编码，并且不包含长度信息。</p></li></ul><hr><h4 id="h-assembly" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">assembly</h4><p>这里作者还是使用了内联汇编，但是的确没有搞懂其中的意思，通过doc看到的，这个是把变量保存到0offset的内存，之后读取出来。感觉和直接return 是一个意思。可以只是一种用法练习，这里先存疑。后面清楚了在进行补充</p><pre data-type="codeBlock" text="assembly {
    mstore(0, hash)
    _address := mload(0)
}
"><code><span class="hljs-keyword">assembly</span> {
    <span class="hljs-built_in">mstore</span>(<span class="hljs-number">0</span>, hash)
    _address <span class="hljs-operator">:=</span> <span class="hljs-built_in">mload</span>(<span class="hljs-number">0</span>)
}
</code></pre><h4 id="h-new-claimer" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">New claimer</h4><p>使用上面的函数计算得到这里Claimer 的合约地址，来传入这里的构造函数。用于这里对是否成功mint来做判断。避免后面的transfer的操作节约gas。</p><p>这里直接调用Token合约的 Claim 函数，如果有余额之后来进行transfer转账。汇集到总的账户下，完成批量mint。</p><pre data-type="codeBlock" text="contract claimer{
    constructor(address selfAdd, address receiver){
        address contra = address(0xbb2A2D70d6a4B80FA2C4d4Ca43a8525da430196c);
        airdrop(contra).claim();
        uint256 balance = airdrop(contra).balanceOf(selfAdd);
        require(balance&gt;0,&apos;Oh no&apos;);
        airdrop(contra).transfer(receiver, balance);
    }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">claimer</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> selfAdd, <span class="hljs-keyword">address</span> receiver</span>)</span>{
        <span class="hljs-keyword">address</span> contra <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0xbb2A2D70d6a4B80FA2C4d4Ca43a8525da430196c</span>);
        airdrop(contra).claim();
        <span class="hljs-keyword">uint256</span> balance <span class="hljs-operator">=</span> airdrop(contra).balanceOf(selfAdd);
        <span class="hljs-built_in">require</span>(balance<span class="hljs-operator">></span><span class="hljs-number">0</span>,<span class="hljs-string">'Oh no'</span>);
        airdrop(contra).<span class="hljs-built_in">transfer</span>(receiver, balance);
    }
}
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">值得注意的</h3><p>前面提到这里的批量脚本只适用于没有验证EOA的，且每人人到可以claim 的项目，这里来担当度讨论一下，关于合约中的EOA的校验的方法。</p><p>这里使用到 深入解析<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/taipei-ethereum-meetup/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90solidity%E5%90%88%E7%B4%84-4213c8c7dfa0">Solidity</a> 里面的 <strong>3. 修補EtherDice的漏洞</strong> 这段落。solidity里面提供原生 extcodesize 操作符号来进行EOA 地址的验证</p><pre data-type="codeBlock" text="assembly { size := extcodesize(addr) }
"><code><span class="hljs-keyword">assembly</span> { size <span class="hljs-operator">:=</span> <span class="hljs-built_in">extcodesize</span>(addr) }
</code></pre><p>这里的extcodesize实际上是runtime 的bytecode 也就是运行时的 字节码，对合约地址 这里的 byte是 0。</p><p>所以这里可以做出来下下面的验证方式，来进行 EOA地址的检测。</p><pre data-type="codeBlock" text="    function isHuman(address addr) internal view returns (bool) {
      uint size;
      assembly { size := extcodesize(addr) }
      return size == 0;
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isHuman</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> addr</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
      <span class="hljs-keyword">uint</span> size;
      <span class="hljs-keyword">assembly</span> { size <span class="hljs-operator">:=</span> <span class="hljs-built_in">extcodesize</span>(addr) }
      <span class="hljs-keyword">return</span> size <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
    }
</code></pre><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">后</h1><p>至此，这个mutilclaim 的合约已经分析完毕了，总结起来就是创建合约使用合约地址来远程调用token的claim函数，之后再使用标准的 transfer 的函数来吧token汇集到sender。</p><p>后面有实践的机会可以操作一波。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">附</h2><p>附上全部的代码内容，完全拷贝自<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GGCCCC/airdrop_multi_claim/blob/main/contracts/multi_claim.sol">代码仓库</a></p><pre data-type="codeBlock" text="// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

interface airdrop {
    function transfer(address recipient, uint256 amount) external;
    function balanceOf(address account) external view returns (uint256);
    function claim() external;
}

contract multiCall{
    uint256 nonce = 1;
    function addressto(address _origin, uint256 _nonce) internal pure returns (address _address) {
        bytes memory data;
        if(_nonce == 0x00)          data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80));
        else if(_nonce &lt;= 0x7f)     data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce));
        else if(_nonce &lt;= 0xff)     data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce));
        else if(_nonce &lt;= 0xffff)   data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce));
        else if(_nonce &lt;= 0xffffff) data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce));
        else                        data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce));
        bytes32 hash = keccak256(data);
        assembly {
            mstore(0, hash)
            _address := mload(0)
        }
    }
    function call(uint256 times) public {
        for(uint i=0;i&lt;times;++i){
            address to = addressto(address(this), nonce);
            new claimer(to, address(msg.sender));
            nonce+=1;
        }
    }
}
contract claimer{
    constructor(address selfAdd, address receiver){
        address contra = address(0xbb2A2D70d6a4B80FA2C4d4Ca43a8525da430196c);
        airdrop(contra).claim();
        uint256 balance = airdrop(contra).balanceOf(selfAdd);
        require(balance&gt;0,&apos;Oh no&apos;);
        airdrop(contra).transfer(receiver, balance);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: GPL-3.0</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">airdrop</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> recipient, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">balanceOf</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> account</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">claim</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;
}

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">multiCall</span></span>{
    <span class="hljs-keyword">uint256</span> nonce <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addressto</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _origin, <span class="hljs-keyword">uint256</span> _nonce</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span> _address</span>) </span>{
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data;
        <span class="hljs-keyword">if</span>(_nonce <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0x00</span>)          data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xd6</span>), <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x94</span>), _origin, <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x80</span>));
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(_nonce <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">0x7f</span>)     data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xd6</span>), <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x94</span>), _origin, <span class="hljs-keyword">uint8</span>(_nonce));
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(_nonce <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">0xff</span>)     data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xd7</span>), <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x94</span>), _origin, <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x81</span>), <span class="hljs-keyword">uint8</span>(_nonce));
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(_nonce <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">0xffff</span>)   data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xd8</span>), <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x94</span>), _origin, <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x82</span>), <span class="hljs-keyword">uint16</span>(_nonce));
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(_nonce <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">0xffffff</span>) data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xd9</span>), <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x94</span>), _origin, <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x83</span>), <span class="hljs-keyword">uint24</span>(_nonce));
        <span class="hljs-keyword">else</span>                        data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xda</span>), <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x94</span>), _origin, <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0x84</span>), <span class="hljs-keyword">uint32</span>(_nonce));
        <span class="hljs-keyword">bytes32</span> hash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(data);
        <span class="hljs-keyword">assembly</span> {
            <span class="hljs-built_in">mstore</span>(<span class="hljs-number">0</span>, hash)
            _address <span class="hljs-operator">:=</span> <span class="hljs-built_in">mload</span>(<span class="hljs-number">0</span>)
        }
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">call</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> times</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">uint</span> i<span class="hljs-operator">=</span><span class="hljs-number">0</span>;i<span class="hljs-operator">&#x3C;</span>times;<span class="hljs-operator">+</span><span class="hljs-operator">+</span>i){
            <span class="hljs-keyword">address</span> to <span class="hljs-operator">=</span> addressto(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), nonce);
            <span class="hljs-keyword">new</span> claimer(to, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>));
            nonce<span class="hljs-operator">+</span><span class="hljs-operator">=</span><span class="hljs-number">1</span>;
        }
    }
}
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">claimer</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> selfAdd, <span class="hljs-keyword">address</span> receiver</span>)</span>{
        <span class="hljs-keyword">address</span> contra <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0xbb2A2D70d6a4B80FA2C4d4Ca43a8525da430196c</span>);
        airdrop(contra).claim();
        <span class="hljs-keyword">uint256</span> balance <span class="hljs-operator">=</span> airdrop(contra).balanceOf(selfAdd);
        <span class="hljs-built_in">require</span>(balance<span class="hljs-operator">></span><span class="hljs-number">0</span>,<span class="hljs-string">'Oh no'</span>);
        airdrop(contra).<span class="hljs-built_in">transfer</span>(receiver, balance);
    }
}
</code></pre>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[动荡行情U理财]]></title>
            <link>https://paragraph.com/@r4yyy/u</link>
            <guid>Hupeb3womg9Wm0IET2ur</guid>
            <pubDate>Sun, 08 May 2022 16:51:34 GMT</pubDate>
            <description><![CDATA[在动荡市场下寻求点稳点的收益 原则有以下两条稳定币理财先追求追求稳定，再追求APY不要让资金闲置，让他们去打工交易所cefi 理财如果你不知道apy 是哪里来的，那么你就是apy交易所提供的收益交易所的杠杆交易，你是贷出方，赚利息交易所搭建的某些项目节点staking，去哪staking 的年化交易所收集资金来进行代理的链上Defi项目理财在币安，OK 等交易所，都有部分限额的10%的年化可以拿下来。其他的可以兑换其他稳定币 USDC / USDT / TUSD ...推荐 吴忌寒 家的 Matrixport ，一款专门做理财的APP。 一个基于衍生品和各种策略的理财工具，U 目前有 5% 起步的收益。其中借贷/Defi/固收种类都有。 还有推出的私募基金，很值得购买。 这里的链接可以领 21U Matrixport LinkDEFI回顾下前面的原则，稳定为先。所以这里只推荐成熟项目，经过时间检验的Defi 项目，才是好项目。Curve 提供稳定币的兑换服务AMM，提供LP收取交易抽成 APY 4%Aave 提供借贷服务，项目方赚取利息差，贷出方收利息 APY 3%Compoun...]]></description>
            <content:encoded><![CDATA[<p>在动荡市场下寻求点稳点的收益</p><p>原则有以下两条</p><ol><li><p>稳定币理财先追求追求稳定，再追求APY</p></li><li><p>不要让资金闲置，让他们去打工</p></li></ol><h2 id="h-cefi" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">交易所cefi 理财</h2><blockquote><p>如果你不知道apy 是哪里来的，那么你就是apy</p></blockquote><p>交易所提供的收益</p><ul><li><p>交易所的杠杆交易，你是贷出方，赚利息</p></li><li><p>交易所搭建的某些项目节点staking，去哪staking 的年化</p></li><li><p>交易所收集资金来进行代理的链上Defi项目理财</p></li></ul><p>在币安，OK 等交易所，都有部分限额的10%的年化可以拿下来。其他的可以兑换其他稳定币</p><p>USDC / USDT / TUSD ...</p><hr><p>推荐 吴忌寒 家的 Matrixport ，一款专门做理财的APP。</p><p>一个基于衍生品和各种策略的理财工具，U 目前有 5% 起步的收益。其中借贷/Defi/固收种类都有。</p><p>还有推出的私募基金，很值得购买。</p><p>这里的链接可以领 21U <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://invest.matrixport.com/newRegister/cn?invite_code=GE4AVC">Matrixport Link</a></p><h2 id="h-defi" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">DEFI</h2><p>回顾下前面的原则，稳定为先。所以这里只推荐成熟项目，经过时间检验的Defi 项目，才是好项目。</p><ul><li><p>Curve 提供稳定币的兑换服务AMM，提供LP收取交易抽成 APY 4%</p></li><li><p>Aave 提供借贷服务，项目方赚取利息差，贷出方收利息 APY 3%</p></li><li><p>Compound 同样提供借贷服务 APY 3%</p></li><li><p>yearn 收益聚合器 机枪池的方式来在协议之间提供流动性 APY 2.8%</p></li></ul><p>（虽然Defi是革命性的技术，但是在现阶段其安全性以及实用性还是大幅低于Cefi）</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[ERC721项目发行分析]]></title>
            <link>https://paragraph.com/@r4yyy/erc721</link>
            <guid>GjbMpC2KuXVh9G9bt7jN</guid>
            <pubDate>Sat, 30 Apr 2022 03:30:02 GMT</pubDate>
            <description><![CDATA[前从庖丁解牛说起，懂了刀法之后就要开了这头牛了。 从区块链浏览器来看看 项目方的动作以及用户的东西还是很有意思的一件事情 这里使用Legend Maps Adventurers (LMA) 这个项目，自己蛮喜欢的一个 NFT项目。是做的链上的像素地牢。 这篇文章就简单的把这个NFT 的生命周期来分析一下。正文正文顺序就是直接按照合约的交互顺序来进行分析。deployTX：0xc6520011946abdf7577a6224a12d663556c836dbfd71d6fa4f802b3e2f186619 内容在Data字段，对应的是合约的编译后的 EVM操作码，实际上对应的就是合约。set Base URITX: 0x511692fe76c4c6a325bc47edc7d464269405dd498be608e8fa0744bbf74c3c16 在这里设置 NFT 的资源的BaseURIFunction: setBaseURI(string baseURI_) MethodID: 0x55f804b3 [0]: 00000000000000000000000000000000000...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前</h1><p>从庖丁解牛说起，懂了刀法之后就要开了这头牛了。</p><p>从区块链浏览器来看看 项目方的动作以及用户的东西还是很有意思的一件事情</p><p>这里使用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/token/0xca72fecc4bdb993650654a9881f2be15a7875796">Legend Maps Adventurers (LMA)</a> 这个项目，自己蛮喜欢的一个 NFT项目。是做的链上的像素地牢。</p><p>这篇文章就简单的把这个NFT 的生命周期来分析一下。</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">正文</h1><p>正文顺序就是直接按照合约的交互顺序来进行分析。</p><h2 id="h-deploy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">deploy</h2><p>TX：0xc6520011946abdf7577a6224a12d663556c836dbfd71d6fa4f802b3e2f186619</p><p>内容在Data字段，对应的是合约的编译后的 EVM操作码，实际上对应的就是合约。</p><h2 id="h-set-base-uri" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">set Base URI</h2><p>TX: 0x511692fe76c4c6a325bc47edc7d464269405dd498be608e8fa0744bbf74c3c16</p><p>在这里设置 NFT 的资源的BaseURI</p><pre data-type="codeBlock" text="Function: setBaseURI(string baseURI_)

MethodID: 0x55f804b3
[0]:  0000000000000000000000000000000000000000000000000000000000000020
[1]:  0000000000000000000000000000000000000000000000000000000000000036
[2]:  697066733a2f2f516d5a45545459326f766541327669375a336f7038505a4c6d
[3]:  614c54744d73437961623157786a5857664b78645a2f00000000000000000000
"><code><span class="hljs-section">Function: setBaseURI(string baseURI_)</span>

<span class="hljs-section">MethodID: 0x55f804b3</span>
<span class="hljs-section">[0]:  0000000000000000000000000000000000000000000000000000000000000020</span>
<span class="hljs-section">[1]:  0000000000000000000000000000000000000000000000000000000000000036</span>
<span class="hljs-section">[2]:  697066733a2f2f516d5a45545459326f766541327669375a336f7038505a4c6d</span>
<span class="hljs-section">[3]:  614c54744d73437961623157786a5857664b78645a2f00000000000000000000</span>
</code></pre><p>涉及到的函数是下面这部分</p><pre data-type="codeBlock" text=" function setBaseURI(string memory baseURI_) external onlyOwner {
    _tokenBaseURI = baseURI_;
 }
"><code> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setBaseURI</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> baseURI_</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    _tokenBaseURI <span class="hljs-operator">=</span> baseURI_;
 }
</code></pre><p>设置的 baseURI 解码出来之后是 是一个 ipfs 的存储链接</p><ul><li><p>ipfs://QmZETTY2oveA2vi7Z3op8PZLmaLTtMsCyab1WxjXWfKxdZ/</p></li></ul><p>我们可以通过ipfs 的网关来进行访问</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ipfs.io/ipfs/QmZETTY2oveA2vi7Z3op8PZLmaLTtMsCyab1WxjXWfKxdZ">https://ipfs.io/ipfs/QmZETTY2oveA2vi7Z3op8PZLmaLTtMsCyab1WxjXWfKxdZ</a></p></li></ul><hr><p>这里项目方犯了一个错误，BASE设置错误了，所以项目方里面用下面这比交易修改</p><p>TX: 0x0379fddc8c43c35a3f7dd4403003cc44424e742f7aa0b9bf514c07702c21e1e2</p><h2 id="h-reserve" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Reserve</h2><p>交易TX: 0x7f8962455056175e720abf68e8bd89a3ba468ec3ded33567ec9a20c239d103b7</p><p>项目方调用合约来进行批量的NFT 预留。直接safemint 50个，到Owner自己的账户下。</p><pre data-type="codeBlock" text="Function: reserve(uint256 count)

MethodID: 0x819b25ba
[0]:  000000000000000000000000000000000000000000000000000000000000003
"><code><span class="hljs-section">Function: reserve(uint256 count)</span>

<span class="hljs-section">MethodID: 0x819b25ba</span>
<span class="hljs-section">[0]:  000000000000000000000000000000000000000000000000000000000000003</span>
</code></pre><pre data-type="codeBlock" text="  function reserve(uint256 count) external onlyOwner {
    _safeMint(msg.sender, count);
  }
"><code>  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reserve</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> count</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    _safeMint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, count);
  }
</code></pre><h2 id="h-set-root" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Set Root</h2><p>因为NFT项目有 白名单机制存在，所以这里存在白名单的存在，这里设置的是验证用的默克尔根.</p><p>TX: 0xe4bbbcb007714283b1b5f856182b92c721a2206a29eddadfc1f70436cce77bda</p><pre data-type="codeBlock" text="Function: setRoot(bytes32 _merkleRoot)

MethodID: 0xdab5f340
[0]:  30f52ca24faeadfdd4ef012276e95ee9f2e4100f66ff1f3cb64b5b99331399b5
"><code><span class="hljs-section">Function: setRoot(bytes32 _merkleRoot)</span>

<span class="hljs-section">MethodID: 0xdab5f340</span>
<span class="hljs-section">[0]:  30f52ca24faeadfdd4ef012276e95ee9f2e4100f66ff1f3cb64b5b99331399b5</span>
</code></pre><pre data-type="codeBlock" text="  function setRoot(bytes32 _merkleRoot) external onlyOwner {
    merkleRoot = _merkleRoot;
  }
"><code>  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setRoot</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> _merkleRoot</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    merkleRoot <span class="hljs-operator">=</span> _merkleRoot;
  }
</code></pre><p>具体的白名单的验证机制可以参考前面那篇<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog2.12ms.xyz/posts/2022/02/2022-02-07-%E6%B5%85%E6%9E%90%E7%99%BD%E5%90%8D%E5%8D%95%E6%A0%A1%E9%AA%8C%E5%8E%9F%E7%90%86/">《浅析白名单校验原理》</a>，这里就不多赘述了。</p><h2 id="h-toggle-whitelist" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Toggle Whitelist</h2><p>前面完成了Nft的前置工作，这里就需要用来打开NFT的销售了，函数体如下，对一个布尔型的变量来进行翻转。相关的函数在执行的前会对此变量进行检查。</p><pre data-type="codeBlock" text="  function toggleWhitelist() external onlyOwner {
    whitelistLive = !whitelistLive;
  }
"><code>  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toggleWhitelist</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    whitelistLive <span class="hljs-operator">=</span> <span class="hljs-operator">!</span>whitelistLive;
  }
</code></pre><h2 id="h-withelistmintmint" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">WitheListMint/Mint</h2><p>往后就是白名单用户mint nft的过程了。Data 体如下</p><pre data-type="codeBlock" text="Function: whitelistMint(uint256 mintsRequested, uint256 maxMints, bytes32[] proof)
MethodID: 0xc4be5b59
"><code>Function: whitelistMint(<span class="hljs-keyword">uint256</span> mintsRequested, <span class="hljs-keyword">uint256</span> maxMints, <span class="hljs-keyword">bytes32</span>[] proof)
MethodID: <span class="hljs-number">0xc4be5b59</span>
</code></pre><p>proof 这里是用户的 默克尔路径，具体的验证方式还是在《浅析白名单校验原理》这个文章里说明。目前基本上都是标准的函数。函数体如下，在里面补充备注说明</p><pre data-type="codeBlock" text="function whitelistMint(uint256 mintsRequested, uint256 maxMints, bytes32[] calldata proof) external payable {
    uint256 remainingMints = maxMints - whiteListPurchases[msg.sender];
    // 检查mint是否进行
    require(!saleLive &amp;&amp; whitelistLive, &quot;WHITELIST_CLOSED&quot;);
    // 检查默克尔路径
    require(_verify(_leaf(msg.sender, maxMints), proof), &quot;UNAUTHORIZED&quot;);
    // mint逻辑
    require(totalSupply() + mintsRequested &lt;= LMA_SUPPLY, &quot;EXCEEDS_SUPPLY&quot;);
    require(LMA_PRICE * mintsRequested &lt;= msg.value, &quot;INSUFFICIENT_ETH&quot;);
    require(mintsRequested &lt;= remainingMints, &quot;EXCEEDS_ALLOCATION&quot;);
    require(remainingMints &gt; 0, &quot;MINTS_CONSUMED&quot;);
        
    whiteListPurchases[msg.sender] += mintsRequested;

    _safeMint(msg.sender, mintsRequested);
}

function mint(uint256 mintsRequested) external payable {
    require(saleLive, &quot;SALE_CLOSED&quot;);
    require(!whitelistLive, &quot;WHITELIST_ONLY&quot;);
    require(LMA_PRICE * mintsRequested &lt;= msg.value, &quot;INSUFFICIENT_ETH&quot;);
    require(totalSupply() + mintsRequested &lt;= LMA_SUPPLY, &quot;EXCEEDS_SUPPLY&quot;);
        // mint 逻辑一致，去掉白名单验证逻辑
    _safeMint(msg.sender, mintsRequested);
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">whitelistMint</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> mintsRequested, <span class="hljs-keyword">uint256</span> maxMints, <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">calldata</span> proof</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-keyword">uint256</span> remainingMints <span class="hljs-operator">=</span> maxMints <span class="hljs-operator">-</span> whiteListPurchases[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>];
    <span class="hljs-comment">// 检查mint是否进行</span>
    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>saleLive <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> whitelistLive, <span class="hljs-string">"WHITELIST_CLOSED"</span>);
    <span class="hljs-comment">// 检查默克尔路径</span>
    <span class="hljs-built_in">require</span>(_verify(_leaf(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, maxMints), proof), <span class="hljs-string">"UNAUTHORIZED"</span>);
    <span class="hljs-comment">// mint逻辑</span>
    <span class="hljs-built_in">require</span>(totalSupply() <span class="hljs-operator">+</span> mintsRequested <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> LMA_SUPPLY, <span class="hljs-string">"EXCEEDS_SUPPLY"</span>);
    <span class="hljs-built_in">require</span>(LMA_PRICE <span class="hljs-operator">*</span> mintsRequested <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>, <span class="hljs-string">"INSUFFICIENT_ETH"</span>);
    <span class="hljs-built_in">require</span>(mintsRequested <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> remainingMints, <span class="hljs-string">"EXCEEDS_ALLOCATION"</span>);
    <span class="hljs-built_in">require</span>(remainingMints <span class="hljs-operator">></span> <span class="hljs-number">0</span>, <span class="hljs-string">"MINTS_CONSUMED"</span>);
        
    whiteListPurchases[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> mintsRequested;

    _safeMint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, mintsRequested);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> mintsRequested</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>(saleLive, <span class="hljs-string">"SALE_CLOSED"</span>);
    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>whitelistLive, <span class="hljs-string">"WHITELIST_ONLY"</span>);
    <span class="hljs-built_in">require</span>(LMA_PRICE <span class="hljs-operator">*</span> mintsRequested <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>, <span class="hljs-string">"INSUFFICIENT_ETH"</span>);
    <span class="hljs-built_in">require</span>(totalSupply() <span class="hljs-operator">+</span> mintsRequested <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> LMA_SUPPLY, <span class="hljs-string">"EXCEEDS_SUPPLY"</span>);
        <span class="hljs-comment">// mint 逻辑一致，去掉白名单验证逻辑</span>
    _safeMint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, mintsRequested);
}
</code></pre><h2 id="h-erc721-transfer" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ERC721 Transfer</h2><p>前面的过程完成了Nft的mint 过程。后面的的话就是用户侧的操作了，主要有 approve 和 转移。</p><ul><li><p>setApprovalForAll</p></li><li><p>safeTransferFrom</p></li></ul><p>这里是 ERC721的标准函数，就不再这里展开分析了。</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">后</h1><p>通过分析一些链上的交互来学习链上原理还是蛮不错的过程，这样可以看清楚项目的链上运作流程，还有像是看到项目方操作失误这种有意思的事件。</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[NFT合约级mint实践]]></title>
            <link>https://paragraph.com/@r4yyy/nft-mint</link>
            <guid>fxDxyanlRTrUFXCsOaRV</guid>
            <pubDate>Sat, 30 Apr 2022 03:26:56 GMT</pubDate>
            <description><![CDATA[简明教程找到对应地址 可以先试用页面触发小狐狸的审批页面，之后找对应的地址。浏览器查看合约交互 nft 项目一般都是 mint 的方法的调用。在交易详情的advance里面看到decode 之后的内容，一般是合约的方法名和参数看合约代码 直接找到 调用的mint 函数的定义内容，检查其逻辑。构造调用参数，进行合约交互实践记录以下面的合约为例子。 https://polygonscan.com/address/0x3a67c34c7dbd846108044fed38a3154b99a97cb1 看到有大量的Mint 调用，在advence查看调用内容，如下Function: mint(uint256 amount, uint256 timeout) *** MethodID: 0x1b2ef1ca [0]: 0000000000000000000000000000000000000000000000000000000000000001 [1]: 000000000000000000000000000000000000000000000000000000000001c378 这里主要...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">简明教程</h1><ol><li><p>找到对应地址</p><p>可以先试用页面触发小狐狸的审批页面，之后找对应的地址。</p></li><li><p>浏览器查看合约交互</p><p>nft 项目一般都是 mint 的方法的调用。在交易详情的advance里面看到decode 之后的内容，一般是合约的方法名和参数</p></li><li><p>看合约代码</p><p>直接找到 调用的mint 函数的定义内容，检查其逻辑。</p></li><li><p>构造调用参数，进行合约交互</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">实践记录</h2><p>以下面的合约为例子。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://polygonscan.com/address/0x3a67c34c7dbd846108044fed38a3154b99a97cb1">https://polygonscan.com/address/0x3a67c34c7dbd846108044fed38a3154b99a97cb1</a></p><p>看到有大量的Mint 调用，在advence查看调用内容，如下</p><pre data-type="codeBlock" text="Function: mint(uint256 amount, uint256 timeout) ***

MethodID: 0x1b2ef1ca
[0]:  0000000000000000000000000000000000000000000000000000000000000001
[1]:  000000000000000000000000000000000000000000000000000000000001c378
"><code>Function: mint(<span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">uint256</span> timeout) <span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span>

MethodID: <span class="hljs-number">0x1b2ef1ca</span>
[<span class="hljs-number">0</span>]:  0000000000000000000000000000000000000000000000000000000000000001
[<span class="hljs-number">1</span>]:  000000000000000000000000000000000000000000000000000000000001c378
</code></pre><p>这里主要是看解码出来的 Functiin name，这里的参数不一定百分百准确，因为是使用 字典来进行解码的。</p><p>这里推荐使用 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.4byte.directory/">4byte</a>来进行方法名解码。有比较强大的 DB</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.4byte.directory/signatures/?bytes4_signature=0x1b2ef1ca">https://www.4byte.directory/signatures/?bytes4_signature=0x1b2ef1ca</a></p><p>我们可以看到解码的内容是。</p><p>167720mint(uint256,uint256)<code>0x1b2ef1ca</code></p><p>在其公布的sol code的 <code>RaremintsERC721</code> 里面我们可以找到mint的定义：</p><pre data-type="codeBlock" text="    function mint(uint256 num, uint256 metadata) public payable {
        uint256 supply = totalSupply();
        require( !_paused,                              &quot;Sale paused&quot; );
        require( supply + num &lt;= _supply - _reserved,   &quot;Exceeds maximum supply&quot; );
        require( msg.value &gt;= _price * num,             &quot;Ether sent is not correct&quot; );

        for(uint256 i; i &lt; num; i++){
            performMint(msg.sender, supply + i, metadata);
        }
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> num, <span class="hljs-keyword">uint256</span> metadata</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-keyword">uint256</span> supply <span class="hljs-operator">=</span> totalSupply();
        <span class="hljs-built_in">require</span>( <span class="hljs-operator">!</span>_paused,                              <span class="hljs-string">"Sale paused"</span> );
        <span class="hljs-built_in">require</span>( supply <span class="hljs-operator">+</span> num <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> _supply <span class="hljs-operator">-</span> _reserved,   <span class="hljs-string">"Exceeds maximum supply"</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">></span><span class="hljs-operator">=</span> _price <span class="hljs-operator">*</span> num,             <span class="hljs-string">"Ether sent is not correct"</span> );

        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">uint256</span> i; i <span class="hljs-operator">&#x3C;</span> num; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>){
            performMint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, supply <span class="hljs-operator">+</span> i, metadata);
        }
    }
</code></pre><p>可以看到 ：</p><ol><li><p>payable</p></li><li><p>num</p></li><li><p>metadata</p></li></ol><p>这里第三个request就看出来我们的payable需要达到单价以上。在这里price 设置的是 0.5matic 所以我们这里就保持1:0.5 的数量价格比。另外一个参数是 metadata，这里 <code>performMint</code>用到了这个参数，其函数内容如下。</p><pre data-type="codeBlock" text="    function performMint(address _to, uint256 _tokenId, uint256 _metadata) private {
        _safeMint(_to, _tokenId);
        emit MintEvent(_to, _tokenId, _metadata);
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">performMint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _to, <span class="hljs-keyword">uint256</span> _tokenId, <span class="hljs-keyword">uint256</span> _metadata</span>) <span class="hljs-title"><span class="hljs-keyword">private</span></span> </span>{
        _safeMint(_to, _tokenId);
        <span class="hljs-keyword">emit</span> MintEvent(_to, _tokenId, _metadata);
    }
</code></pre><p>我们可以看到，这里只是在 emit 事件的时候用到了这个 metadata，其他地方没有，所以这里意味着我们可以进行自定义。</p><p>综上 构建出参数 ，使用 write Contract 来进行交互。</p><p>找到 mint 函数，参数如下</p><ol><li><p>Payable: 1.5 matic</p></li><li><p>Num: 3</p></li><li><p>Metadata: 0000（任意）</p></li></ol><p>点击 Write 写合约 ，完成交互过程。简单轻松</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">后</h2><p>从简单的流程分析来切入智能合约的路子上</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[浅析白名单校验原理]]></title>
            <link>https://paragraph.com/@r4yyy/clCnL62dg4G12fAddQhz</link>
            <guid>clCnL62dg4G12fAddQhz</guid>
            <pubDate>Mon, 07 Feb 2022 14:46:16 GMT</pubDate>
            <description><![CDATA[ETH 上的GAS 不是欢乐豆 这篇文章来在于一个群中的一次关于一个项目的大讨论。 一个NFT项目 mint 的成本异常的高。售价是 0.01 但是实际上mint 的 gas 在 0.1+@75gwei的情况下。 网络并不繁忙。 项目的合约地址在下面 https://etherscan.io/address/0x99a8e1cc829d6b33dc37851466c360e08757a0ae 在 MonkeyPoly.sol 的69~76段。可以看到一个白名单校验的函数。这里存在着一个循环结构。我们知道ETH上的计算是需要gas的，所以这里的一个对白名单列表的遍历。就是调用高gas的元凶。function isWhitelisted(address _user) public view returns (bool) { for (uint i = 0; i &#x3C; whitelistedAddresses.length; i++) { if (whitelistedAddresses[i] == _user) { return true; } } return false;...]]></description>
            <content:encoded><![CDATA[<p>ETH 上的GAS 不是欢乐豆</p><p>这篇文章来在于一个群中的一次关于一个项目的大讨论。</p><p>一个NFT项目 mint 的成本异常的高。售价是 0.01 但是实际上mint 的 gas 在 0.1+@75gwei的情况下。 网络并不繁忙。</p><p>项目的合约地址在下面</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x99a8e1cc829d6b33dc37851466c360e08757a0ae">https://etherscan.io/address/0x99a8e1cc829d6b33dc37851466c360e08757a0ae</a></p><p>在 MonkeyPoly.sol 的69~76段。可以看到一个白名单校验的函数。这里存在着一个循环结构。我们知道ETH上的计算是需要gas的，所以这里的一个对白名单列表的遍历。就是调用高gas的元凶。</p><pre data-type="codeBlock" text="function isWhitelisted(address _user) public view returns (bool) {
    for (uint i = 0; i &lt; whitelistedAddresses.length; i++) {
      if (whitelistedAddresses[i] == _user) {
        return true;
      }
    }
    return false;
  }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isWhitelisted</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _user</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> whitelistedAddresses.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
      <span class="hljs-keyword">if</span> (whitelistedAddresses[i] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> _user) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
</code></pre><p>而在另一段代码里面我们看到了写入白名单的地方</p><pre data-type="codeBlock" text="function whitelistUsers(address[] calldata _users) public onlyOwner {
    delete whitelistedAddresses;
    whitelistedAddresses = _users;
  }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">whitelistUsers</span>(<span class="hljs-params"><span class="hljs-keyword">address</span>[] <span class="hljs-keyword">calldata</span> _users</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    <span class="hljs-keyword">delete</span> whitelistedAddresses;
    whitelistedAddresses <span class="hljs-operator">=</span> _users;
  }
</code></pre><p>这里项目方直接把地址列表给导入了进去，通过传参的方式。十分的生猛。</p><p>这里的gas 消耗，可以参考下文，记录了每个evm操作符消耗的 gas 情况。对上面的来进行一个估算。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.csdn.net/shangsongwww/article/details/90178332">以太坊的存储成本_跨链技术践行者-CSDN博客</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/djrtwo/evm-opcode-gas-costs/blob/master/opcode-gas-costs_EIP-150_revision-1e18248_2017-04-12.csv">evm-opcode-gas-costs/opcode-gas-costs_EIP-150_revision-1e18248_2017-04-12.csv at master · djrtwo/evm-opcode-gas-costs</a></p><p>所以在这里，项目方付出了巨大的存储成本。 用户付出了巨大的gas 成本。矿工窃喜。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x99a8e1cc829d6b33dc37851466c360e08757a0ae">https://etherscan.io/address/0x99a8e1cc829d6b33dc37851466c360e08757a0ae</a></p><p>具体的mint 成本，可以在这里里面看到，很多都是 out of gas 导致交易被 <strong>Reverted了.</strong> 真的ETH 就是欢乐豆了呗。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">优雅的做法</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">合约里的实现</h3><p>在一般项目的白名单机制中普遍使用<strong>默克尔树</strong>来实现。</p><p>简单讲计算所有白名单钱包地址得到默克尔树的Root hash。</p><p>在合约中只需要存储Root hash值，在调用mint函数时页面上的JS代码基于钱包地址生成proof路径（地址的上级父节点hash），合约就可以校验该地址是否属于白名单。</p><p>这里使用 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x6fd053bff10512d743fa36c859e49351a4920df6#code">https://etherscan.io/address/0x6fd053bff10512d743fa36c859e49351a4920df6#code</a></p><p>这个项目的代码为例子。选取部分的nft预售的代码，内容如下，可见这里对于预售的调用做了 merkleProof 的判断。来验证是否是白名单用户。比那个 循环实现要好上太多。</p><pre data-type="codeBlock" text="// 预售代码
function mintNFTDuringPresale(
        uint256 _numOfTokens,
        bytes32[] memory _proof
    ) 
        public 
        payable
    {
        require(isActive, &apos;Sale is not active&apos;);
        require(isPresaleActive, &apos;Whitelist is not active&apos;);
        require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), &quot;Not whitelisted&quot;);
        // ...
"><code><span class="hljs-comment">// 预售代码</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mintNFTDuringPresale</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> _numOfTokens,
        <span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> _proof
    </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>(isActive, <span class="hljs-string">'Sale is not active'</span>);
        <span class="hljs-built_in">require</span>(isPresaleActive, <span class="hljs-string">'Whitelist is not active'</span>);
        <span class="hljs-built_in">require</span>(verify(_proof, <span class="hljs-keyword">bytes32</span>(<span class="hljs-keyword">uint256</span>(<span class="hljs-keyword">uint160</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>)))), <span class="hljs-string">"Not whitelisted"</span>);
        <span class="hljs-comment">// ...</span>
</code></pre><p>verify 的函数实现代码如下</p><pre data-type="codeBlock" text="// Verify MerkleProof
    function verify(bytes32[] memory proof, bytes32 leaf) public view returns (bool) {
        bytes32 computedHash = leaf;

        for (uint256 i = 0; i &lt; proof.length; i++) {
            bytes32 proofElement = proof[i];
            
            if (computedHash &lt;= proofElement) {
                // Hash(current computed hash + current element of the proof)
                computedHash = sha256(abi.encodePacked(computedHash, proofElement));
            } else {
                // Hash(current element of the proof + current computed hash)
                computedHash = sha256(abi.encodePacked(proofElement, computedHash));
            }
        }

        // Check if the computed hash (root) is equal to the provided root
        return computedHash == root;
    }
"><code><span class="hljs-comment">// Verify MerkleProof</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verify</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span>[] <span class="hljs-keyword">memory</span> proof, <span class="hljs-keyword">bytes32</span> leaf</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
        <span class="hljs-keyword">bytes32</span> computedHash <span class="hljs-operator">=</span> leaf;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint256</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> proof.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            <span class="hljs-keyword">bytes32</span> proofElement <span class="hljs-operator">=</span> proof[i];
            
            <span class="hljs-keyword">if</span> (computedHash <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> proofElement) {
                <span class="hljs-comment">// Hash(current computed hash + current element of the proof)</span>
                computedHash <span class="hljs-operator">=</span> <span class="hljs-built_in">sha256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(computedHash, proofElement));
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// Hash(current element of the proof + current computed hash)</span>
                computedHash <span class="hljs-operator">=</span> <span class="hljs-built_in">sha256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(proofElement, computedHash));
            }
        }

        <span class="hljs-comment">// Check if the computed hash (root) is equal to the provided root</span>
        <span class="hljs-keyword">return</span> computedHash <span class="hljs-operator">=</span><span class="hljs-operator">=</span> root;
    }
</code></pre><h3 id="h-dapp" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Dapp 中的实现</h3><p>前面提到，在使用默克尔树进行验证 的情况下，在合约中只需要保存一个 默克尔根。由用户在前端提供 proof 来证明自己在 默克尔树中。默克尔树这一个结构是比较熟悉的了。下面给出简单的结构。数据在 最终的block中，进行hash 之后得到叶子，叶子再进行hash 之后得到父节点，最终得到一个 Root hash。位于树上叶节点正上方的每个父节点最多只能培养两个叶节点。如果存在奇数个叶节点，则父节点将培育一个叶节点。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/de1744f3cb67a14ff142fac3da747eaa9065ecb722e9f4fd893347b8107f2e26.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>当白名单的地址是恒定且已知的话，就可以使用 merkle tree 的结构来把数据构造出来。下面给出引用的代码片段，下面是 使用 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.npmjs.com/package/merkletreejs">merkletreejs</a>和<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.npmjs.com/package/keccak256">keccak256</a> 来实现的 默克尔树的构建。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d006711bee19de002b38c22cf47317906f318bb39377bb447f317c154cb3df29.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>最终的运行结果如下图</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/68f95cd471c37d9848705d5573841c3fa2cde48e4c87001c05a7e107ae73eba4.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Merkle Tree 的独创性点在于它不需要任何原始数据来验证节点是否存在于树中。只需要知道直接相邻叶节点哈希（如果有的话）和直接在叶节点上方的相邻父节点哈希。所以这一段称之为 默克尔路径。</p><p>所以，我们会最终得到一个类似这样的 默克尔路径。</p><pre data-type="codeBlock" text="Merkle Proof for Address 0x7b6217492d5B7088A8b7adE75364F289Caa1A0Fe
[
    &apos;Oxd7a3faaaa893aa663f894f29aae4851e028b4ffa394e825816b5cb3b163b20ce&apos;
    &apos;0x6dcecbf773ecd9d6a2dffe343430bdb01d2d1efcc872e072201ffcd0133adeb0&apos;
    &apos;0x3cc09b3073afe3acf80dc067d2b5b4672dc055430f1cf45bf155e6eeb36ec010&apos;
]
"><code>Merkle Proof for <span class="hljs-selector-tag">Address</span> <span class="hljs-number">0</span>x7b6217492d5B7088A8b7adE75364F289Caa1A0Fe
<span class="hljs-selector-attr">[    <span class="hljs-string">'Oxd7a3faaaa893aa663f894f29aae4851e028b4ffa394e825816b5cb3b163b20ce'</span>    <span class="hljs-string">'0x6dcecbf773ecd9d6a2dffe343430bdb01d2d1efcc872e072201ffcd0133adeb0'</span>    <span class="hljs-string">'0x3cc09b3073afe3acf80dc067d2b5b4672dc055430f1cf45bf155e6eeb36ec010'</span>]</span>
</code></pre><p>回到上面的合约的校验代码，如果如果我提供的 proof 可以通过校验，那么我就可以证明我是存在这个原始列表中。交易不会被require 终止。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">小结</h3><p>到这里可以看到，使用默克尔树的方法在 EVM 上的实现，比使用循环来进行地址校验的方式优雅太多，只需要简单的几次hash 操作就可以完成校验，而且项目方更新白名单也可以简单的通过修改默克尔树的方式来实现。</p><p>更具体的实现细节可以看这篇 medium</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@ItsCuzzo/using-merkle-trees-for-nft-whitelists-523b58ada3f9">Using Merkle Trees for NFT Whitelists</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">另一个问题</h2><p>对于已知白名单的项目我们可以直接使用 默克尔书的方式来进行验证。但是像是 stopdao 这种项目。对于每一个参与OS 的人都可以获得空投。项目方不可能或者说很难获得全部的交互过的用户地址。怎么处理呢。</p><p>见到来说，这一类 我称之为 土DAO的原理是：后端私钥签名，合约公钥验证。当然这种方式的问题也先提出来：<strong>项目方有私钥在手，可以随随便便的签。所以理论上是无限量增发的。</strong></p><p>我们可以使用 SOS 的代码来进行分析。在Claim函数中我们可以看到最后一个 require</p><pre data-type="codeBlock" text="function claim(uint256 amountV, bytes32 r, bytes32 s) external {
        uint256 amount = uint248(amountV);
        uint8 v = uint8(amountV &gt;&gt; 248);
        uint256 total = _totalSupply + amount;
        require(total &lt;= MAX_SUPPLY, &quot;OpenDAO: Exceed max supply&quot;);
        require(minted(msg.sender) == 0, &quot;OpenDAO: Claimed&quot;);
                // 这里得到提交的数据指纹
        bytes32 digest = keccak256(abi.encodePacked(&quot;\x19Ethereum Signed Message:\n32&quot;, 
            ECDSA.toTypedDataHash(_domainSeparatorV4(),
                keccak256(abi.encode(MINT_CALL_HASH_TYPE, msg.sender, amount))
        )));
        // 这里对后端签出来的数据来进行验证，看看签名者是不是 cSinger 也就是项目作者。
        require(ecrecover(digest, v, r, s) == cSigner, &quot;OpenDAO: Invalid signer&quot;);
        _totalSupply = total;
        _mint(msg.sender, amount);
    }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">claim</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amountV, <span class="hljs-keyword">bytes32</span> r, <span class="hljs-keyword">bytes32</span> s</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-keyword">uint256</span> amount <span class="hljs-operator">=</span> <span class="hljs-keyword">uint248</span>(amountV);
        <span class="hljs-keyword">uint8</span> v <span class="hljs-operator">=</span> <span class="hljs-keyword">uint8</span>(amountV <span class="hljs-operator">></span><span class="hljs-operator">></span> <span class="hljs-number">248</span>);
        <span class="hljs-keyword">uint256</span> total <span class="hljs-operator">=</span> _totalSupply <span class="hljs-operator">+</span> amount;
        <span class="hljs-built_in">require</span>(total <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> MAX_SUPPLY, <span class="hljs-string">"OpenDAO: Exceed max supply"</span>);
        <span class="hljs-built_in">require</span>(minted(<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-number">0</span>, <span class="hljs-string">"OpenDAO: Claimed"</span>);
                <span class="hljs-comment">// 这里得到提交的数据指纹</span>
        <span class="hljs-keyword">bytes32</span> digest <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-string">"\x19Ethereum Signed Message:\n32"</span>, 
            ECDSA.toTypedDataHash(_domainSeparatorV4(),
                <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(MINT_CALL_HASH_TYPE, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount))
        )));
        <span class="hljs-comment">// 这里对后端签出来的数据来进行验证，看看签名者是不是 cSinger 也就是项目作者。</span>
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">ecrecover</span>(digest, v, r, s) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> cSigner, <span class="hljs-string">"OpenDAO: Invalid signer"</span>);
        _totalSupply <span class="hljs-operator">=</span> total;
        _mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }
</code></pre><p>两行关键代码已经写在上面了，可以看到在这种方式是依靠 项目方在后端来进行的签名来实现对Claim数量的确定。问题就是没有公开性。依赖项目方来进行签名。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>这篇文章，通过一个失败的例子来简单讲了一下 现在常用的白名单验证方式。后面拓展一下 土DAO 的白名单原理。有不正确的地方也欢迎大家指出。共同学习进步</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/49d1d8e809c9550d3cd9f3c1bceb1a3b58ca56a4aac28880c72c781e1ddb293b.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[ERC20 标准再学习]]></title>
            <link>https://paragraph.com/@r4yyy/erc20</link>
            <guid>RVS7OI4cilSmKOGAfQAv</guid>
            <pubDate>Tue, 18 Jan 2022 14:42:02 GMT</pubDate>
            <description><![CDATA[ERC20 标准，是ETH上的一种合约标准。固定了事件和对应的接口contract ERC20Interface { function totalSupply() public constant returns (uint); function balanceOf(address tokenOwner) public constant returns (uint balance); function allowance(address tokenOwner, address spender) public constant returns (uint remaining); function transfer(address to, uint tokens) public returns (bool success); function approve(address spender, uint tokens) public returns (bool success); function transferFrom(address from, address to, uint t...]]></description>
            <content:encoded><![CDATA[<p>ERC20 标准，是ETH上的一种合约标准。固定了事件和对应的接口</p><pre data-type="codeBlock" text="contract ERC20Interface {
      function totalSupply() public constant returns (uint);  
      function balanceOf(address tokenOwner) public constant returns (uint balance);
       function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
       function transfer(address to, uint tokens) public returns (bool success); 
      function approve(address spender, uint tokens) public returns (bool success); 
      function transferFrom(address from, address to, uint tokens) public returns (bool success);

      event Transfer(address indexed from, address indexed to, uint tokens);  
      event Approval(address indexed tokenOwner, address indexed spender, uint tokens);

    string public constant name = &quot;TEST Token&quot;;
    string public constant symbol = &quot;TST&quot;;
    uint8 public constant decimals = 18;  // 18 is the most common number of decimal places
        uint256 public _totalSupply;
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ERC20Interface</span> </span>{
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">totalSupply</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">constant</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span></span>)</span>;  
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">balanceOf</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> tokenOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">constant</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> balance</span>)</span>;
       <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">allowance</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> tokenOwner, <span class="hljs-keyword">address</span> spender</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">constant</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> remaining</span>)</span>;
       <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint</span> tokens</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span> success</span>)</span>; 
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">approve</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> spender, <span class="hljs-keyword">uint</span> tokens</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span> success</span>)</span>; 
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFrom</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint</span> tokens</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span> success</span>)</span>;

      <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> to, <span class="hljs-keyword">uint</span> tokens</span>)</span>;  
      <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Approval</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> tokenOwner, <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> spender, <span class="hljs-keyword">uint</span> tokens</span>)</span>;

    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> name <span class="hljs-operator">=</span> <span class="hljs-string">"TEST Token"</span>;
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> symbol <span class="hljs-operator">=</span> <span class="hljs-string">"TST"</span>;
    <span class="hljs-keyword">uint8</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> decimals <span class="hljs-operator">=</span> <span class="hljs-number">18</span>;  <span class="hljs-comment">// 18 is the most common number of decimal places</span>
        <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> _totalSupply;
}
</code></pre><p>合约分为 函数和事件变量，三个部分，</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">变量</h2><p>变量在 contract 里面是全局的。定义了Token 的名称符号和精度。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">函数</h2><h3 id="h-totalsupply" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">totalSupply</h3><p>可以看到函数部分，只是规定了接口名称。其中的实现逻辑就是开发者自行定义了。</p><p>比如这里的totalSupply 就是一个可以自定义的函数，这里的返回逻辑可以是一个定值。 或者是一个自动Burn的逻辑。</p><h3 id="h-balanceof" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">balanceOf</h3><p>这里的balanceOf，就要说到 ETH 的模型了。ETH 是账户模型，不是UTXO 的模型。所以这里的 balance 在合约里面是有状态的。地址和与余额之间有着对应的关系。</p><pre data-type="codeBlock" text="// 这里保存着这个账户下的该Token的余额
mapping (address =&gt; uint256) public balances
// 这里是一个重要的授权额度，也就是允许 外层addr 操作 内层 addr 的该Token 的数量。
// 所以一般的 授权额度就是这里。
mapping (address =&gt; mapping (address =&gt; uint256)) public allowed
"><code><span class="hljs-comment">// 这里保存着这个账户下的该Token的余额</span>
<span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> balances
<span class="hljs-comment">// 这里是一个重要的授权额度，也就是允许 外层addr 操作 内层 addr 的该Token 的数量。</span>
<span class="hljs-comment">// 所以一般的 授权额度就是这里。</span>
<span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint256</span>)) <span class="hljs-keyword">public</span> allowed
</code></pre><p>那么balanceOf这个就很好实现了，传入地址，返回地址在这个map 中的数量即可</p><pre data-type="codeBlock" text="function balanceOf(address tokenOwner) public constant returns (uint balance) {
       return balances[tokenOwner];
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">balanceOf</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> tokenOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">constant</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> balance</span>) </span>{
       <span class="hljs-keyword">return</span> balances[tokenOwner];
}
</code></pre><h3 id="h-transfer" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">transfer</h3><p>前面提到的 Token 实际上只是一个 账户的mapping，我们可以理解这个合约存储了每个人的账户以及余额。你的 Token 实际上只是这张合约里面的数据而已，实际上并没有进行转出。所以在这里的逻辑其实和在支付宝中进行转账一样。直接对db中的数据进行操作。</p><pre data-type="codeBlock" text="function transfer(address to, uint tokens) public returns (bool success) {
    balances[msg.sender] = balances[msg.sender].sub(tokens);
    balances[to] = balances[to].add(tokens);
    emit Transfer(msg.sender, to, tokens);
    return true;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint</span> tokens</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span> success</span>) </span>{
    balances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> balances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].sub(tokens);
    balances[to] <span class="hljs-operator">=</span> balances[to].add(tokens);
    <span class="hljs-keyword">emit</span> Transfer(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, to, tokens);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre><p>这里的msg.sender 是 保留字，实际上就是调用这个合约的账户（钱包 或者 另一个合约）</p><p>可以通过代码看到，在sender 的账户上 减去一定数量的token， 接受转账的账户上再增加一定的数量。就完成了最简单的转账操作。如果想实现，收5% 的转账手续费这种逻辑的话，就直接在前面加上一行转给合约方的逻辑就好。</p><p><strong>所以这里实现的是本人向另一个账户来进行转账</strong></p><p>另外这里emit了 Transfer ，在转账逻辑中没有实际的功能。在这里是一个事件。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://segmentfault.com/a/1190000014882480">详解Solidity事件Event - 完全搞懂事件的使用</a></p><p>如果我们不emit 这个事件的话，实际上我们的逻辑是已经完成了。但是，如果我们想，把完成转账的事件让大家知道，那么这里就需要去emit 这该事件。</p><pre data-type="codeBlock" text="
var infoContract = web3.eth.contract(ABI INFO);
var info = infoContract.at(&apos;CONTRACT ADDRESS&apos;);
// 这里的Transfer 是通过ABI 获取的
var transferEvent = info.Transfer();
// 这里进行事件的监听，如果触发了 Transfer 就执行
var transferEvent.watch(function(error, result){
    // handle result.args.from  result.args.to
});
"><code>
<span class="hljs-keyword">var</span> infoContract <span class="hljs-operator">=</span> web3.eth.contract(ABI INFO);
<span class="hljs-keyword">var</span> info <span class="hljs-operator">=</span> infoContract.at(<span class="hljs-string">'CONTRACT ADDRESS'</span>);
<span class="hljs-comment">// 这里的Transfer 是通过ABI 获取的</span>
<span class="hljs-keyword">var</span> transferEvent <span class="hljs-operator">=</span> info.Transfer();
<span class="hljs-comment">// 这里进行事件的监听，如果触发了 Transfer 就执行</span>
<span class="hljs-keyword">var</span> transferEvent.watch(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-keyword">error</span>, result</span>)</span>{
    <span class="hljs-comment">// handle result.args.from  result.args.to</span>
});
</code></pre><h3 id="h-transferfrom" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>transferFrom</strong></h3><p>有了上面的Transfer 逻辑，这里的TransferFrom 也好理解了，其函数基本定义如下</p><pre data-type="codeBlock" text="function transferFrom(address from, address to, uint tokens) public returns (bool success){
        balances[from] = balances[from].sub(tokens);
        allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens);
        balances[to] = balances[to].add(tokens);
        Transfer(from, to, tokens);
        return true;
 }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFrom</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint</span> tokens</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span> success</span>)</span>{
        balances[<span class="hljs-keyword">from</span>] <span class="hljs-operator">=</span> balances[<span class="hljs-keyword">from</span>].sub(tokens);
        allowed[<span class="hljs-keyword">from</span>][<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> allowed[<span class="hljs-keyword">from</span>][<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].sub(tokens);
        balances[to] <span class="hljs-operator">=</span> balances[to].add(tokens);
        Transfer(<span class="hljs-keyword">from</span>, to, tokens);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
 }
</code></pre><p>这里的逻辑比上面复杂一点，第一行是在from 的账户来减去转账的份额。这点很好理解。</p><p>第二行稍稍复杂慢慢讲，刚刚前面提到来授权额度。</p><p>allowed[from][msg.sender] 这里表示的是 From 账户对 sender（合约调用者）的授权额度，也就是说允许 sender 来对我这一部分钱来进行操作。</p><p>所以这里有两个sub，一个是从From 账户中减去这一部分转账额，另一部分是从授权额度中减去from对该sender 的授权额度。（如果失败那么触发回滚，后面再讲）</p><p><strong>所以简单来说这部分实现的是一个委托转账的功能</strong></p><p>后面的就是换汤不换药的， To 账户增加对应的Token，触发Transfer 事件。</p><h3 id="h-approve" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>approve</strong></h3><p>刚刚已经讲了授权额度的事情，就是 A允许B对自己的一部分Token 来进行操作。那么这里的approve 就是来进行这一部分授权的。</p><pre data-type="codeBlock" text="function approve(address spender, uint tokens) public returns (bool success) {
        allowed[msg.sender][spender] = tokens;
        Approval(msg.sender, spender, tokens);
        return true;
 }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">approve</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> spender, <span class="hljs-keyword">uint</span> tokens</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span> success</span>) </span>{
        allowed[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>][spender] <span class="hljs-operator">=</span> tokens;
        Approval(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, spender, tokens);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
 }
</code></pre><p>可以看到，两个参数一个是 被授权的地址，还有一个是 授权的 Token 份额。前面的委托转账的部分，就是需要在这里进行份额的扣除。</p><p>同样的这里有 Approval 事件，用于通知Dapp，这里的动作完成。</p><h3 id="h-allowance" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">allowance</h3><p>用于检查当前的授权额度，直接进行return来进行返回。</p><pre data-type="codeBlock" text="function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
                reutrn allowed[_owner][_spender]
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">allowance</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _owner, <span class="hljs-keyword">address</span> _spender</span>) <span class="hljs-title"><span class="hljs-keyword">constant</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> remaining</span>) </span>{
                reutrn allowed[_owner][_spender]
}
</code></pre><h3 id="h-totalbalance" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">totalBalance</h3><p>这里返回总的调用量，经典的实现，就是直接返回 <code>_totalSupply</code></p><pre data-type="codeBlock" text="function totalSupply() public constant returns (uint) {
                return _totalSupply
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">totalSupply</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">constant</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
                <span class="hljs-keyword">return</span> _totalSupply
}
</code></pre><p>当然，这里拓展一下，除了协议的本身逻辑我们也可以进行自我拓展。</p><p>比如我这里就可以搞一个增发的逻辑，之前的total 的数量不够。</p><pre data-type="codeBlock" text="function mintToken(address target, uint256 mintedAmount) onlyOwner public {
        balances[target] += mintedAmount;
        _totalSupply += mintedAmount;
        emit Transfer(address(0), address(this), mintedAmount);
        emit Transfer(address(this), target, mintedAmount);
    }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mintToken</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> target, <span class="hljs-keyword">uint256</span> mintedAmount</span>) <span class="hljs-title">onlyOwner</span> <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        balances[target] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> mintedAmount;
        _totalSupply <span class="hljs-operator">+</span><span class="hljs-operator">=</span> mintedAmount;
        <span class="hljs-keyword">emit</span> Transfer(<span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), mintedAmount);
        <span class="hljs-keyword">emit</span> Transfer(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), target, mintedAmount);
    }
</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><p>希望这次自己可以坚定的学下去。知道自己想要什么就很幸福。</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[Web3 ]]></title>
            <link>https://paragraph.com/@r4yyy/web3</link>
            <guid>aoJzDrgGrCUN1kATWXJS</guid>
            <pubDate>Tue, 09 Nov 2021 16:55:34 GMT</pubDate>
            <description><![CDATA[如果不明白什么是区块链 参看下面的这个页面 blockchain Demo 来体验一下 Blockchain Demo]]></description>
            <content:encoded><![CDATA[<p>如果不明白什么是区块链</p><p>参看下面的这个页面 blockchain Demo 来体验一下</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://andersbrownworth.com/blockchain/">Blockchain Demo</a></p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[second]]></title>
            <link>https://paragraph.com/@r4yyy/second</link>
            <guid>3Ivx6ZsWRKvYb4VQQt52</guid>
            <pubDate>Sun, 10 Oct 2021 14:49:37 GMT</pubDate>
            <description><![CDATA[the second post]]></description>
            <content:encoded><![CDATA[<p>the second post</p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
        <item>
            <title><![CDATA[Hello World]]></title>
            <link>https://paragraph.com/@r4yyy/hello-world</link>
            <guid>aj4jYmVcVVvwmCZ9Wqh2</guid>
            <pubDate>Fri, 08 Oct 2021 05:06:18 GMT</pubDate>
            <description><![CDATA[这个是在 Mirror 平台上的第一篇文章，加油加油 初步感觉体验感是不错的**。** 支持MarkDown 语法 耶耶耶]]></description>
            <content:encoded><![CDATA[<p>这个是在 Mirror 平台上的第一篇文章，加油加油</p><p>初步感觉体验感是不错的**。**</p><p><strong>支持MarkDown 语法 耶耶耶</strong></p>]]></content:encoded>
            <author>r4yyy@newsletter.paragraph.com (r4yyy.eth)</author>
        </item>
    </channel>
</rss>