<?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>DAO4Resilience</title>
        <link>https://paragraph.com/@dao4resilience</link>
        <description>DAO for Resilience</description>
        <lastBuildDate>Thu, 14 May 2026 03:52:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>DAO4Resilience</title>
            <url>https://storage.googleapis.com/papyrus_images/f8b01a35facc574a0b10b816cad9b69a74f7a6c375c2d925670b273654688ec4.png</url>
            <link>https://paragraph.com/@dao4resilience</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Solidity极简入门 ERC721专题：1. ERC721相关库]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-erc721-1-erc721</link>
            <guid>hJ2AAc9BpZxWJZhuxMmp</guid>
            <pubDate>Sun, 24 Apr 2022 07:19:37 GMT</pubDate>
            <description><![CDATA[ERC721合约概览ERC721主合约一共引用了7个合约：import "./Address.sol"; import "./Context.sol"; import "./Strings.sol"; import "./IERC721.sol"; import "./IERC721Receiver.sol"; import "./IERC721Metadata.sol"; import "./ERC165.sol"; 他们分别是：3个库合约：Address.sol, Context.sol和Strings.sol3个接口合约：IERC721.sol, IERC721Receiver.sol, IERC721Metadata.sol1个EIP165合约：ERC165.sol所以在讲ERC721的主合约之前，我们会花两讲在引用的库合约和接口合约上。ERC721相关库Address库Address库是Address变量相关函数的合集，包括判断某地址是否为合约，更安全的function call。ERC721用到其中的isContract()： function isContract(...]]></description>
            <content:encoded><![CDATA[<h2 id="h-erc721" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ERC721合约概览</h2><p><code>ERC721</code>主合约一共引用了7个合约：</p><pre data-type="codeBlock" text="import &quot;./Address.sol&quot;;
import &quot;./Context.sol&quot;;
import &quot;./Strings.sol&quot;;
import &quot;./IERC721.sol&quot;;
import &quot;./IERC721Receiver.sol&quot;;
import &quot;./IERC721Metadata.sol&quot;;
import &quot;./ERC165.sol&quot;;
"><code><span class="hljs-keyword">import</span> <span class="hljs-string">"./Address.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./Context.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./Strings.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./IERC721.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./IERC721Receiver.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./IERC721Metadata.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./ERC165.sol"</span>;
</code></pre><p>他们分别是：</p><ul><li><p>3个库合约：<code>Address.sol</code>, <code>Context.sol</code>和<code>Strings.sol</code></p></li><li><p>3个接口合约：<code>IERC721.sol</code>, <code>IERC721Receiver.sol</code>, <code>IERC721Metadata.sol</code></p></li><li><p>1个<code>EIP165</code>合约：<code>ERC165.sol</code></p></li></ul><p>所以在讲<code>ERC721</code>的主合约之前，我们会花两讲在引用的库合约和接口合约上。</p><h2 id="h-erc721" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ERC721相关库</h2><h3 id="h-address" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Address库</h3><p><code>Address</code>库是<code>Address</code>变量相关函数的合集，包括判断某地址是否为合约，更安全的function call。<code>ERC721</code>用到其中的<code>isContract()</code>：</p><pre data-type="codeBlock" text="    function isContract(address account) internal view returns (bool) {
        return account.code.length &gt; 0;
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isContract</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> account</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">return</span> account.<span class="hljs-built_in">code</span>.<span class="hljs-built_in">length</span> <span class="hljs-operator">></span> <span class="hljs-number">0</span>;
    }
</code></pre><p>这个函数利用了非合约地址<code>account.code</code>的长度为0的特性，从而区分某个地址是否为合约地址。</p><p>ERC721主合约在<code>_checkOnERC721Received()</code>函数中调用了<code>isContract()</code>。</p><pre data-type="codeBlock" text="    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert(&quot;ERC721: transfer to non ERC721Receiver implementer&quot;);
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_checkOnERC721Received</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">uint256</span> tokenId,
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> _data
    </span>) <span class="hljs-title"><span class="hljs-keyword">private</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">if</span> (to.isContract()) {
            <span class="hljs-keyword">try</span> IERC721Receiver(to).onERC721Received(_msgSender(), <span class="hljs-keyword">from</span>, tokenId, _data) <span class="hljs-keyword">returns</span> (<span class="hljs-keyword">bytes4</span> retval) {
                <span class="hljs-keyword">return</span> retval <span class="hljs-operator">=</span><span class="hljs-operator">=</span> IERC721Receiver.onERC721Received.<span class="hljs-built_in">selector</span>;
            } <span class="hljs-keyword">catch</span> (<span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> reason) {
                <span class="hljs-keyword">if</span> (reason.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
                    <span class="hljs-keyword">revert</span>(<span class="hljs-string">"ERC721: transfer to non ERC721Receiver implementer"</span>);
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-keyword">assembly</span> {
                        <span class="hljs-keyword">revert</span>(<span class="hljs-built_in">add</span>(<span class="hljs-number">32</span>, reason), <span class="hljs-built_in">mload</span>(reason))
                    }
                }
            }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
    }
</code></pre><p>该函数的目的是在接收<code>ERC721</code>代币的时候判断该地址是否是合约地址；如果是合约地址，则继续检查是否实现了<code>IERC721Receiver</code>接口（<code>ERC721</code>的接收接口），防止有人误把代币转到了黑洞。</p><h3 id="h-context" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Context库</h3><p><code>Context</code>库非常简单，封装了两个Solidity的<code>global</code>变量：<code>msg.sender</code>和<code>msg.data</code></p><pre data-type="codeBlock" text="abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}
"><code><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Context</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_msgSender</span>(<span class="hljs-params"></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">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_msgData</span>(<span class="hljs-params"></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">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">data</span>;
    }
}
</code></pre><p>这两个函数只是单纯的返回<code>msg.sender</code>和<code>msg.data</code>。所以<code>Context</code>库就是为了用函数把<code>msg.sender</code>和<code>msg.data</code>关键词包装起来，应对solidity未来某次升级换掉关键字的情况，没其他作用。</p><h3 id="h-strings" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Strings库</h3><p><code>String</code>库包含两个库函数：<code>toString()</code>和<code>toHexString()</code>。<code>toString()</code>把<code>uint256</code>直接转换成<code>string</code>，比如777变为”777”；而<code>toHexString()</code>把<code>uint256</code>先转换为<code>16进制</code>，再转换为<code>string</code>，比如170变为”0xaa”。<code>ERC721</code>调用了<code>toString()</code>函数：</p><pre data-type="codeBlock" text="    function toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return &quot;0&quot;;
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> value</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">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-keyword">if</span> (value <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"0"</span>;
        }
        <span class="hljs-keyword">uint256</span> temp <span class="hljs-operator">=</span> value;
        <span class="hljs-keyword">uint256</span> digits;
        <span class="hljs-keyword">while</span> (temp <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            digits<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            temp <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> buffer <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(digits);
        <span class="hljs-keyword">while</span> (value <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            digits <span class="hljs-operator">-</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
            buffer[digits] <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-keyword">uint8</span>(<span class="hljs-number">48</span> <span class="hljs-operator">+</span> <span class="hljs-keyword">uint256</span>(value <span class="hljs-operator">%</span> <span class="hljs-number">10</span>)));
            value <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">string</span>(buffer);
    }
</code></pre><p>这个函数先确定了传入的<code>uint256</code>参数是几位数，并存在digits变量中。然后用循环把每一位数字的<code>ASCII码</code>转换成<code>bytes1</code>，存在<code>buffer</code>中，最后把<code>buffer</code>转换成<code>string</code>返回。</p><p><code>ERC721</code>主合约在<code>tokenURI()</code>函数中调用了<code>toString()</code>：</p><pre data-type="codeBlock" text="    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), &quot;ERC721Metadata: URI query for nonexistent token&quot;);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length &gt; 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : &quot;&quot;;
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tokenURI</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</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">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-built_in">require</span>(_exists(tokenId), <span class="hljs-string">"ERC721Metadata: URI query for nonexistent token"</span>);

        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> baseURI <span class="hljs-operator">=</span> _baseURI();
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">bytes</span>(baseURI).<span class="hljs-built_in">length</span> <span class="hljs-operator">></span> <span class="hljs-number">0</span> ? <span class="hljs-keyword">string</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(baseURI, tokenId.toString())) : <span class="hljs-string">""</span>;
    }
</code></pre><p>这个函数把<code>baseURI</code>和指定的<code>tokenId</code>拼接到一起，返回<code>ERC721 metadata</code>的网址，你花几十个ETH买的的jpeg就是存在这个网址上的。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>这一讲是<code>ERC721</code>专题的第一讲，我们概览了<code>ERC721</code>的合约，并介绍了<code>ERC721</code>主合约调用的3个库合约<code>Address</code>，<code>Context</code>和<code>String</code>。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 13.异常]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-13</link>
            <guid>SUOScwsZynlLcnFkhQPs</guid>
            <pubDate>Sun, 24 Apr 2022 07:17:22 GMT</pubDate>
            <description><![CDATA[这一讲，我们介绍solidity三种抛出异常的方法：error，require和assert，并比较三种方法的gas消耗。异常写智能合约经常会出bug，solidity中的异常命令帮助我们debug。ErrorError是solidity 0.8版本新加的内容，方便且高效（省gas）的向用户解释操作失败的原因。人们可以在contract之外定义异常。下面，我们定义一个TransferNotOwner异常，当用户不是代币owner的时候尝试转账，会抛出错误：error TransferNotOwner(); // 自定义error 在执行当中，error必须搭配revert（回退）命令使用。 function transferOwner1(uint256 tokenId, address newOwner) public { if(_owners[tokenId] != msg.sender){ revert TransferNotOwner(); } _owners[tokenId] = newOwner; } 我们定义了一个transferOwner1()函数，他会检查代币的o...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们介绍<code>solidity</code>三种抛出异常的方法：<code>error</code>，<code>require</code>和<code>assert</code>，并比较三种方法的gas消耗。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">异常</h2><p>写智能合约经常会出bug，<code>solidity</code>中的异常命令帮助我们debug。</p><h3 id="h-error" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Error</h3><p><code>Error</code>是<code>solidity</code> 0.8版本新加的内容，方便且高效（省gas）的向用户解释操作失败的原因。人们可以在<code>contract</code>之外定义异常。下面，我们定义一个<code>TransferNotOwner</code>异常，当用户不是代币<code>owner</code>的时候尝试转账，会抛出错误：</p><pre data-type="codeBlock" text="error TransferNotOwner(); // 自定义error
"><code><span class="hljs-function">error <span class="hljs-title">TransferNotOwner</span>()</span>; <span class="hljs-comment">// 自定义error</span>
</code></pre><p>在执行当中，<code>error</code>必须搭配<code>revert</code>（回退）命令使用。</p><pre data-type="codeBlock" text="    function transferOwner1(uint256 tokenId, address newOwner) public {
        if(_owners[tokenId] != msg.sender){
            revert TransferNotOwner();
        }
        _owners[tokenId] = newOwner;
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferOwner1</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">address</span> newOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-keyword">if</span>(_owners[tokenId] <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>){
            <span class="hljs-keyword">revert</span> TransferNotOwner();
        }
        _owners[tokenId] <span class="hljs-operator">=</span> newOwner;
    }
</code></pre><p>我们定义了一个<code>transferOwner1()</code>函数，他会检查代币的<code>owner</code>是不是发起人，如果不是，就会抛出<code>TransferNotOwner</code>异常；如果是的话，就会转账。</p><h3 id="h-require" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Require</h3><p><code>require</code>命令是<code>solidity</code> 0.8版本之前抛出异常的常用方法，目前很多主流合约仍然还在使用它。他很好用，唯一的缺点就是<code>gas</code>随着描述异常的字符串长度增加，比<code>error</code>命令要高。使用方法：<code>require(检查条件，”异常的描述”)</code>，当<code>检查条件</code>不成立的时候，就会抛出异常。</p><p>我们用<code>require</code>命令重写一下上面的<code>transferOwner</code>函数：</p><pre data-type="codeBlock" text="    function transferOwner2(uint256 tokenId, address newOwner) public {
        require(_owners[tokenId] == msg.sender, &quot;Transfer Not Owner&quot;);
        _owners[tokenId] = newOwner;
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferOwner2</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">address</span> newOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">require</span>(_owners[tokenId] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-string">"Transfer Not Owner"</span>);
        _owners[tokenId] <span class="hljs-operator">=</span> newOwner;
    }
</code></pre><h3 id="h-assert" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Assert</h3><p><code>assert</code>命令一般用于程序员写程序debug，因为他不能解释抛出异常的原因（比<code>require</code>少个字符串）。他的用法很简单，<code>require(检查条件）</code>，当<code>检查条件</code>不成立的时候，就会抛出异常。</p><p>我们用<code>assert</code>命令重写一下上面的<code>transferOwner</code>函数：</p><pre data-type="codeBlock" text="    function transferOwner3(uint256 tokenId, address newOwner) public {
        assert(_owners[tokenId] == msg.sender);
        _owners[tokenId] = newOwner;
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferOwner3</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">address</span> newOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">assert</span>(_owners[tokenId] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
        _owners[tokenId] <span class="hljs-operator">=</span> newOwner;
    }
</code></pre><h3 id="h-gas" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">三种方法的gas比较</h3><p>我们比较一下三种抛出异常的gas消耗，方法很简单，部署合约，分别运行写的<code>transferOwner</code>函数的三个版本。</p><ol><li><p><code>error</code>方法gas消耗：24445</p></li><li><p><code>require</code>方法gas消耗：24743</p></li><li><p><code>assert</code>方法gas消耗：24446</p></li></ol><p>我们可以看到，<code>error</code>方法gas cost最少，其次是<code>assert</code>，<code>require</code>方法消耗gas最多！因此，<code>error</code>既可以告知用户抛出异常的原因，又能省gas，大家要多用！</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">总结</h3><p>这一讲，我们介绍<code>solidity</code>三种抛出异常的方法：<code>error</code>，<code>require</code>和<code>assert</code>，并比较了三种方法的gas消耗。结论：<code>error</code>既可以告知用户抛出异常的原因，又能省gas。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 12. 库合约 站在巨人的肩膀上]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-12</link>
            <guid>nNOFP57CIXdgsUvRfKmg</guid>
            <pubDate>Sun, 24 Apr 2022 07:16:13 GMT</pubDate>
            <description><![CDATA[这一讲，我们用ERC721的引用的库合约String为例介绍solidity中的库合约（library），并总结了常用的库函数。库函数库函数是一种特殊的合约，主要是为了提升solidity代码的复用性和减少gas fee而存在。库合约一般都是一些好用的函数合集（库函数），由大神或者项目方创作，咱们站在巨人的肩膀上，会用就行了。库合约：站在巨人的肩膀上库合约：站在巨人的肩膀上 他和普通合约主要有以下几点不同：不能存在状态变量不能够继承或被继承不能接收以太币不可以被销毁String库合约String库合约是将uint256类型转换为相应的string类型的代码库，样例代码如下：library Strings { bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) public pure ret...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们用<code>ERC721</code>的引用的库合约<code>String</code>为例介绍solidity中的库合约（<code>library</code>），并总结了常用的库函数。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">库函数</h2><p>库函数是一种特殊的合约，主要是为了提升solidity代码的复用性和减少gas fee而存在。库合约一般都是一些好用的函数合集（库函数），由大神或者项目方创作，咱们站在巨人的肩膀上，会用就行了。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/dce2c3b857a2ac6998a36cca241c70d4416c8ecdf960b1039a0f4460182dfba6.jpg" alt="库合约：站在巨人的肩膀上" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">库合约：站在巨人的肩膀上</figcaption></figure><p>库合约：站在巨人的肩膀上</p><p>他和普通合约主要有以下几点不同：</p><ul><li><p>不能存在状态变量</p></li><li><p>不能够继承或被继承</p></li><li><p>不能接收以太币</p></li><li><p>不可以被销毁</p></li></ul><h3 id="h-string" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">String库合约</h3><p><code>String</code>库合约是将uint256类型转换为相应的string类型的代码库，样例代码如下：</p><pre data-type="codeBlock" text="library Strings {
    bytes16 private constant _HEX_SYMBOLS = &quot;0123456789abcdef&quot;;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) public pure returns (string memory) {
        // Inspired by OraclizeAPI&apos;s implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return &quot;0&quot;;
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) public pure returns (string memory) {
        if (value == 0) {
            return &quot;0x00&quot;;
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp &gt;&gt;= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) public pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = &quot;0&quot;;
        buffer[1] = &quot;x&quot;;
        for (uint256 i = 2 * length + 1; i &gt; 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value &amp; 0xf];
            value &gt;&gt;= 4;
        }
        require(value == 0, &quot;Strings: hex length insufficient&quot;);
        return string(buffer);
    }
}
"><code><span class="hljs-class"><span class="hljs-keyword">library</span> <span class="hljs-title">Strings</span> </span>{
    <span class="hljs-keyword">bytes16</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">constant</span> _HEX_SYMBOLS <span class="hljs-operator">=</span> <span class="hljs-string">"0123456789abcdef"</span>;

    <span class="hljs-comment">/**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-comment">// Inspired by OraclizeAPI's implementation - MIT licence</span>
        <span class="hljs-comment">// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol</span>

        <span class="hljs-keyword">if</span> (value <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"0"</span>;
        }
        <span class="hljs-keyword">uint256</span> temp <span class="hljs-operator">=</span> value;
        <span class="hljs-keyword">uint256</span> digits;
        <span class="hljs-keyword">while</span> (temp <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            digits<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            temp <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> buffer <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(digits);
        <span class="hljs-keyword">while</span> (value <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            digits <span class="hljs-operator">-</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
            buffer[digits] <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-keyword">uint8</span>(<span class="hljs-number">48</span> <span class="hljs-operator">+</span> <span class="hljs-keyword">uint256</span>(value <span class="hljs-operator">%</span> <span class="hljs-number">10</span>)));
            value <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">string</span>(buffer);
    }

    <span class="hljs-comment">/**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toHexString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-keyword">if</span> (value <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"0x00"</span>;
        }
        <span class="hljs-keyword">uint256</span> temp <span class="hljs-operator">=</span> value;
        <span class="hljs-keyword">uint256</span> length <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
        <span class="hljs-keyword">while</span> (temp <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            length<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            temp <span class="hljs-operator">></span><span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">8</span>;
        }
        <span class="hljs-keyword">return</span> toHexString(value, length);
    }

    <span class="hljs-comment">/**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toHexString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> value, <span class="hljs-keyword">uint256</span> length</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> buffer <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(<span class="hljs-number">2</span> <span class="hljs-operator">*</span> length <span class="hljs-operator">+</span> <span class="hljs-number">2</span>);
        buffer[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-string">"0"</span>;
        buffer[<span class="hljs-number">1</span>] <span class="hljs-operator">=</span> <span class="hljs-string">"x"</span>;
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint256</span> i <span class="hljs-operator">=</span> <span class="hljs-number">2</span> <span class="hljs-operator">*</span> length <span class="hljs-operator">+</span> <span class="hljs-number">1</span>; i <span class="hljs-operator">></span> <span class="hljs-number">1</span>; <span class="hljs-operator">-</span><span class="hljs-operator">-</span>i) {
            buffer[i] <span class="hljs-operator">=</span> _HEX_SYMBOLS[value <span class="hljs-operator">&#x26;</span> <span class="hljs-number">0xf</span>];
            value <span class="hljs-operator">></span><span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">4</span>;
        }
        <span class="hljs-built_in">require</span>(value <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Strings: hex length insufficient"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">string</span>(buffer);
    }
}
</code></pre><p>他主要包含两个函数，<code>toString()</code>将<code>uint256</code>转为<code>string</code>，<code>toHexString()</code>将<code>uint256</code>转换为<code>16进制</code>，在转换为<code>string</code>。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">如何使用库合约</h3><p>我们用<code>String</code>库函数的<code>toHexString()</code>来演示两种使用库合约中函数的办法。</p><p><strong>1. 利用using for指令</strong>：</p><p>指令 <code>using A for B;</code> 可用于附加库函数（从库 <code>A</code>）到任何类型（<code>B</code>）。添加完指令后，库<code>A</code>中的函数会自动添加为<code>B</code>类型变量的成员，可以直接调用。<code>注意</code>：在调用的时候，这个变量会被当作第一个参数传递给函数：</p><pre data-type="codeBlock" text="    // 利用using for指令
    using Strings for uint256;
    function getString1(uint256 _number) public pure returns(string memory){
        // 库函数会自动添加为uint256型变量的成员
        return _number.toHexString();
    }
"><code>    <span class="hljs-comment">// 利用using for指令</span>
    <span class="hljs-keyword">using</span> <span class="hljs-title">Strings</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title"><span class="hljs-keyword">uint256</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getString1</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _number</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-comment">// 库函数会自动添加为uint256型变量的成员</span>
        <span class="hljs-keyword">return</span> _number.toHexString();
    }
</code></pre><p><strong>2. 通过库合约名称调用库函数：</strong></p><pre data-type="codeBlock" text="    // 直接通过库合约名调用
    function getString2(uint256 _number) public pure returns(string memory){
        return Strings.toHexString(_number);
    }
"><code>    <span class="hljs-comment">// 直接通过库合约名调用</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getString2</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _number</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span> Strings.toHexString(_number);
    }
</code></pre><p>我们部署合约并输入<code>170</code>测试一下，两种方法均能返回正确的16进制string <code>“0xaa”</code>。证明我们调用库函数成功！</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5af8811d5da956bd98f2f0e14af729b6719ac588de2f01c42c16e7f52abb6148.png" alt="成功调用库函数" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">成功调用库函数</figcaption></figure><p>成功调用库函数</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">总结</h3><p>这一讲，我们用<code>ERC721</code>的引用的库函数<code>String</code>为例介绍solidity中的库函数（<code>Library</code>）。99%的开发者都不需要自己去写库合约，会用大神写的就可以了。我们只需要知道什么情况该用什么库合约。常用的有：</p><ol><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/String.sol">String</a>：将uint256转换为String</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Address.sol">Address</a>：判断某个地址是否为合约地址</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Create2.sol">Create2</a>：更安全的使用Create2 EVM opcode</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Arrays.sol">Arrays</a>：跟数组相关的库函数</p></li></ol>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 11. 抽象合约和接口]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-11</link>
            <guid>nSU2h3LeFx9VpqeGFElQ</guid>
            <pubDate>Sun, 24 Apr 2022 07:15:02 GMT</pubDate>
            <description><![CDATA[这一讲，我们用ERC721的接口合约为例介绍solidity中的抽象合约（abstact）和接口（interface），帮助大家更好的理解ERC721标准。抽象合约如果一个智能合约里至少有一个未实现的函数，即某个函数缺少{}中的内容，则必须将该合约标为abstract，不然编译会报错；另外，未实现的函数需要加virtual，以便子合约重写。拿我们之前的插入排序合约为例，如果我们还没想好具体怎么实现插入排序函数，那么可以把合约标为abstract，之后让别人补写上。abstract contract InsertionSort{ function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); }ERC 接口接口也是一种合约，跟抽象合约类似，都是为了设立标准和减少代码冗余，但是接口的要求更加严格：不能包含状态变量不能包含构造函数不能继承除接口外的其他合约所有函数都必须是external（接口与合约的ABI等价）我们看一个ERC721接口的例子，所有NFT都使用了这个接口：inter...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们用<code>ERC721</code>的接口合约为例介绍solidity中的抽象合约（<code>abstact</code>）和接口（<code>interface</code>），帮助大家更好的理解<code>ERC721</code>标准。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">抽象合约</h2><p>如果一个智能合约里至少有一个未实现的函数，即某个函数缺少{}中的内容，则必须将该合约标为<code>abstract</code>，不然编译会报错；另外，未实现的函数需要加<code>virtual</code>，以便子合约重写。拿我们之前的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/AmazingAng/MinimalSolidity-CN/blob/main/07_InsertionSort.sol">插入排序合约</a>为例，如果我们还没想好具体怎么实现插入排序函数，那么可以把合约标为abstract，之后让别人补写上。</p><pre data-type="codeBlock" text="abstract contract InsertionSort{
    function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory);
}ERC
"><code><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">InsertionSort</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">insertionSort</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span> a</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span></span>)</span>;
}ERC
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">接口</h2><p>接口也是一种合约，跟抽象合约类似，都是为了设立标准和减少代码冗余，但是接口的要求更加严格：</p><ol><li><p>不能包含状态变量</p></li><li><p>不能包含构造函数</p></li><li><p>不能继承除接口外的其他合约</p></li><li><p>所有函数都必须是<code>external</code>（接口与合约的ABI等价）</p></li></ol><p>我们看一个<code>ERC721</code>接口的例子，所有NFT都使用了这个接口：</p><pre data-type="codeBlock" text="interface IERC721 is IERC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    
    function balanceOf(address owner) external view returns (uint256 balance);

    function ownerOf(uint256 tokenId) external view returns (address owner);

    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    function transferFrom(address from, address to, uint256 tokenId) external;

    function approve(address to, uint256 tokenId) external;

    function getApproved(uint256 tokenId) external view returns (address operator);

    function setApprovalForAll(address operator, bool _approved) external;

    function isApprovedForAll(address owner, address operator) external view returns (bool);

    function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data) external;
}
"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IERC721</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IERC165</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">uint256</span> <span class="hljs-keyword">indexed</span> tokenId</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> owner, <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> approved, <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">indexed</span> tokenId</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">ApprovalForAll</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> owner, <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> operator, <span class="hljs-keyword">bool</span> approved</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> owner</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> balance</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ownerOf</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</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">address</span> owner</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeTransferFrom</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">uint256</span> tokenId</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">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">uint256</span> tokenId</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">approve</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> tokenId</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">getApproved</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</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">address</span> operator</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setApprovalForAll</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> operator, <span class="hljs-keyword">bool</span> _approved</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">isApprovedForAll</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">address</span> operator</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">bool</span></span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeTransferFrom</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">uint256</span> tokenId, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;
}
</code></pre><p>这个接口里面共定义了3个<code>events</code>和9个<code>function</code>，为所有<code>ERC721</code>函数设立了标准：所有<code>ERC721</code>合约都要实现这些<code>function</code>。</p><h3 id="h-erc721events" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">ERC721接口的Events</h3><p>接口中事件的定义和正常合约里一样。<code>ERC721</code>接口里面的<code>events</code>包括：</p><ol><li><p><code>Transfer</code>（转账事件）：记录<code>发起地址</code>，<code>接收地址</code>，和<code>tokenId</code></p></li><li><p><code>Approve</code>（批准事件）：记录<code>持有地址</code>，<code>批准地址</code>，和<code>tokenId</code></p></li><li><p><code>ApproveForAll</code>（批量批准事件）：记录<code>持有地址</code>，<code>批准地址</code>，和一个代表批准与否的<code>bool</code>值。可以看到，接口中的事件和正常合约里的事件一样。</p></li></ol><h3 id="h-erc721function" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">ERC721接口的Function</h3><p>接口中所有<code>function</code>都必须带有<code>external</code>标签（不需要写<code>virtual</code>标签，因为默认为<code>virtual</code>）。<code>ERC721</code>接口里面的<code>function</code>包括：</p><ol><li><p><code>balanceOf</code>：查询某个地址的<code>NFT</code>持有量</p></li><li><p><code>ownerOf</code>：查询某个<code>tokenId</code>的持有人</p></li><li><p><code>safeTransferFrom</code>：安全转账（如果接收方是合约地址，会要求实现<code>ERC721</code>的接收接口）。</p></li><li><p><code>transferFrom</code>：普通转账</p></li><li><p><code>approve</code>：批准另一个地址使用你的<code>NFT</code></p></li><li><p><code>getApproved</code>：查询<code>NFT</code>被批准给了哪个地址</p></li><li><p><code>setApprovalForAll</code>：将全部<code>NFT</code>批量批准给某个地址</p></li><li><p><code>isApprovedForAll</code>：查询全部<code>NFT</code>是否批准给了某个地址</p></li><li><p><code>safeTransferFrom</code>：安全转账，与<code>7.</code>不同的地方在于参数里面包含了<code>data</code></p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">什么时候使用抽象合约和接口？</h2><ol><li><p>写大工程的时候打草稿用</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>这一讲，我介绍了solidity中的抽象合约（<code>abstact</code>）和接口（<code>interface</code>），他们都可以写模版并且减少代码冗余。我们还讲了ERC721接口合约，以后会更详细的讲一下ERC721代币标准。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 10. 继承 父与子]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-10</link>
            <guid>g1gaYsuCyzf6iFq6KRqB</guid>
            <pubDate>Sun, 24 Apr 2022 07:14:23 GMT</pubDate>
            <description><![CDATA[这一讲，我们介绍solidity中的继承（inheritance），包括简单继承，多重继承，以及修饰器（modifier）和构造函数（constructor）的继承。继承继承是面向对象编程很重要的组成部分，可以显著减少重复代码。如果把合约看作是对象的话，solidity也是面向对象的编程，也支持继承。规则virtual: 父合约中的函数，如果希望子合约重写，需要加上virtual关键字。 override：子合约重写了父合约中的函数，需要加上override关键。简单继承我们先写一个简单的爷爷合约Yeye，里面包含3个function: hip(), pop(), yeye()，返回值都是”Yeye”。contract Yeye { // 定义3个function: hip(), pop(), man()，返回值设为A。 function hip() public pure virtual returns (string memory){ return("Yeye"); } function pop() public pure virtual returns (string me...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们介绍solidity中的继承（<code>inheritance</code>），包括简单继承，多重继承，以及修饰器（<code>modifier</code>）和构造函数（<code>constructor</code>）的继承。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">继承</h2><p>继承是面向对象编程很重要的组成部分，可以显著减少重复代码。如果把合约看作是对象的话，solidity也是面向对象的编程，也支持继承。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">规则</h3><p><code>virtual</code>: 父合约中的函数，如果希望子合约重写，需要加上<code>virtual</code>关键字。</p><p><code>override</code>：子合约重写了父合约中的函数，需要加上<code>override</code>关键。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">简单继承</h3><p>我们先写一个简单的爷爷合约<code>Yeye</code>，里面包含3个<code>function</code>: <code>hip()</code>, <code>pop()</code>, <code>yeye()</code>，返回值都是<code>”Yeye”</code>。</p><pre data-type="codeBlock" text="contract Yeye {
    // 定义3个function: hip(), pop(), man()，返回值设为A。
    function hip() public pure virtual returns (string memory){
        return(&quot;Yeye&quot;);
    }

    function pop() public pure virtual returns (string memory){
        return(&quot;Yeye&quot;);
    }

    function yeye() public pure virtual returns (string memory){
        return(&quot;Yeye&quot;);
    }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Yeye</span> </span>{
    <span class="hljs-comment">// 定义3个function: hip(), pop(), man()，返回值设为A。</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hip</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">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Yeye"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pop</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">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Yeye"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">yeye</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">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Yeye"</span>);
    }
}
</code></pre><p>我们再定义一个爸爸合约<code>Baba</code>，让他继承Yeye<code>合约</code>，语法就是<code>contract Baba is Yeye</code>，非常直观。在Baba合约里，我们重写一下<code>hip()</code>和<code>pop()</code>这两个函数，加上<code>override</code>关键字，并将他们的返回值改为<code>”Baba”</code>；并且加一个新的函数<code>baba</code>，返回值也是<code>”Baba”</code>。</p><pre data-type="codeBlock" text="contract Baba is Yeye{
    // 继承两个function: hip()和pop()，返回值改为B。
    function hip() public pure virtual override returns (string memory){
        return(&quot;Baba&quot;);
    }

    function pop() public pure virtual override returns (string memory){
        return(&quot;Baba&quot;);
    }

    function baba() public pure virtual returns (string memory){
        return(&quot;Baba&quot;);
    }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Baba</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Yeye</span></span>{
    <span class="hljs-comment">// 继承两个function: hip()和pop()，返回值改为B。</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hip</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">pure</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 class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Baba"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pop</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">pure</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 class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Baba"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">baba</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">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Baba"</span>);
    }
}
</code></pre><p>我们部署合约，可以看到Baba合约里有4个函数，其中<code>hip()</code>和<code>pop()</code>的返回值被成功改写成<code>”Baba”</code>，而继承来的<code>yeye()</code>的返回值仍然是<code>”Yeye”</code>。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c8ec7e143b3c3194c4c2b17fea4b8aaa95430ec148881abb1b922489b2ec7392.png" alt="Baba合约成功重写了hip()和pop()" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Baba合约成功重写了hip()和pop()</figcaption></figure><p>Baba合约成功重写了hip()和pop()</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">多重继承</h3><p>solidity的合约可以继承多个合约。规则：</p><ol><li><p>继承时要按辈分最高到最低的顺序排。比如我们写一个<code>Erzi</code>合约，继承Yeye合约和Baba合约，那么就要写成<code>contract Erzi is Yeye, Baba</code>，而不能写成<code>contract Erzi is Baba, Yeye</code>，不然就会报错。</p></li><li><p>如果某一个函数在多个继承的合约里都存在，比如例子中的<code>hip()</code>和<code>pop()</code>，在子合约里必须重写，不然会报错。</p></li><li><p>重写在多个父合约中重名函数时，override关键字后面要加上父合约名字，例如<code>override(Yeye, Baba)</code>。</p></li></ol><p>例子：</p><pre data-type="codeBlock" text="contract Erzi is Yeye, Baba{
    // 继承两个function: hip()和pop()，返回值改为B。
    function hip() public pure virtual override(Yeye, Baba) returns (string memory){
        return(&quot;Erzi&quot;);
    }

    function pop() public pure virtual override(Yeye, Baba) returns (string memory){
        return(&quot;Erzi&quot;);
    }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Erzi</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Yeye</span>, <span class="hljs-title">Baba</span></span>{
    <span class="hljs-comment">// 继承两个function: hip()和pop()，返回值改为B。</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hip</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">pure</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 class="hljs-params">Yeye, Baba</span>) <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Erzi"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pop</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">pure</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 class="hljs-params">Yeye, Baba</span>) <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span>(<span class="hljs-string">"Erzi"</span>);
    }
}
</code></pre><p>我们可以看到，Erzi合约里面重写了<code>hip()</code>和<code>pop()</code>两个函数，将返回值改为<code>”Erzi”</code>，并且还分别从<code>Yeye</code>和<code>Baba</code>合约继承了<code>yeye()</code>和<code>baba()</code>两个函数。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e5cc0112b3a3809145fd69152786f62e7f9ea1315cb96ab237e9122fd45e4e96.png" alt="Erzi合约继承了baba()和yeye()" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Erzi合约继承了baba()和yeye()</figcaption></figure><p>Erzi合约继承了baba()和yeye()</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">修饰器的继承</h3><p>Solidity中的修饰器（Modifier）同样可以继承，用法与函数继承类似，在相应的地方加<code>virtual</code>和<code>override</code>关键字即可。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">构造函数的继承</h3><p>子合约有两种方法即成父合约的构造函数。举个简单的例子，父合约<code>A</code>里面有一个状态变量a，并由构造函数的参数来确定：</p><pre data-type="codeBlock" text="// 构造函数的继承
abstract contract A {
    uint public a;

    constructor(uint _a) {
        a = _a;
    }
}
"><code><span class="hljs-comment">// 构造函数的继承</span>
<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">A</span> </span>{
    <span class="hljs-keyword">uint</span> <span class="hljs-keyword">public</span> a;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _a</span>) </span>{
        a <span class="hljs-operator">=</span> _a;
    }
}
</code></pre><ol><li><p>在继承时声明父构造函数的参数，例如：<code>contract B is A(1)</code></p></li><li><p>在子合约的构造函数中声明构造函数的参数，例如：</p></li></ol><pre data-type="codeBlock" text="contract C is A {
    constructor(uint _c) A(_c * _c) {}
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">C</span> <span class="hljs-keyword">is</span> <span class="hljs-title">A</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _c</span>) <span class="hljs-title">A</span>(<span class="hljs-params">_c * _c</span>) </span>{}
}
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">总结</h3><p>这一讲，我们介绍了solidity继承的基本用法，包括简单继承，多重继承，以及修饰器和构造函数的继承。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 9. 事件]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-9</link>
            <guid>3kcdNE5GdVvErQyrPc67</guid>
            <pubDate>Sun, 24 Apr 2022 07:11:47 GMT</pubDate>
            <description><![CDATA[这一讲，我们用转账ERC20代币为例来介绍solidity中的事件（event）。事件Solidity中的事件（event）是EVM上日志的抽象，它具有两个特点：响应：应用程序（ether.js）可以通过RPC接口订阅和监听这些事件，并在前端做响应。经济：事件是EVM上比较经济的存储数据的方式，每个大概消耗2000-5000 gas不等。相比之下，存储一个新的变量至少需要20,000 gas。规则事件的声明由event关键字开头，然后跟事件名称，括号里面写好时间需要记录的变量类型和变量名。以ERC20代币合约的Transfer事件为例：event Transfer(address indexed from, address indexed to, uint256 value); 我们可以看到，Transfer事件共记录了3个变量 from，to和value，分别对应代币的转账地址，接收地址和转账数量。同时from和to前面带着indexed关键字，表示很重要，程序可以轻松的筛选出特定转账地址和接收地址的转账事件。每个事件最多有3个带indexed的变量。 我们可以在函数里释放事件...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们用转账<code>ERC20代币</code>为例来介绍solidity中的事件（<code>event</code>）。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">事件</h2><p>Solidity中的事件（<code>event</code>）是EVM上日志的抽象，它具有两个特点：</p><ol><li><p><strong>响应</strong>：应用程序（<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/docs/ethers.js/api-contract.html#id18">ether.js</a>）可以通过RPC接口订阅和监听这些事件，并在前端做响应。</p></li><li><p><strong>经济</strong>：事件是<code>EVM</code>上比较经济的存储数据的方式，每个大概消耗2000-5000 gas不等。相比之下，存储一个新的变量至少需要20,000 gas。</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">规则</h3><p>事件的声明由<code>event</code>关键字开头，然后跟事件名称，括号里面写好时间需要记录的变量类型和变量名。以<code>ERC20</code>代币合约的<code>Transfer</code>事件为例：</p><pre data-type="codeBlock" text="event Transfer(address indexed from, address indexed to, uint256 value);
"><code><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">uint256</span> value</span>)</span>;
</code></pre><p>我们可以看到，<code>Transfer</code>事件共记录了3个变量 <code>from</code>，<code>to</code>和<code>value</code>，分别对应代币的转账地址，接收地址和转账数量。同时<code>from</code>和<code>to</code>前面带着<code>indexed</code>关键字，表示很重要，程序可以轻松的筛选出特定转账地址和接收地址的转账事件。每个事件最多有3个带<code>indexed</code>的变量。</p><p>我们可以在函数里释放事件。在下面的例子中，每次用<code>_transfer()</code>函数进行转账操作的时候，都会释放<code>Transfer</code>事件，并记录相应的变量。</p><pre data-type="codeBlock" text="    // 定义_transfer函数，执行转账逻辑
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) external {

        _balances[from] = 10000000; // 给转账地址一些初始代币

        _balances[from] -=  amount; // from地址减去转账数量
        _balances[to] += amount; // to地址加上转账数量

        // 释放事件
        emit Transfer(from, to, amount);
    }
"><code>    <span class="hljs-comment">// 定义_transfer函数，执行转账逻辑</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> <span class="hljs-keyword">from</span>,
        <span class="hljs-keyword">address</span> to,
        <span class="hljs-keyword">uint256</span> amount
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{

        _balances[<span class="hljs-keyword">from</span>] <span class="hljs-operator">=</span> <span class="hljs-number">10000000</span>; <span class="hljs-comment">// 给转账地址一些初始代币</span>

        _balances[<span class="hljs-keyword">from</span>] <span class="hljs-operator">-</span><span class="hljs-operator">=</span>  amount; <span class="hljs-comment">// from地址减去转账数量</span>
        _balances[to] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount; <span class="hljs-comment">// to地址加上转账数量</span>

        <span class="hljs-comment">// 释放事件</span>
        <span class="hljs-keyword">emit</span> Transfer(<span class="hljs-keyword">from</span>, to, amount);
    }
</code></pre><h3 id="h-etherscan" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">在etherscan上查询事件</h3><p>我们尝试用<code>_transfer()</code>函数在Rinkeby测试网络上转账100代币，可以在etherscan上查询到相应的tx：</p><p>点击Logs按钮，就能看到事件明细：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/aabe8cfc0182b0dcec4ff91c8defd3458070706ff41332998478f8c7f84ea481.png" alt="Event明细" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Event明细</figcaption></figure><p>Event明细</p><p>Topics里面有三个元素，[0]是这个事件的哈希，[1]和[2]是我们定义的两个<code>index</code>变量的信息，即转账的转出地址和接收地址。Data里面是剩下的不带<code>index</code>的变量，也就是转账数量。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>这一讲，我们介绍了如何使用和查询solidity中的事件。很多链上分析工具包括<code>Nansen</code>和<code>Dune Analysis</code>都是基于事件工作的。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 8. 构造函数和修饰器。。]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-8</link>
            <guid>asWPaWlaGCVXyoifr6Jy</guid>
            <pubDate>Sun, 24 Apr 2022 07:09:31 GMT</pubDate>
            <description><![CDATA[这一讲，我们将用合约权限控制（Ownable）的例子介绍solidity语言中构造函数（constructor）和独有的修饰器（modifier）。构造函数构造函数（constructor）是一种特殊的函数，每个合约可以定义一个，并在部署合约的时候自动运行一次。它可以用来初始化合约的一些参数，例如初始化合约的owner地址： address owner; // 定义owner变量 // 构造函数 constructor() public { owner = msg.sender; // 在部署合约的时候，将owner设置为部署者的地址 } 修饰器修饰器（modifier）是solidity特有的语法，类似于面向对象编程中的decorator，声明函数拥有的特性，并减少代码冗余。它就像钢铁侠的智能盔甲，穿上它的函数会带有某些特定的行为。modifier的主要使用场景是运行函数前的检查，例如地址，变量，余额等。钢铁侠的modifier钢铁侠的modifier 我们定义一个叫做onlyOwner的modifier： // 定义modifier modifier onlyOwner { ...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们将用合约权限控制（<code>Ownable</code>）的例子介绍solidity语言中构造函数（<code>constructor</code>）和独有的修饰器（<code>modifier</code>）。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">构造函数</h2><p>构造函数（<code>constructor</code>）是一种特殊的函数，每个合约可以定义一个，并在部署合约的时候自动运行一次。它可以用来初始化合约的一些参数，例如初始化合约的<code>owner</code>地址：</p><pre data-type="codeBlock" text="   address owner; // 定义owner变量

   // 构造函数
   constructor() public {
      owner = msg.sender; // 在部署合约的时候，将owner设置为部署者的地址
   }
"><code>   <span class="hljs-keyword">address</span> owner; <span class="hljs-comment">// 定义owner变量</span>

   <span class="hljs-comment">// 构造函数</span>
   <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
      owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>; <span class="hljs-comment">// 在部署合约的时候，将owner设置为部署者的地址</span>
   }
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">修饰器</h2><p>修饰器（<code>modifier</code>）是solidity特有的语法，类似于面向对象编程中的<code>decorator</code>，声明函数拥有的特性，并减少代码冗余。它就像钢铁侠的智能盔甲，穿上它的函数会带有某些特定的行为。<code>modifier</code>的主要使用场景是运行函数前的检查，例如地址，变量，余额等。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6fead81e344454294d8d3a60f318e40ad49342aa1d6a6e1054cc993b33bbc693.jpg" alt="钢铁侠的modifier" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">钢铁侠的modifier</figcaption></figure><p>钢铁侠的modifier</p><p>我们定义一个叫做<code>onlyOwner</code>的<code>modifier</code>：</p><pre data-type="codeBlock" text="   // 定义modifier
   modifier onlyOwner {
      require(msg.sender == owner); // 检查调用者是否为owner地址
      _; // 如果是的话，继续运行函数主体；否则报错并revert交易
   }
"><code>   <span class="hljs-comment">// 定义modifier</span>
   <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyOwner</span> </span>{
      <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner); <span class="hljs-comment">// 检查调用者是否为owner地址</span>
      <span class="hljs-keyword">_</span>; <span class="hljs-comment">// 如果是的话，继续运行函数主体；否则报错并revert交易</span>
   }
</code></pre><p>代有<code>onlyOwner</code>修饰符的函数只能被<code>owner</code>地址调用，比如下面这个例子：</p><pre data-type="codeBlock" text="   function changeOwner(address _newOwner) external onlyOwner{
      owner = _newOwner; // 只有owner地址运行这个函数，并改变owner
   }
"><code>   <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">changeOwner</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _newOwner</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span></span>{
      owner <span class="hljs-operator">=</span> _newOwner; <span class="hljs-comment">// 只有owner地址运行这个函数，并改变owner</span>
   }
</code></pre><p>我们定义了一个<code>changeOwner</code>函数，运行他可以改变合约的<code>owner</code>，但是由于<code>onlyOwner</code>修饰符的存在，只有原先的<code>owner</code>可以调用，别人调用就会报错。这也是最常用的控制智能合约权限的方法。</p><p>OppenZepplin的<code>Ownable</code>标准实现：</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>这一讲，我们介绍了solidity中的构造函数和修饰符，并举了一个控制合约权限的<code>Ownable</code>合约。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[MyPunks技术分享-可定制化的NFT]]></title>
            <link>https://paragraph.com/@dao4resilience/mypunks-nft</link>
            <guid>Wehe2Ecq4eIWuVfXbo3O</guid>
            <pubDate>Sun, 24 Apr 2022 07:08:11 GMT</pubDate>
            <description><![CDATA[最近在CryptoApes的DC里看见朋友分享的MyPunks项目，该项目定位是NFT2.0，即可定制化的NFT。因为比较新颖，抱着尝鲜的态度所以我就入坑了。 该项目分为两个NFT系列，一个是MyPunks Face（以下简称Face），一个是MyPunks Item（以下简称Item，就是眼镜、头发、口红、衣服等各种traits），貌似两个系列的所有元素均为Crypto Punks的设计元素。 鄙人本着对Crypto Punks的热爱，并且对该项目如何实现可定制化NFT这一目标十分好奇，故关注并购买了该项目的两个系列NFT，并自己定制的两个MyPunks。本文记录并分享一下该项目从技术上是如何实现定制NFT这一目标的。中年大叔style中年大叔style东北二人转Style东北二人转StyleNFT的定制过程首先你需要购买至少各一个Face和Item。Face当初是每个地址可以免费mint 2个，而1个Item的公售价格是0.035E，截止到目前为止，Face已经全部mint完了，而Item还未mint完（就目前行情来说项目方可能会考虑降价）。 在拥有了Face和Item之后，...]]></description>
            <content:encoded><![CDATA[<p>最近在CryptoApes的DC里看见朋友分享的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/MYPUNKofficial">MyPunks</a>项目，该项目定位是NFT2.0，即可定制化的NFT。因为比较新颖，抱着尝鲜的态度所以我就入坑了。</p><p>该项目分为两个NFT系列，一个是MyPunks Face（以下简称Face），一个是MyPunks Item（以下简称Item，就是眼镜、头发、口红、衣服等各种traits），貌似两个系列的所有元素均为Crypto Punks的设计元素。</p><p>鄙人本着对Crypto Punks的热爱，并且对该项目如何实现可定制化NFT这一目标十分好奇，故关注并购买了该项目的两个系列NFT，并自己定制的两个MyPunks。本文记录并分享一下该项目从技术上是如何实现定制NFT这一目标的。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7bc0e95534914da99ff286fe1b9cef449ff29474c433d77a4f6b39b7b3d18a10.png" alt="中年大叔style" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">中年大叔style</figcaption></figure><p>中年大叔style</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ee44a7c964ffa8419eff9450fff647a2ed7a4417f9ef48b45ecb90de8eab91b1.png" alt="东北二人转Style" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">东北二人转Style</figcaption></figure><p>东北二人转Style</p><h2 id="h-nft" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">NFT的定制过程</h2><p>首先你需要购买至少各一个Face和Item。Face当初是每个地址可以免费mint 2个，而1个Item的公售价格是0.035E，截止到目前为止，Face已经全部mint完了，而Item还未mint完（就目前行情来说项目方可能会考虑降价）。</p><p>在拥有了Face和Item之后，即可登陆MyPunks<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mypunks.xyz/app/equip">官网定制页面</a>进行定制，整个定制过程你需要做的就是：</p><ol><li><p>选择一个Face</p></li><li><p>选择最多七个Item</p></li><li><p>确认效果后进行确认即可（确认操作需要上链，上链之后发生什么见后文）</p></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b994885cd5e3cc890bad5ee17ef9d6d719805be57983a2104ad51097629bfa87.png" alt="定制界面" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">定制界面</figcaption></figure><p>定制界面</p><p>这里需要说明几点：</p><ul><li><p>Face NFT是一个独立合约，Item NFT也是一个独立合约，所以两者都可以独立买卖</p></li><li><p>定制好的NFT实际上还是那个Face NFT，只不过将Item与Face绑定在一起了</p></li><li><p>定制好NFT后，你在OpenSea或者钱包里将看不见你那些已被使用了的Item，但你能看见这些Item在你的Face NFT上</p></li><li><p>定制好的NFT可以转移和卖出，但是卖出后已经装饰的Item也会一并转移，所以如果你准备给你的Face装饰多个Item的话，一定要挂高一些的价格</p></li><li><p>定制好的NFT也可以拆分（拆分也是一个上链操作），拆分后你又能在OpenSea或者钱包里看见你的Item NFT，然后根据自己的喜好重新组装</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">如何实现</h2><h2 id="h-nft" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">定制NFT</h2><p>上文所述用Item去装饰Face这个操作需要上链，而整个上链过程从<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x117c2e95aa1b1ead9db1e39ce494d9776c72a254#code">Item的合约</a>来看，就是将Item NFT转到Face NFT合约地址的一个过程。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a8c0db1a5707201a75b2880ee34693c766fd796bb1709d73983499a673191dba.png" alt="Item合约片段" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Item合约片段</figcaption></figure><p>Item合约片段</p><p>当你用Item去装饰Face的时候就是调用上图中Item合约里的stakeItem这个方法 ，该方法会将传入的所有Item NFT转移到Face这个合约中去（相当于质押），这些Item的Owner将会变成Face的合约地址，所以你从你的钱包里将看不到这些Item。</p><p>当调用Item合约中的safeTransferFrom方法发送Item到Face合约地址时，会去调用Face合约中的onERC721Received这个方法，该方法触发了Face合约去记录被装饰的Face都绑定了哪些Items。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c1daf9cdb6af5e83a1098efb367ea81cacfe7c3186aa6f171c0bdfb5e4ae6f1a.png" alt="Face合约片段" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Face合约片段</figcaption></figure><p>Face合约片段</p><p>上图中“items”这个map变量就记录了faceId对应绑定的itemId列表。这个“items”非常重要，相当于Face合约中不仅仅记录了你拥有哪些Face，并且还可以根据你拥有的Face得到你拥有了哪些Item。Item是与Face绑定的，这也就是为什么当你卖出或者转移了Face之后，所有已经绑定了的Item也会跟着被转移的原因。</p><p>以上过程就完成了NFT的定制：将Item从你的钱包转移至Face合约地址，同时在Face合约中记录该Face绑定了哪些Item。</p><p>之前我们见到的大多数NFT的Metadata信息基本上都是存在IPFS上的（关于Metadata信息的科普建议看看鄙人<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/xing824.eth/O3hpbibMf9vLNz6p80YUriU8Bf3bEaJWvRL49FGAgAc">这篇文章</a>），因为所有的Metadata信息都预先知道，所以提前制作好上传到IPFS上，与此同时项目方一般会在合约里保留设置baseURI的权利以实现开盲盒等操作。</p><p>但对于MyPunks这种需要根据用户定制动态生成Metadata的情况，项目方的解决方案是采用HTTP方式通过中心化服务器生成，也就是说Face和Item的所属权上链，而定制NFT的Medata信息采用中心化方式动态返回。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f54f271124449d6a5d82fe561ca7e76033877adda9da9b2e13ad1481312fa26e.png" alt="调用tokenURI方法" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">调用tokenURI方法</figcaption></figure><p>调用tokenURI方法</p><p>如图所示，调用Face合约查询1172这个Face的tokenURI返回的是一个HTTP的API地址，MyPunks的中心化服务器可以根据链上1172这个Face绑定了哪些Item动态地返回相应的Metadata信息，从而让用户从OpenSea可以看到对应的图片及属性。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cbc39266e927d4e9522f75fb5646c7fa7c149d78148495b0391a280e6ff5bda9.png" alt="OpenSea展示该Face NFT的Metadata信息" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">OpenSea展示该Face NFT的Metadata信息</figcaption></figure><p>OpenSea展示该Face NFT的Metadata信息</p><p>同时Face合约还提供了修改名字方法，除了可以定制自己NFT的样子之外，你还可以给自己的NFT定制专有的名字。</p><p>实际上鄙人不太喜欢HTTP方式去存储NFT的Metadata，因为这样项目方可以更容易且毫无察觉地修改Metadata。目前大多数NFT合约几乎都用IPFS，虽然去中心化存储不可以修改已存储的Metadata，但大多NFT合约也都会有留有Admin权限，只要Admin操作一次setBaseURI实际上也可以修改NFT指向的Metadta信息，从而修改NFT的Metadata（如果修改了可以在链上查到）。</p><p>MyPunks这个项目为了实现可定制化的NFT，将NFT的所有权上链，而metadata采用服务器动态生成的方式，鄙人认为还是可以接受。鄙人一贯的观点是，完全去中心化和完全中心化都是偏激的，重要的是需要达成的目标，具体实现目标的方式可以灵活调整。</p><p>如果非要使用去中心化方式存储，鄙人能想到的方案就是穷尽所有Face+Item组合的可能，将所有组合的metadata放到IPFS上，然后合约中根据Face+Item的组合去拼IPFS的tokenURI拿到Metadata，但是各种各样组合的数量实在是太多了。</p><p>抑或是像鄙人之前<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/xing824.eth/OlvKvj3k0M_g7d8SgmNdMYlthYI3LYWqdtsK1IX3mTM">这个文章中</a>说Pak的Censored项目方案一样，将Face和Item的图片数据存储到链上，然后根据Face+Item的组合在合约里动态地去拼装一个SVG图片，这个难度实际上也不小，同时存储的成本费用也不低。。。</p><p>这里同时也希望下一个定制化NFT的项目能有更好的Metadata动态生成的解决方案。</p><h2 id="h-nft" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">拆分NFT</h2><p>定制好的NFT因为有了上述的“items”变量存在，因此拆分也就变得容易了。拆分的过程实际上就是根据“items”存储的Face和Item的绑定信息，将Item从Face的合约地址转回到该Face的Owner钱包地址去。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/82f75fd68aa5d0d3d250d9526bd374d6746c5c881bc700e2c3c5d240f61fcdce.png" alt="Face合约中的拆分代码" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Face合约中的拆分代码</figcaption></figure><p>Face合约中的拆分代码</p><p>拆分是调用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x327f25b05a291817fd10bab26053f0eb66c16158#readContract">Face合约</a>中的withdraw方法，在维护好“items”这个变量状态之后，最终Item的转移实际上又是去调用的是<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x117c2e95aa1b1ead9db1e39ce494d9776c72a254#code">Item合约</a>的“unstakeItem”方法</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cb9646c0ff5bb3e6e73b750969297d83bdbad442d55a89c53937644c42d8e686.png" alt="Item合约中的解除质押代码" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Item合约中的解除质押代码</figcaption></figure><p>Item合约中的解除质押代码</p><p>上图是<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x117c2e95aa1b1ead9db1e39ce494d9776c72a254#code">Item合约</a>中“unstakeItem”方法，该方法就是将这些Item转到这个Face的owner钱包地址去。</p><p>所以拆分后，你就可以在你的钱包地址里重新看见这些Item，并且开始新的定制再创作了。同时如果市场上没有你喜欢的Item出售，你也可以考虑购买一个包含你喜欢Item的MyPunk，买来整个MyPunks将其拆分，它上面的所有Item也就都属于你了。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">最后</h2><p>目前在NFT熊市，且项目同质化非常严重的市场环境下，MyPunks的创新和探索是值得鼓励和认可的。但目前看来市场的接受度并不是太高，很便宜的价格、很低的交易量，并且连Item都没mint完。即便如此鄙人还是觉得需要各个NFT项目方从更多方面去突破创新，来丰富NFT的生态以及扩宽NFT的边界，而不是一味地在10K形式的PFP上内卷。</p><p>利益相关：MyPunks这个项目比较新颖，所以鄙人购买了一些，已被深深套牢，虽然价格不贵钱也不多，但建议我的读者朋友们就别入坑了。如果是不差钱的大佬想尝试玩玩，也欢迎为鄙人接盘，哈哈。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 7. 控制流，用solidity实现插入排序
Use solidity to write Insertion Sort]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-7-solidity-use-solidity-to-write-insertion-sort</link>
            <guid>f5BuW2WF76I7e4I25Min</guid>
            <pubDate>Sun, 24 Apr 2022 06:29:43 GMT</pubDate>
            <description><![CDATA[这一讲，我们将介绍solidity中的控制流，然后讲如何用solidity实现插入排序（InsertionSort），一个看起来简单，但实际上很容易写出bug的程序。控制流Solidity的控制流与其他语言类似，主要包含以下几种： 1. if-else // if else function IfElseTest(uint256 _number) public returns(uint256){ if(条件){ ...; }else{ ...; } } 2 for循环 for(uint256 i = 0; i &#x3C; n; i++){ ...; } 3. while循环 while(i &#x3C; n){ ...; } 4. do-while循环 do{ ...; }while(i &#x3C; n); 另外还有continue（立即进入下一个循环）和break（跳出当前循环）关键字可以使用。用solidity实现插入排序写在前面：90%以上的人用solidity写插入算法都会出错。插入排序排序算法解决的问题是将无序的一组数字，例如[2, 5, 3, 1]，从小到大一次排...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们将介绍solidity中的控制流，然后讲如何用solidity实现插入排序（InsertionSort），一个看起来简单，但实际上很容易写出bug的程序。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">控制流</h2><p>Solidity的控制流与其他语言类似，主要包含以下几种：</p><p>1. <code>if-else</code></p><pre data-type="codeBlock" text="    // if else
    function IfElseTest(uint256 _number) public returns(uint256){
        if(条件){
            ...;
        }else{
            ...;
        }
    }
"><code>    <span class="hljs-comment">// if else</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">IfElseTest</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _number</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">uint256</span></span>)</span>{
        <span class="hljs-keyword">if</span>(条件){
            ...;
        }<span class="hljs-keyword">else</span>{
            ...;
        }
    }
</code></pre><p>2 <code>for</code>循环</p><pre data-type="codeBlock" text="        for(uint256 i = 0; i &lt; n; i++){
            ...;
        }
"><code>        <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> n; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>){
            ...;
        }
</code></pre><p>3. <code>while</code>循环</p><pre data-type="codeBlock" text="        while(i &lt; n){
            ...;
        }
"><code>        <span class="hljs-keyword">while</span>(i <span class="hljs-operator">&#x3C;</span> n){
            ...;
        }
</code></pre><p>4. <code>do-while</code>循环</p><pre data-type="codeBlock" text="        do{
            ...;

        }while(i &lt; n);
"><code>        do{
            ...;

        }<span class="hljs-keyword">while</span>(i <span class="hljs-operator">&#x3C;</span> n);
</code></pre><p>另外还有<code>continue</code>（立即进入下一个循环）和<code>break</code>（跳出当前循环）关键字可以使用。</p><h2 id="h-solidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">用solidity实现插入排序</h2><p><strong>写在前面：90%以上的人用solidity写插入算法都会出错。</strong></p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">插入排序</h3><p>排序算法解决的问题是将无序的一组数字，例如<code>[2, 5, 3, 1]</code>，从小到大一次排列好。插入排序（InsertionSort）是最简单的一种排序算法，也是很多人学习的第一个算法。它的思路很简答，从前往后，依次将每一个数和排在他前面的数字比大小，如果比前面的数字小，就互换位置。示意图：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1499c42f00400b925afff6cbfbd84eec754e5ba628eac192d4dc88a1892c0f26.gif" alt="插入排序" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">插入排序</figcaption></figure><p>插入排序</p><h3 id="h-python" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">python代码</h3><p>我们可以先看一下插入排序的python代码：</p><pre data-type="codeBlock" text="# Python program for implementation of Insertion Sort
def insertionSort(arr):
    for i in range(1, len(arr)):
        key = arr[i]
        j = i-1
        while j &gt;=0 and key &lt; arr[j] :
                arr[j+1] = arr[j]
                j -= 1
        arr[j+1] = key
"><code><span class="hljs-comment"># Python program for implementation of Insertion Sort</span>
def insertionSort(arr):
    for i in range(1, len(arr)):
        <span class="hljs-attr">key</span> = arr[i]
        <span class="hljs-attr">j</span> = i-<span class="hljs-number">1</span>
        while j >=0 and key &#x3C; arr<span class="hljs-section">[j]</span> :
                arr<span class="hljs-section">[j+1]</span> = arr<span class="hljs-section">[j]</span>
                j <span class="hljs-attr">-</span>= <span class="hljs-number">1</span>
        arr<span class="hljs-section">[j+1]</span> = key
</code></pre><h3 id="h-soliditybug" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">改写成solidity后有BUG！</h3><p>一共8行python代码就可以完成插入排序，非常简单。那么我们将它改写成solidity代码，将函数，变量，循环等等都做了相应的转换，只需要9行代码：</p><pre data-type="codeBlock" text="    // 插入排序 错误版
    function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) {
        
        for (uint i = 1;i &lt; a.length;i++){
            uint temp = a[i];
            uint j=i-1;
            while( (j &gt;= 0) &amp;&amp; (temp &lt; a[j])){
                a[j+1] = a[j];
                j--;
            }
            a[j+1] = temp;
        }
        return(a);
    }
"><code>    <span class="hljs-comment">// 插入排序 错误版</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">insertionSortWrong</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span> a</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">uint</span>[] <span class="hljs-keyword">memory</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">1</span>;i <span class="hljs-operator">&#x3C;</span> a.<span class="hljs-built_in">length</span>;i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>){
            <span class="hljs-keyword">uint</span> temp <span class="hljs-operator">=</span> a[i];
            <span class="hljs-keyword">uint</span> j<span class="hljs-operator">=</span>i<span class="hljs-number">-1</span>;
            <span class="hljs-keyword">while</span>( (j <span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> (temp <span class="hljs-operator">&#x3C;</span> a[j])){
                a[j<span class="hljs-operator">+</span><span class="hljs-number">1</span>] <span class="hljs-operator">=</span> a[j];
                j<span class="hljs-operator">-</span><span class="hljs-operator">-</span>;
            }
            a[j<span class="hljs-operator">+</span><span class="hljs-number">1</span>] <span class="hljs-operator">=</span> temp;
        }
        <span class="hljs-keyword">return</span>(a);
    }
</code></pre><p>那我们把改好的放到remix上去跑，输入<code>[2, 5, 3, 1]</code>。BOOM！有bug！改了半天，没找到bug在哪。我又去google搜”solidity insertion sort”，然后发现网上用solidity写的插入算法教程都是错的，比如</p><p>1. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/coinmonks/sorting-in-solidity-without-comparison-4eb47e04ff0d">Sorting in Solidity without Comparison</a></p><p>2. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nippycodes.com/coding/solidity-insertion-sort-for-sorting-less-than-10-items-with-reference/">Solidity Insertion sort for sorting less than 10 items with reference</a></p><h3 id="h-solidity" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">正确的solidity插入排序</h3><p>花了几个小时，在Dapp-Learning社群一个朋友的帮助下，终于找到了bug所在。solidity中最常用的变量类型是uint，也就是正整数，取到负值的话，会报underflow错误。而在插入算法中，变量j有可能会取到-1，引起报错。</p><p>这里，我们需要把<code>j</code>加1，让它无法取到负值。正确代码：</p><pre data-type="codeBlock" text="    // 插入排序 正确版
    function insertionSort(uint[] memory a) public pure returns(uint[] memory) {
        // note that uint can not take negative value
        for (uint i = 1;i &lt; a.length;i++){
            uint temp = a[i];
            uint j=i;
            while( (j &gt;= 1) &amp;&amp; (temp &lt; a[j-1])){
                a[j] = a[j-1];
                j--;
            }
            a[j] = temp;
        }
        return(a);
    }
"><code>    <span class="hljs-comment">// 插入排序 正确版</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">insertionSort</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span> a</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">uint</span>[] <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-comment">// note that uint can not take negative value</span>
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;i <span class="hljs-operator">&#x3C;</span> a.<span class="hljs-built_in">length</span>;i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>){
            <span class="hljs-keyword">uint</span> temp <span class="hljs-operator">=</span> a[i];
            <span class="hljs-keyword">uint</span> j<span class="hljs-operator">=</span>i;
            <span class="hljs-keyword">while</span>( (j <span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>) <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> (temp <span class="hljs-operator">&#x3C;</span> a[j<span class="hljs-number">-1</span>])){
                a[j] <span class="hljs-operator">=</span> a[j<span class="hljs-number">-1</span>];
                j<span class="hljs-operator">-</span><span class="hljs-operator">-</span>;
            }
            a[j] <span class="hljs-operator">=</span> temp;
        }
        <span class="hljs-keyword">return</span>(a);
    }
</code></pre><p>运行后的结果：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ce0ca676f6cf2f732a9854f6d012322afda3fafb55cc481fb35982c502b58990.png" alt="输入[2,5,3,1] 输出[1,2,3,5]" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">输入[2,5,3,1] 输出[1,2,3,5]</figcaption></figure><p>输入[2,5,3,1] 输出[1,2,3,5]</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>这一讲，我们介绍了solidity中控制流，并且用solidity写了插入排序。看起来很简单，但实际很难。这就是solidity，坑很多，每个月都有项目因为这些小bug损失几千万甚至上亿美元。掌握好基础，不断练习，才能写出更好的solidity代码。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 6. 映射类型 mapping]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-6-mapping</link>
            <guid>jcW1xTrgU5WJgPxR9s5R</guid>
            <pubDate>Sun, 24 Apr 2022 06:22:18 GMT</pubDate>
            <description><![CDATA[这一讲，我们将介绍solidity中的哈希表：映射（Mapping）类型。映射Mapping在映射中，人们可以通过键（Key）来查询对应的值（Value），比如：通过一个人的id来查询他的钱包地址。 声明映射的格式为 mapping(_KeyType => _ValueType)，其中_KeyType和_ValueType分别是Key和Value的变量类型。例子： mapping(uint => address) public idToAddress; // id映射到地址 mapping(address => address) public swapPair; // 币对的映射，地址到地址 映射的规则规则1：映射的_KeyType只能选择solidity默认的类型，比如uint，address等，不能用自定义的结构体。而_ValueType可以使用自定义的类型。下面这个例子会报错，因为_KeyType使用了我们自定义的结构体： // 我们定义一个结构体 Struct struct Student{ uint256 id; uint256 score; } mapping(Stu...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们将介绍solidity中的哈希表：映射（Mapping）类型。</p><h2 id="h-mapping" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">映射Mapping</h2><p>在映射中，人们可以通过键（Key）来查询对应的值（Value），比如：通过一个人的id来查询他的钱包地址。</p><p>声明映射的格式为 <code>mapping(_KeyType =&gt; _ValueType)</code>，其中<code>_KeyType</code>和<code>_ValueType</code>分别是Key和Value的变量类型。例子：</p><pre data-type="codeBlock" text="    mapping(uint =&gt; address) public idToAddress; // id映射到地址
    mapping(address =&gt; address) public swapPair; // 币对的映射，地址到地址
"><code>    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">uint</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">address</span>) <span class="hljs-keyword">public</span> idToAddress; <span class="hljs-comment">// id映射到地址</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">address</span>) <span class="hljs-keyword">public</span> swapPair; <span class="hljs-comment">// 币对的映射，地址到地址</span>
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">映射的规则</h2><p><strong>规则1</strong>：映射的<code>_KeyType</code>只能选择<code>solidity</code>默认的类型，比如<code>uint</code>，<code>address</code>等，不能用自定义的结构体。而<code>_ValueType</code>可以使用自定义的类型。下面这个例子会报错，因为<code>_KeyType</code>使用了我们自定义的结构体：</p><pre data-type="codeBlock" text="    // 我们定义一个结构体 Struct
    struct Student{
        uint256 id;
        uint256 score; 
    }
     mapping(Student =&gt; uint) public testVar;
"><code>    <span class="hljs-comment">// 我们定义一个结构体 Struct</span>
    <span class="hljs-keyword">struct</span> <span class="hljs-title">Student</span>{
        <span class="hljs-keyword">uint256</span> id;
        <span class="hljs-keyword">uint256</span> score; 
    }
     <span class="hljs-keyword">mapping</span>(Student <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint</span>) <span class="hljs-keyword">public</span> testVar;
</code></pre><p><strong>规则2</strong>：映射的存储位置必须是<code>storage</code>，因此可以用于合约的状态变量，函数中的<code>stoage</code>变量。不能用于<code>public</code>函数的参数或返回结果中。</p><p><strong>规则3</strong>：如果映射声明为<code>public</code>，那么<code>solidity</code>会自动给你创建一个<code>getter</code>函数，可以通过Key来查询对应的Value。</p><p><strong>规则4</strong>：给映射新增的键值对的语法为<code>var[_Key] = _Value</code> ，其中是映射变量名，分别<code>_Key</code>和<code>_Value</code>对应新增的键值对。例子：</p><pre data-type="codeBlock" text="    function writeMap (uint _Key, address _Value) public{
        idToAddress[_Key] = _Value;
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">writeMap</span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> _Key, <span class="hljs-keyword">address</span> _Value</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span></span>{
        idToAddress[_Key] <span class="hljs-operator">=</span> _Value;
    }
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>这一讲，我们介绍了solidity中哈希表——映射（Mapping）的用法。至此，我们已经学习了所有常用变量种类，之后我们会学习控制流if-else, while等。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 5. 引用类型, array, struct

这一讲，我们将介绍solidity中的两个重要变量类型：数组（array）和结构体（struct）]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-5-array-struct-solidity-array-struct</link>
            <guid>bFFmvMRBsN1sAerZsZao</guid>
            <pubDate>Sun, 24 Apr 2022 06:19:18 GMT</pubDate>
            <description><![CDATA[这一讲，我们将介绍solidity中的两个重要变量类型：数组（array）和结构体（struct）数组 array数组（Array）是solidity常用的一种变量类型，用来存储一组数据（整数，字节，地址等等）。数组分为固定长度数组和可变长度数组两种：固定长度数组：在声明时指定数组的长度。用T[k]的格式声明，其中T是元素的类型，k是长度，例如： // 固定长度 Array uint[8] array1; byte[5] array2; address[100] array3; 可变长度数组：在声明时不指定数组的长度。用T[]的格式声明，其中T是元素的类型，例如（bytes比较特殊，是数组，但是不用加[]）： // 可变长度 Array uint[] array4; byte[] array5; address[] array6; bytes array7; 创建数组的规则在solidity里，创建数组有一些规则：对于memory可变长度数组，可以用new操作符来创建，但是必须声明长度，并且长度不能改变。例子： // memory可变长度 Array uint[] memory ...]]></description>
            <content:encoded><![CDATA[<p>这一讲，我们将介绍solidity中的两个重要变量类型：数组（array）和结构体（struct）</p><h2 id="h-array" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">数组 array</h2><p>数组（Array）是solidity常用的一种变量类型，用来存储一组数据（整数，字节，地址等等）。数组分为固定长度数组和可变长度数组两种：</p><ul><li><p><strong>固定长度数组</strong>：在声明时指定数组的长度。用T[k]的格式声明，其中T是元素的类型，k是长度，例如：</p></li></ul><pre data-type="codeBlock" text="    // 固定长度 Array
    uint[8] array1;
    byte[5] array2;
    address[100] array3;
"><code>    // 固定长度 Array
    uint<span class="hljs-section">[8]</span> array1<span class="hljs-comment">;</span>
    byte<span class="hljs-section">[5]</span> array2<span class="hljs-comment">;</span>
    address<span class="hljs-section">[100]</span> array3<span class="hljs-comment">;</span>
</code></pre><ul><li><p><strong>可变长度数组</strong>：在声明时不指定数组的长度。用T[]的格式声明，其中T是元素的类型，例如（bytes比较特殊，是数组，但是不用加[]）：</p></li></ul><pre data-type="codeBlock" text="    // 可变长度 Array
    uint[] array4;
    byte[] array5;
    address[] array6;
    bytes array7;
"><code>    // 可变长度 Array
    uint<span class="hljs-section">[]</span> array4<span class="hljs-comment">;</span>
    byte<span class="hljs-section">[]</span> array5<span class="hljs-comment">;</span>
    address<span class="hljs-section">[]</span> array6<span class="hljs-comment">;</span>
    bytes array7<span class="hljs-comment">;</span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">创建数组的规则</h3><p>在solidity里，创建数组有一些规则：</p><ul><li><p>对于memory可变长度数组，可以用new操作符来创建，但是必须声明长度，并且长度不能改变。例子：</p></li></ul><pre data-type="codeBlock" text="    // memory可变长度 Array
    uint[] memory array8 = new uintUnsupported embed;
    bytes memory array9 = new bytes(9);
"><code>    <span class="hljs-comment">// memory可变长度 Array</span>
    <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span> array8 <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> uintUnsupported embed;
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> array9 <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(<span class="hljs-number">9</span>);
</code></pre><ul><li><p>数组字面常数是写作表达式形式的数组，并且不会立即赋值给变量，例如 <code>[uint(1),2,3]</code>（需要声明第一个元素的类型，不然默认用存储空间最小的类型）</p></li><li><p>如果创建的是dynamic array，你需要一个一个元素的赋值。</p></li></ul><pre data-type="codeBlock" text="        uint[] memory x = new uintUnsupported embed;
        x[0] = 1;
        x[1] = 3;
        x[2] = 4;
"><code>        uint<span class="hljs-section">[]</span> memory <span class="hljs-attr">x</span> = new uintUnsupported embed<span class="hljs-comment">;</span>
        x<span class="hljs-section">[0]</span> = 1<span class="hljs-comment">;</span>
        x<span class="hljs-section">[1]</span> = 3<span class="hljs-comment">;</span>
        x<span class="hljs-section">[2]</span> = 4<span class="hljs-comment">;</span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">数组成员</h3><ul><li><p><strong>length</strong>: 数组有一个包含元素数量的length成员， 内存数组的长度在创建后是固定的。</p></li><li><p><strong>push()</strong>:可变长度数组 和 <code>bytes</code>拥有 <code>push()</code> 成员，可以在数组最后添加一个0元素。</p></li><li><p><strong>push(x):</strong> 可变长度数组 和 bytes拥有 <code>push(x)</code> 成员，可以在数组最后添加一个x元素。</p></li><li><p><strong>pop</strong>: 可变长度数组 和 bytes拥有 <code>pop</code> 成员，可以移除数组最后一个元素。</p></li></ul><h2 id="h-struct" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结构体 struct</h2><p>Solidity 支持通过构造结构体的形式定义新的类型。创建结构体的方法：</p><pre data-type="codeBlock" text="    // 结构体
    struct Student{
        uint256 id;
        uint256 score; 
    }

    Student student; // 初始一个student结构体
"><code>    <span class="hljs-comment">// 结构体</span>
    <span class="hljs-keyword">struct</span> <span class="hljs-title">Student</span>{
        <span class="hljs-keyword">uint256</span> id;
        <span class="hljs-keyword">uint256</span> score; 
    }

    Student student; <span class="hljs-comment">// 初始一个student结构体</span>
</code></pre><p>给结构体赋值的两种方法：</p><pre data-type="codeBlock" text="    //  给结构体赋值
    // 方法1:在函数中创建一个storage的struct引用
    function initStudent1() external{
        Student storage _student = student; // assign a copy of student
        _student.id = 11;
        _student.score = 100;
    }

     // 方法2:直接引用状态变量的struct
    function initStudent2() external{
        student.id = 1;
        student.score = 80;
    }
  
"><code>    <span class="hljs-comment">//  给结构体赋值</span>
    <span class="hljs-comment">// 方法1:在函数中创建一个storage的struct引用</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initStudent1</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>{
        Student <span class="hljs-keyword">storage</span> _student <span class="hljs-operator">=</span> student; <span class="hljs-comment">// assign a copy of student</span>
        _student.id <span class="hljs-operator">=</span> <span class="hljs-number">11</span>;
        _student.score <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;
    }

     <span class="hljs-comment">// 方法2:直接引用状态变量的struct</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initStudent2</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>{
        student.id <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
        student.score <span class="hljs-operator">=</span> <span class="hljs-number">80</span>;
    }
  
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>这一讲，我们介绍了solidity中数组（array）和结构体（struct）的基本用法。下一讲我们将介绍solidity中的哈希表——映射（mapping）。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门:4. 引用类型：存储位置 storage, memory, calldata]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-4-storage-memory-calldata</link>
            <guid>8U82vCbrc7zJI61ZoaBx</guid>
            <pubDate>Sun, 24 Apr 2022 05:54:08 GMT</pubDate>
            <description><![CDATA[Solidity中的引用类型引用类型(Reference Type)：包括数组（array），结构体（struct）和映射（mapping），这类变量占空间大，赋值时候直接传递地址（类似指针）。由于这类变量比较复杂，占用存储空间大，我们在使用时必须要声明数据存储的位置。数据位置solidity数据存储位置有三类：storage，memory和calldata。不同存储位置的gas成本不同。storage类型的数据存在链上，类似计算机的硬盘，消耗gas多；memory和calldata类型的临时存在内存里，消耗gas少。大致用法： 1. storage：合约里的状态变量默认都是storage，存储在链上。 2. memory：函数里的参数和临时变量一般用memory，存储在内存中，不上链。 3. calldata：和memory类似，存储在内存中，不上链。与memory的不同点在于calldata变量不能修改（immutable），一般用于函数的参数。例子： function fCalldata(uint[] calldata _x) public pure returns(uin...]]></description>
            <content:encoded><![CDATA[<h2 id="h-solidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Solidity中的引用类型</h2><p><strong>引用类型(Reference Type)</strong>：包括数组（<code>array</code>），结构体（<code>struct</code>）和映射（<code>mapping</code>），这类变量占空间大，赋值时候直接传递地址（类似指针）。由于这类变量比较复杂，占用存储空间大，我们在使用时必须要声明数据存储的位置。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">数据位置</h2><p>solidity数据存储位置有三类：storage，memory和calldata。不同存储位置的gas成本不同。storage类型的数据存在链上，类似计算机的硬盘，消耗gas多；memory和calldata类型的临时存在内存里，消耗gas少。大致用法：</p><p><strong>1.</strong> <code>storage</code>：合约里的状态变量默认都是storage，存储在链上。</p><p><strong>2.</strong> <code>memory</code>：函数里的参数和临时变量一般用memory，存储在内存中，不上链。</p><p><strong>3.</strong> <code>calldata</code>：和memory类似，存储在内存中，不上链。与memory的不同点在于calldata变量不能修改（immutable），一般用于函数的参数。例子：</p><pre data-type="codeBlock" text="    function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
        //参数为calldata数组，不能被修改
        // _x[0] = 0 //这样修改会报错
        return(_x);
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fCalldata</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">calldata</span> _x</span>) <span class="hljs-title"><span class="hljs-keyword">public</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">uint</span>[] <span class="hljs-keyword">calldata</span></span>)</span>{
        <span class="hljs-comment">//参数为calldata数组，不能被修改</span>
        <span class="hljs-comment">// _x[0] = 0 //这样修改会报错</span>
        <span class="hljs-keyword">return</span>(_x);
    }
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">不同类型相互赋值时的规则：</h3><p>在不同存储类型相互赋值时候，有时会产生独立的副本（修改新变量不会影响原变量），有时会产生引用（修改新变量会影响原变量）。规则如下：</p><p>1. <code>storage</code>（合约的状态变量）赋值给本地<code>storage</code>（函数里的）时候，会创建引用，改变新变量会影响原变量。例子：</p><pre data-type="codeBlock" text="    uint[] x = [1,2,3]; // 状态变量：数组 x

    function fStorage() public{
        //声明一个storage的变量 xStorage，指向x。修改xCopy也会影响x
        uint[] storage xStorage = x;
        xStorage[0] = 100;
    }
"><code>    <span class="hljs-keyword">uint</span>[] x <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]; <span class="hljs-comment">// 状态变量：数组 x</span>

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fStorage</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span></span>{
        <span class="hljs-comment">//声明一个storage的变量 xStorage，指向x。修改xCopy也会影响x</span>
        <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">storage</span> xStorage <span class="hljs-operator">=</span> x;
        xStorage[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;
    }
</code></pre><p>2. <code>storage</code>赋值给<code>memory</code>，会创建独立的复本，修改其中一个不会影响另一个；反之亦然。例子：</p><pre data-type="codeBlock" text="    uint[] x = [1,2,3]; // 状态变量：数组 x
    
    function fMemory() public view{
        //声明一个Memory的变量xMemory，复制x。修改xMemory不会影响x
        uint[] memory xMemory = x;
        xMemory[0] = 100;
    }
"><code>    <span class="hljs-keyword">uint</span>[] x <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]; <span class="hljs-comment">// 状态变量：数组 x</span>
    
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fMemory</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">view</span></span></span>{
        <span class="hljs-comment">//声明一个Memory的变量xMemory，复制x。修改xMemory不会影响x</span>
        <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span> xMemory <span class="hljs-operator">=</span> x;
        xMemory[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;
    }
</code></pre><p>3. <code>memory</code>赋值给<code>memory</code>，会创建引用，改变新变量会影响原变量。</p><p>4. 其他情况，变量赋值给<code>storage</code>，会创建独立的复本，修改其中一个不会影响另一个。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>在第4讲，我们介绍了solidity中的引用类型和数据位置，重点是storage, memory和calldata三个关键字的用法。他们出现的原因是为了节省链上有限的存储空间和降低gasfee消耗。下一讲我们会介绍引用类型中的数组。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门:3. 函数类型]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-3</link>
            <guid>0tSObM5RhNPAL8oBkf6I</guid>
            <pubDate>Sun, 24 Apr 2022 05:28:38 GMT</pubDate>
            <description><![CDATA[Solidity中的函数solidity官方文档里把函数归到数值类型，但我觉得差别很大，所以单独分一类。我们先看一下solidity中函数的形式：function (&#x3C;parameter types>) {internal|external} [pure|view|payable] [returns (&#x3C;return types>)] 看着些复杂，咱们从前往后一个一个看（方括号中的是可写可不写的关键字）： 1. function：声明函数时的固定用法，想写函数，就要以function关键字开头。 2. ()：圆括号里写函数的参数，也就是要输入到函数的变量类型和名字。 3. {internal|external|public|private} ：函数可见性说明符，一共4种。没标明函数类型的，默认internal。public: 内部外部均可见，并且自动给stoage变量生成 getter 函数 。private: 只能从本合约内部访问，继承的合约也不能用。external: 只能从合约外部访问（但是可以用this.f()来调用，f是函数名）internal: 只...]]></description>
            <content:encoded><![CDATA[<h2 id="h-solidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Solidity中的函数</h2><p>solidity官方文档里把函数归到数值类型，但我觉得差别很大，所以单独分一类。我们先看一下solidity中函数的形式：</p><pre data-type="codeBlock" text="function (&lt;parameter types&gt;) {internal|external} [pure|view|payable] [returns (&lt;return types&gt;)]
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">&#x3C;parameter types></span>) </span>{<span class="hljs-keyword">internal</span><span class="hljs-operator">|</span><span class="hljs-keyword">external</span>} [<span class="hljs-keyword">pure</span><span class="hljs-operator">|</span><span class="hljs-keyword">view</span><span class="hljs-operator">|</span><span class="hljs-keyword">payable</span>] [<span class="hljs-keyword">returns</span> (<span class="hljs-operator">&#x3C;</span><span class="hljs-keyword">return</span> types<span class="hljs-operator">></span>)]
</code></pre><p>看着些复杂，咱们从前往后一个一个看（方括号中的是可写可不写的关键字）：</p><p><strong>1. function</strong>：声明函数时的固定用法，想写函数，就要以function关键字开头。</p><p><strong>2. ()</strong>：圆括号里写函数的参数，也就是要输入到函数的变量类型和名字。</p><p><strong>3. {internal|external|public|private}</strong> ：函数可见性说明符，一共4种。没标明函数类型的，默认internal。</p><ul><li><p><code>public</code>: 内部外部均可见，并且自动给stoage变量生成 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.soliditylang.org/en/v0.8.13/contracts.html#getter-functions">getter 函数</a> 。</p></li><li><p><code>private</code>: 只能从本合约内部访问，继承的合约也不能用。</p></li><li><p><code>external</code>: 只能从合约外部访问（但是可以用this.f()来调用，f是函数名）</p></li><li><p><code>internal</code>: 只能从合约内部访问，继承的合约可以用。</p></li></ul><p><strong>4.</strong> <strong>[pure|view|payable]</strong>：决定函数权限/功能的关键字。payable很好理解，带着它的函数，运行的时候可以给合约转入ETH。pure和view的介绍见下一节。</p><p><strong>5. [returns ()]</strong>：函数返回的变量类型和名称。</p><h2 id="h-pureview" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">到底什么是Pure和View？</h2><p>我刚开始学solidity的时候，一直不理解pure跟view关键字，因为别的语言没有类似的关键字。solidity加入这两个关键字，我认为是因为gas fee。合约的状态变量存储在链上，gas fee很贵，如果不改写这些变量，就不用付gas。调用pure跟view的函数是不需要付gas的。</p><p>我画了一个马里奥插画，帮助大家理解。在插画里，我把合约中的状态变量（存储在链上）比作碧池公主，三种不同的角色代表不同的关键字。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/478d6bd5dd7638df0845249891acfaad7f337ebac6697745fe8d739f36956495.png" alt="WTH is pure and view in solidity?" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">WTH is pure and view in solidity?</figcaption></figure><p>WTH is pure and view in solidity?</p><p><strong>pure</strong>，中文意思是“纯”，在solidity里理解为“纯纯牛马”。包含pure关键字的函数，不能读取也不能写入存储在链上的状态变量。就像小怪一样，看不到也摸不到碧池公主。</p><p><strong>view</strong>，“看”，在solidity里理解为“看客”。包含pure关键字的函数，能读取但也不能写入状态变量。类似马里奥，能看到碧池，但终究是看客，不能入洞房。</p><p><strong>不写pure也不写view</strong>，函数既可以读取也可以写入状态变量。类似马里奥里的boss，可以对碧池公主为所欲为🐶。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">代码</h2><h3 id="h-1-pure-vs-view" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1. pure v.s. view</h3><p>我们在合约里定义一个状态变量 _number = 5。</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract FunctionTypes{
    uint256 public _number = 5;
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">FunctionTypes</span></span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> _number <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;
</code></pre><p>定义一个add() function，每次调用，输出 _number + 1。</p><pre data-type="codeBlock" text="    // 默认
    function add() external{
        _number = _number + 1;
    }
"><code>    <span class="hljs-comment">// 默认</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>{
        _number <span class="hljs-operator">=</span> _number <span class="hljs-operator">+</span> <span class="hljs-number">1</span>;
    }
</code></pre><p>如果add()包含了pure关键字，例如 function add() pure external，就会报错。因为pure（纯纯牛马）是不配读取合约里的状态变量的，更不配改写。那pure函数能做些什么？举个例子，你可以给函数传递一个参数 _number，然后让他返回 _number+1。</p><pre data-type="codeBlock" text="    // pure: 纯纯牛马
    function addPure(uint256 _number) external pure returns(uint256 new_number){
        new_number = _number+1;
    }
"><code>    <span class="hljs-comment">// pure: 纯纯牛马</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addPure</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _number</span>) <span class="hljs-title"><span class="hljs-keyword">external</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">uint256</span> new_number</span>)</span>{
        new_number <span class="hljs-operator">=</span> _number<span class="hljs-operator">+</span><span class="hljs-number">1</span>;
    }
</code></pre><p>如果add()包含view，比如function add() view external，也会报错。因为view能读取，但不能够改写状态变量。可以稍微改写下方程，让他不改写 _number，而是返回一个新的变量。</p><pre data-type="codeBlock" text="    // view: 看客
    function addView() external view returns(uint256 new_number) {
        new_number = number + 1;
    }
"><code>    <span class="hljs-comment">// view: 看客</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addView</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> new_number</span>) </span>{
        new_number <span class="hljs-operator">=</span> number <span class="hljs-operator">+</span> <span class="hljs-number">1</span>;
    }
</code></pre><h3 id="h-2-internal-vs-external" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2. Internal v.s. External</h3><pre data-type="codeBlock" text="    // internal: 内部
    function minus() internal {
        number = number - 1;
    }

    // 合约内的函数可以调用内部函数
    function minusCall() external {
        minus();
    }
"><code>    <span class="hljs-comment">// internal: 内部</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">minus</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
        number <span class="hljs-operator">=</span> number <span class="hljs-operator">-</span> <span class="hljs-number">1</span>;
    }

    <span class="hljs-comment">// 合约内的函数可以调用内部函数</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">minusCall</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        minus();
    }
</code></pre><p>我们定义一个internal的minus()函数，每次调用使得number变量减1。由于是internal，只能由合约内部调用。我们再定义一个external的minusCall()函数，调用minus（）。这样，人们就能通过调用minusCall()来间接调用internal的minus()。</p><h3 id="h-3-payable" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3. Payable</h3><pre data-type="codeBlock" text="    // payable: 递钱，能给合约支付eth的函数
    function minusPayable() external payable returns(uint256 balance) {
        minus();    
        balance = address(this).balance;
    }
"><code>    <span class="hljs-comment">// payable: 递钱，能给合约支付eth的函数</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">minusPayable</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> balance</span>) </span>{
        minus();    
        balance <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
    }
</code></pre><p>我们定义一个external payable的minusPayable()函数，间接的调用minus()，并且返回合约里的ETH余额（this关键字可以让我们引用合约地址)。我们调用minusPayable()时，往合约里转入1个ETH。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0e8e5f17c393b2cb21910de832cbe09f768afeabec7ad300c9927e59cab0c9af.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>我们可以在返回的信息中看到，合约的余额是1 ETH。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9f92243de66c27466cecf76e9861863153a98448393ea55e08659f1612ead2eb.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><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>在第三讲，我们介绍了solidity中的函数类型，比较难理解的是pure和view，在其他语言中没出现过。solidity拥有pure和view两种关键字主要是为了节省gas fee和控制函数权限，这两种方程都是不消耗gas的。下一讲我们会介绍引用和映射两种类型，并介绍更复杂的函数。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门: 2. 数值类型]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-2</link>
            <guid>bcShFnAIw9XhciubZ1t6</guid>
            <pubDate>Sun, 24 Apr 2022 05:22:11 GMT</pubDate>
            <description><![CDATA[Solidity中的变量类型1. 数值类型(Value Type)：包括布尔型，整数型等等，这类变量赋值时候直接传递数值。 2. 引用类型(Reference Type)：包括数组和结构体，这类变量占空间大，赋值时候直接传递地址（类似指针）。 3. 映射类型(Mapping Type): Solidity里的哈希表。 4. 函数类型(Function Type)：solidity文档里把函数归到数值类型，但我觉得他跟其他类型差别很大，所以单独分一类。 我们只介绍一些常用的类型，不常用的不讲。这篇介绍数值类型，第3讲介绍函数类型，第4讲介绍引用和映射。数值类型1. 布尔型：取值是true 或者 false。 // 布尔值 bool public _bool = true; 布尔值的运算符，包括：! （逻辑非）&& （逻辑与， "and" ）|| （逻辑或， "or" ）== （等于）!= （不等于）代码： // 布尔运算 bool public _bool1 = !_bool; //取非 bool public _bool2 = _bool &#x26;&#x26; _bool1;...]]></description>
            <content:encoded><![CDATA[<h2 id="h-solidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Solidity中的变量类型</h2><p><strong>1. 数值类型(Value Type)</strong>：包括布尔型，整数型等等，这类变量赋值时候直接传递数值。</p><p><strong>2. 引用类型(Reference Type)</strong>：包括数组和结构体，这类变量占空间大，赋值时候直接传递地址（类似指针）。</p><p><strong>3. 映射类型(Mapping Type)</strong>: Solidity里的哈希表。</p><p><strong>4. 函数类型(Function Type)</strong>：solidity文档里把函数归到数值类型，但我觉得他跟其他类型差别很大，所以单独分一类。</p><p>我们只介绍一些常用的类型，不常用的不讲。这篇介绍数值类型，第3讲介绍函数类型，第4讲介绍引用和映射。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">数值类型</h2><h3 id="h-1-true-false" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1. 布尔型：取值是true 或者 false。</h3><pre data-type="codeBlock" text="    // 布尔值
    bool public _bool = true;
"><code>    <span class="hljs-comment">// 布尔值</span>
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> _bool <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;
</code></pre><p>布尔值的运算符，包括：</p><ul><li><p><code>!</code> （逻辑非）</p></li><li><p><code>&amp;&amp;</code> （逻辑与， &quot;and&quot; ）</p></li><li><p><code>||</code> （逻辑或， &quot;or&quot; ）</p></li><li><p><code>==</code> （等于）</p></li><li><p><code>!=</code> （不等于）</p></li></ul><p>代码：</p><pre data-type="codeBlock" text="    // 布尔运算
    bool public _bool1 = !_bool; //取非
    bool public _bool2 = _bool &amp;&amp; _bool1; //与
    bool public _bool3 = _bool || _bool1; //或
    bool public _bool4 = _bool == _bool1; //相等
    bool public _bool5 = _bool != _bool1; //不相等
"><code>    <span class="hljs-comment">// 布尔运算</span>
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> _bool1 <span class="hljs-operator">=</span> <span class="hljs-operator">!</span>_bool; <span class="hljs-comment">//取非</span>
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> _bool2 <span class="hljs-operator">=</span> _bool <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> _bool1; <span class="hljs-comment">//与</span>
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> _bool3 <span class="hljs-operator">=</span> _bool <span class="hljs-operator">|</span><span class="hljs-operator">|</span> _bool1; <span class="hljs-comment">//或</span>
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> _bool4 <span class="hljs-operator">=</span> _bool <span class="hljs-operator">=</span><span class="hljs-operator">=</span> _bool1; <span class="hljs-comment">//相等</span>
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> _bool5 <span class="hljs-operator">=</span> _bool <span class="hljs-operator">!</span><span class="hljs-operator">=</span> _bool1; <span class="hljs-comment">//不相等</span>
</code></pre><p>变量_bool的取值是true；_bool1是_bool的非，为false；_bool &amp;&amp; _bool1为false；_bool || _bool1为true；_bool == _bool1为false；_bool != _bool1为true。</p><h3 id="h-2" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2. 整型：整数，最常用的包括</h3><pre data-type="codeBlock" text="    // 整型
    int public _int = -1; // 整数，包括负数
    uint public _uint = 1; // 正整数
    uint256 public _number = 20220330; // 256位正整数
"><code>    <span class="hljs-comment">// 整型</span>
    <span class="hljs-keyword">int</span> <span class="hljs-keyword">public</span> _int <span class="hljs-operator">=</span> <span class="hljs-number">-1</span>; <span class="hljs-comment">// 整数，包括负数</span>
    <span class="hljs-keyword">uint</span> <span class="hljs-keyword">public</span> _uint <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; <span class="hljs-comment">// 正整数</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> _number <span class="hljs-operator">=</span> <span class="hljs-number">20220330</span>; <span class="hljs-comment">// 256位正整数</span>
</code></pre><p>整型的运算符，常用的包括：</p><ul><li><p>比较运算符： <code>&lt;=</code> ， <code>&lt;</code> ， <code>==</code> ， <code>!=</code> ， <code>&gt;=</code> ， <code>&gt;</code> （返回布尔值）</p></li><li><p>算数运算符： <code>+</code> ， <code>-</code> ， 一元运算 <code>-</code> ， <code>+</code> ， <code>*</code> ， <code>/</code> ， <code>%</code> （取余） ， <code>**</code> （幂）</p></li></ul><p>代码：</p><pre data-type="codeBlock" text="    // 整数运算
    uint256 public _number1 = _number + 1; // +，-，*，/
    uint256 public _number2 = 2**2; // 指数
    uint256 public _number3 = 7 % 2; // 取余数
    bool public _numberbool = _number2 &gt; _number3; // 比大小
"><code>    <span class="hljs-comment">// 整数运算</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> _number1 <span class="hljs-operator">=</span> _number <span class="hljs-operator">+</span> <span class="hljs-number">1</span>; <span class="hljs-comment">// +，-，*，/</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> _number2 <span class="hljs-operator">=</span> <span class="hljs-number">2</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-number">2</span>; <span class="hljs-comment">// 指数</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> _number3 <span class="hljs-operator">=</span> <span class="hljs-number">7</span> <span class="hljs-operator">%</span> <span class="hljs-number">2</span>; <span class="hljs-comment">// 取余数</span>
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> _numberbool <span class="hljs-operator">=</span> _number2 <span class="hljs-operator">></span> _number3; <span class="hljs-comment">// 比大小</span>
</code></pre><p>大家可以跑一下代码，看看这4个变量分别是多少。答对奖励个POAP？</p><h3 id="h-3-address-20" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3. 地址类型：address存储一个 20 字节的值（以太坊地址的大小）。</h3><p>地址类型也有成员变量，并作为所有合约的基础。有普通的地址和可以转账ETH的地址（payable）。payable的地址拥有balance和tranfer()两个成员，方便查询eth余额以及转账。</p><p>代码</p><pre data-type="codeBlock" text="    // 地址
    address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
    address payable public _address1 = payable(_address); // payable address，可以转账、查余额
    // 地址类型的成员
    uint256 public balance = _address1.balance; // balance of address
"><code>    <span class="hljs-comment">// 地址</span>
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> _address <span class="hljs-operator">=</span> <span class="hljs-number">0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71</span>;
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">payable</span> <span class="hljs-keyword">public</span> _address1 <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(_address); <span class="hljs-comment">// payable address，可以转账、查余额</span>
    <span class="hljs-comment">// 地址类型的成员</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> balance <span class="hljs-operator">=</span> _address1.<span class="hljs-built_in">balance</span>; <span class="hljs-comment">// balance of address</span>
</code></pre><p>下一讲介绍函数的时候，会介绍如何使用地址类型。</p><h3 id="h-4-bytesbyte-bytes8-bytes32" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4. 定长字节数组：bytes分两种，一种定长（byte, bytes8, bytes32），另一种不定长。定长的属于数值类型，不定长的是引用类型（之后讲）。</h3><p>定长bytes可以存一些数据，消耗gas比较少。</p><p>代码：</p><pre data-type="codeBlock" text="    // 固定长度的字节数组
    bytes32 public _byte32 = &quot;MiniSolidity&quot;; 
    bytes1 public _byte = _byte32[0]; 
"><code>    <span class="hljs-comment">// 固定长度的字节数组</span>
    <span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> _byte32 <span class="hljs-operator">=</span> <span class="hljs-string">"MiniSolidity"</span>; 
    <span class="hljs-keyword">bytes1</span> <span class="hljs-keyword">public</span> _byte <span class="hljs-operator">=</span> _byte32[<span class="hljs-number">0</span>]; 
</code></pre><p>“MiniSolidity”以字节的方式存储进变量_byte32，转换成16进制为：0x4d696e69536f6c69646974790000000000000000000000000000000000000000</p><p>_byte是_byte32第一个字节，为0x4d。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>在第二讲，我们介绍了solidity中4种变量类型，并详细介绍了数值变量（value type)中的布尔型，整型，地址和定长字节数组。之后我们会介绍其他几种类型。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[Solidity极简入门:1. HelloWeb3 (三行代码)]]></title>
            <link>https://paragraph.com/@dao4resilience/solidity-1-helloweb3</link>
            <guid>PnauXw8ePbBCGjvLQ98Q</guid>
            <pubDate>Sun, 24 Apr 2022 05:21:28 GMT</pubDate>
            <description><![CDATA[Solidity简述Solidity是以太坊虚拟机（EVM）智能合约的语言。同时，我也觉得solidity是玩链上项目必备的技能：区块链项目大部分是开源的，如果你能读懂代码，能帮你规避很多归钱项目。 Solidity具有两个特点：基于对象：学会之后，能帮你挣钱找对象。高级：不会solidity，在币圈显得很low。开发工具：remix本教程中，我会用remix来跑solidity合约。remix是以太坊官方推荐的智能合约开发IDE，适合新手，可以在浏览器中快速部署测试智能合约，你不需要在本地安装任何程序。 网址：remix.ethereum.org 进入remix，我们可以看到最左边的菜单有三个按钮，分别对应文件（写代码的地方），编译（跑代码），部署（部署到链上）。我们点新建（Create New File）按钮，就可以创建一个空白的solidity合约。remix面板remix面板第一个Solidity程序：很简单，只有1行注释+3行代码：// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; contract Hello...]]></description>
            <content:encoded><![CDATA[<h2 id="h-solidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Solidity简述</h2><p>Solidity是以太坊虚拟机（EVM）智能合约的语言。同时，我也觉得solidity是玩链上项目必备的技能：区块链项目大部分是开源的，如果你能读懂代码，能帮你规避很多归钱项目。</p><p>Solidity具有两个特点：</p><ol><li><p>基于对象：学会之后，能帮你挣钱找对象。</p></li><li><p>高级：不会solidity，在币圈显得很low。</p></li></ol><h2 id="h-remix" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">开发工具：remix</h2><p>本教程中，我会用remix来跑solidity合约。remix是以太坊官方推荐的智能合约开发IDE，适合新手，可以在浏览器中快速部署测试智能合约，你不需要在本地安装任何程序。</p><p>网址：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://remix.ethereum.org/">remix.ethereum.org</a></p><p>进入remix，我们可以看到最左边的菜单有三个按钮，分别对应文件（写代码的地方），编译（跑代码），部署（部署到链上）。我们点新建（Create New File）按钮，就可以创建一个空白的solidity合约。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3fc9edfdf6da63e68f3b245aa4cc9cc64dfbf562c503470dfaab618f54f60fb9.png" alt="remix面板" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">remix面板</figcaption></figure><p>remix面板</p><h2 id="h-solidity" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">第一个Solidity程序：</h2><h3 id="h-13" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">很简单，只有1行注释+3行代码：</h3><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract HelloWeb3{
    string public _string = &quot;Hello Web3!&quot;;}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">HelloWeb3</span></span>{
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> _string <span class="hljs-operator">=</span> <span class="hljs-string">"Hello Web3!"</span>;}
</code></pre><h3 id="h-solidity" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">我们拆开分析，学习solidity代码源文件的结构：</h3><p>1. 第1行是注释，会写一下这个代码所用的软件许可（license），这里用的是MIT license。如果不写许可，编译时会警告（warning），但程序可以运行。solidity的注释由“//”开头，后面跟注释的内容（不会被程序运行）。</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
</code></pre><p>2. 第2行声明源文件所用的solidity版本，因为不同版本语法有差别。</p><pre data-type="codeBlock" text="pragma solidity ^0.8.4;
"><code><span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>
</code></pre><p>这行代码意思是源文件将不允许低于 0.8.4 版本的编译器编译。</p><p>3. 第3-4行是合约部分，第3行创建合约（contract），并声明合约的名字 HelloWeb3。第4行是合约的内容，我们声明了一个string（字符串）变量_string，并给他赋值 “Hello Web3!”。</p><pre data-type="codeBlock" text="contract HelloWeb3{
    string public _string = &quot;Hello Web3!&quot;;}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">HelloWeb3</span></span>{
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> _string <span class="hljs-operator">=</span> <span class="hljs-string">"Hello Web3!"</span>;}
</code></pre><p>以后我们会更细的介绍solidity中的变量。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">编译并部署代码</h2><p>在编辑代码的页面，按ctrl+S就可以编译代码，非常方便。</p><p>编译好之后，点击左侧菜单的“部署”按钮，进入部署页面。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5691933de60d0c97fde253f8699bb70ad6f249aede76d21a489cd7281092bd17.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>在默认情况下，remix会用JS虚拟机来模拟以太坊链，运行智能合约，类似在浏览器里跑一条测试脸。并且remix会分配几个测试账户给你，每个里面有100 ETH（测试代币），可劲儿用。你点Deploy（黄色按钮），就可以部署咱们写好的合约了。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f8643a3ff725382641ed187eef1eb25a631ebf7db54e13616a25a472c04852f2.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>部署成功后，你会在下面看到名为HELLOWEB3的合约，点击_string，就能看到我们代码中写的 “Hello Web3!” 了。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>第一讲，我们简单介绍了solidity，remix工具，并完成了第一个solidity程序--HelloWeb3。接下来我们会介绍solidity的各种变量类型。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[ERC20水龙头合约]]></title>
            <link>https://paragraph.com/@dao4resilience/erc20</link>
            <guid>cwpecrpk1knHX8jAgoYQ</guid>
            <pubDate>Sun, 24 Apr 2022 05:19:50 GMT</pubDate>
            <description><![CDATA[这是一个简单的ERC20水龙头合约，主要是为了开发$PEOPLE水龙头。但这个合约可以用于任何ERC20代币。代码开源，随意使用： https://github.com/AmazingAng/ERC20Faucet什么是代币水龙头？当人渴的时候，就要去水龙头接水；当人想要免费代币的时候，就要去代币水龙头领。代币水龙头就是让用户免费领代币的网站/应用。 最早的代币水龙头是比特币（BTC）水龙头：现在BTC一枚要$44,000，但是在2010年，BTC的价格只有不到$0.1，并且持有人很少。为了扩大影响力，比特币社区的Gavin Andresen开发了BTC水龙头，让别人可以免费领BTC。撸羊毛大家都喜欢，当时就有很多人去撸，然后变成了BTC的信徒。BTC水龙头一共送出了超过19,700枚BTC，现在价值超过8亿美元！水龙头合约逻辑合约仅发放一种ERC20代币，在初始化的时候确认，存于tokenContact变量。 constructor(address _tokenContract) { tokenContract = _tokenContract; // set token co...]]></description>
            <content:encoded><![CDATA[<p>这是一个简单的ERC20水龙头合约，主要是为了开发$PEOPLE水龙头。但这个合约可以用于任何ERC20代币。代码开源，随意使用：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/AmazingAng/ERC20Faucet">https://github.com/AmazingAng/ERC20Faucet</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">什么是代币水龙头？</h2><p>当人渴的时候，就要去水龙头接水；当人想要免费代币的时候，就要去代币水龙头领。代币水龙头就是让用户免费领代币的网站/应用。</p><p>最早的代币水龙头是比特币（BTC）水龙头：现在BTC一枚要$44,000，但是在2010年，BTC的价格只有不到$0.1，并且持有人很少。为了扩大影响力，比特币社区的Gavin Andresen开发了BTC水龙头，让别人可以免费领BTC。撸羊毛大家都喜欢，当时就有很多人去撸，然后变成了BTC的信徒。BTC水龙头一共送出了超过19,700枚BTC，现在价值超过8亿美元！</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">水龙头合约逻辑</h2><ul><li><p>合约仅发放一种ERC20代币，在初始化的时候确认，存于tokenContact变量。</p><p><code>constructor(address _tokenContract) { tokenContract = _tokenContract; // set token contract }</code></p></li><li><p>用户调用<code>requestToken()</code>函数，就能得到空投代币，并且每个用户只能领一次。</p><p>`//allow users to call the requestTokens function to get tokensfunction requestTokens () external {require(requestedAddress[_msgSender()] == false, &quot;Can&apos;t Request Multiple Times!&quot;);IERC20 token = IERC20(tokenContract);require(token.balanceOf(address(this)) &gt;= amountAllowed, &quot;Faucet Empty!&quot;);</p><pre data-type="codeBlock" text="  token.transfer(_msgSender(), amountAllowed); // transfer token
  requestedAddress[_msgSender()] = true; // record requested 
    
  emit SendToken(_msgSender(), amountAllowed); // emit event
"><code>  token.<span class="hljs-built_in">transfer</span>(_msgSender(), amountAllowed); <span class="hljs-comment">// transfer token</span>
  requestedAddress[_msgSender()] <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>; <span class="hljs-comment">// record requested </span>
    
  <span class="hljs-keyword">emit</span> SendToken(_msgSender(), amountAllowed); <span class="hljs-comment">// emit event</span>
</code></pre><p>}`</p></li></ul><p>。</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[NFT及OpenSea交易背后的技术分享]]></title>
            <link>https://paragraph.com/@dao4resilience/nft-opensea</link>
            <guid>5nzYLLfbKEujwedHhhe9</guid>
            <pubDate>Sun, 24 Apr 2022 05:11:41 GMT</pubDate>
            <description><![CDATA[最近购入了NFT的各位可以看看你花大价钱购买的NFT的背后究竟是个什么东西，OpenSea是如何实现对NFT进行买卖交易的，这篇文章可以作为一个入门了解。学习研究OpenSea平台上交易NFT的技术细节并记录分享。 这篇文章鄙人会先讲一讲NFT的合约（ERC721）以及NFT在OpenSea上是如何实现交易的，这两部分的内容比较偏技术，希望可以耐心看完。NFT与ERC721NFT的名词定义就不说了，网上有很多资料大家可以自己搜索。 对于NFT来说，实际上就是Ethereum协会定义的一个规范，也就是ERC721，其作用是与Fungible Token的ERC20规范一样，通过这个规范来统一接口，使得在Token或者NFT上可以衍生出各种各样的DAPP生态，比如各种Swap、借贷等等各类DAPP。 NFT的ERC721规范是从ERC20衍生出来了，有很多相同方法，其下规范方法如下：// 查询NFT中某个owner拥有的数量 balanceOf(owner) // 查询NFT中某个编号属于的是哪个人，例如查询某个编号的猴属于哪个owner ownerOf(tokenId) // 返回...]]></description>
            <content:encoded><![CDATA[<p>最近购入了NFT的各位可以看看你花大价钱购买的NFT的背后究竟是个什么东西，OpenSea是如何实现对NFT进行买卖交易的，这篇文章可以作为一个入门了解。学习研究OpenSea平台上交易NFT的技术细节并记录分享。</p><p>这篇文章鄙人会先讲一讲<strong>NFT的合约（ERC721）以及NFT在</strong>OpenSea上是如何实现交易的，这两部分的内容比较偏技术，希望可以耐心看完。</p><h2 id="h-nfterc721" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">NFT与ERC721</h2><p>NFT的名词定义就不说了，网上有很多资料大家可以自己搜索。</p><p>对于NFT来说，实际上就是Ethereum协会定义的一个规范，也就是ERC721，其作用是与Fungible Token的ERC20规范一样，通过这个规范来统一接口，使得在Token或者NFT上可以衍生出各种各样的DAPP生态，比如各种Swap、借贷等等各类DAPP。</p><p>NFT的ERC721规范是从ERC20衍生出来了，有很多相同方法，其下<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721">规范</a>方法如下：</p><pre data-type="codeBlock" text="// 查询NFT中某个owner拥有的数量
balanceOf(owner)

// 查询NFT中某个编号属于的是哪个人，例如查询某个编号的猴属于哪个owner
ownerOf(tokenId)

// 返回NFT的名字
name()

// 返回NFT的符号
symbol()

// NFT总发行量
totalSupply()

// 返回某个NFT的URI，这个URI就是这个NFT的一切描述信息
tokenURI(tokenId)

// 按index序号返回该owner的所有持有NFT的编号
tokenOfOwnerByIndex(owner, index)

// 按index序号返回NFT的编号
tokenByIndex(index)

// 允许to这个地址可以转移他的tokenId编号的NFT（攸关你的NFT安全！）
approve(to, tokenId)

// 查询tokenId编号的NFT授权给了谁（查询谁可以转走你的NFT！）
getApproved(tokenId)

// 授权或者取消授权operator这个地址转移你这一Collection下的所有NFT（攸关你的NFT安全！）
setApprovalForAll(operator, approved)

// 查询某个operator是否有权转移某个owner的这一Collection中的所有NFT（查询某个地址是否可以转走你这个Collection的所有NFT！）
isApprovedForAll(owner, operator)

// 将from这个地址的tokenID编号的NFT转给to这个地址（需要授权才行）
transferFrom(from, to, tokenId)
"><code><span class="hljs-comment">// 查询NFT中某个owner拥有的数量</span>
<span class="hljs-built_in">balanceOf</span>(owner)

<span class="hljs-comment">// 查询NFT中某个编号属于的是哪个人，例如查询某个编号的猴属于哪个owner</span>
<span class="hljs-built_in">ownerOf</span>(tokenId)

<span class="hljs-comment">// 返回NFT的名字</span>
<span class="hljs-built_in">name</span>()

<span class="hljs-comment">// 返回NFT的符号</span>
<span class="hljs-built_in">symbol</span>()

<span class="hljs-comment">// NFT总发行量</span>
<span class="hljs-built_in">totalSupply</span>()

<span class="hljs-comment">// 返回某个NFT的URI，这个URI就是这个NFT的一切描述信息</span>
<span class="hljs-built_in">tokenURI</span>(tokenId)

<span class="hljs-comment">// 按index序号返回该owner的所有持有NFT的编号</span>
<span class="hljs-built_in">tokenOfOwnerByIndex</span>(owner, index)

<span class="hljs-comment">// 按index序号返回NFT的编号</span>
<span class="hljs-built_in">tokenByIndex</span>(index)

<span class="hljs-comment">// 允许to这个地址可以转移他的tokenId编号的NFT（攸关你的NFT安全！）</span>
<span class="hljs-built_in">approve</span>(to, tokenId)

<span class="hljs-comment">// 查询tokenId编号的NFT授权给了谁（查询谁可以转走你的NFT！）</span>
<span class="hljs-built_in">getApproved</span>(tokenId)

<span class="hljs-comment">// 授权或者取消授权operator这个地址转移你这一Collection下的所有NFT（攸关你的NFT安全！）</span>
<span class="hljs-built_in">setApprovalForAll</span>(operator, approved)

<span class="hljs-comment">// 查询某个operator是否有权转移某个owner的这一Collection中的所有NFT（查询某个地址是否可以转走你这个Collection的所有NFT！）</span>
<span class="hljs-built_in">isApprovedForAll</span>(owner, operator)

<span class="hljs-comment">// 将from这个地址的tokenID编号的NFT转给to这个地址（需要授权才行）</span>
<span class="hljs-built_in">transferFrom</span>(from, to, tokenId)
</code></pre><p>以上就是几个主要的ERC721规范的方法，虽然写了注释，但是我猜测大家还是一脸懵。</p><p>所以下面给大家通过例子来说明，从NFT的起源Mint开始。</p><h3 id="h-mint" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Mint铸造</h3><p>下面这段代码就是<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.openzeppelin.com/contracts/3.x/erc721#constructing_an_erc721_token_contract">OpenZeppelin</a>上一个简单的如何铸造NFT的代码：</p><pre data-type="codeBlock" text="// 铸造NFT的方法，只需要传入铸造给谁，再加上这个NFT的tokenURI即可
function awardItem(address player, string memory tokenURI)
    public
    returns (uint256)
{
    _tokenIds.increment();
    uint256 newTokenId = _tokenIds.current();

    // 将这个新的tokenId与player绑定
    _mint(player, newTokenId);

    // 将tokenURI与这个newTokenId绑定
    _setTokenURI(newTokenId, tokenURI);

    return newItemId;
}
"><code><span class="hljs-comment">// 铸造NFT的方法，只需要传入铸造给谁，再加上这个NFT的tokenURI即可</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">awardItem</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> player, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> tokenURI</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">uint256</span></span>)
</span>{
    _tokenIds.increment();
    <span class="hljs-keyword">uint256</span> newTokenId <span class="hljs-operator">=</span> _tokenIds.current();

    <span class="hljs-comment">// 将这个新的tokenId与player绑定</span>
    _mint(player, newTokenId);

    <span class="hljs-comment">// 将tokenURI与这个newTokenId绑定</span>
    _setTokenURI(newTokenId, tokenURI);

    <span class="hljs-keyword">return</span> newItemId;
}
</code></pre><p>铸造NFT实际上就是往NFT的合约里写入了两个信息：</p><ul><li><p><strong>tokenId及其owner</strong></p></li><li><p><strong>tokenId及其tokenURI</strong></p></li></ul><p>有人说NFT又有图片，又有各种属性，怎么这么简单了就铸造出来了呢？</p><p>没错，就这么简单！</p><p>接下来我们用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://opensea.io/collection/cryptoapes-official">CryptoApes</a>这个NFT举例给大家看看我们大家看到的各种眼花缭乱的NFT是怎么展示出来的？</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d95b4acccf0d08982f2c945549245157e4d947fbbb9f46ab380ae2fff9678f14.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><h3 id="h-play-with-nft-contract" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Play with NFT contract</h3><p>我们先来看看如何通过简单的几行代码查询CryptoApes NFT的基本信息：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/edbcd46d232de8b8adf24851eb50d2dc52f90166598839439d0e3cd3316fca1d.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>截图中可以看到，通过向CryptoApes合约调用上述ERC721规范的方法<strong>name()</strong>，<strong>symbol()</strong>，<strong>totalSupply()</strong>，就能拿到这个NFT的名称、符号和总数分别是：<strong>CryptoApes</strong>，<strong>CRAP</strong>，<strong>6969</strong>。</p><p>CryptoApes的<strong>合约地址</strong>以及<strong>NFT编号</strong>可以在OpenSea中的URL拿到，见下面截图：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c1d7fa25c627a4b9e1f9bd2a0bc89d1742f76f1ccd4ab0a01b7c24a9400dcbc8.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>可以看到OpenSea的NFT链接 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://opensea.io/assets/0x29714cafe792ef8b8c649451d13c89e21a0d7f5b/24">https://opensea.io/assets/0x29714cafe792ef8b8c649451d13c89e21a0d7f5b/24</a> ，assets后的第一个地址就是该NFT Collection的<strong>合约地址</strong>，合约地址后的数字就是该<strong>NFT编号</strong>。</p><p>下面我们还是通过几行简单代码调用合约，查询一下这只编号为24的CryptoApes的信息：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1865a89e630ce6e05b800eb17d6f0ab984ff95adc372271c66606d969f397426.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>可以看到我们通过ERC721规范中的**ownerOf()**方法，就能查到这只CryptoApes的拥有者（没错，就是本人）。</p><p>另外通过**balanceOf()**方法就能查到本人总共拥有了4只CryptoApes。</p><p>值得注意的是截图中的**tokenURI()**方法，通过它就能获取这个24号NFT的tokenURI。<strong>这个tokenURI非常重要！因为里面存储了关于这只猴的所有描述信息（Metadata）。</strong></p><p>24号猴的tokenURI是：ipfs://QmWGAFtzyzB6A6gYMnb6838hysHuT2rcV8B98Gmj4T4pyY/24.json，说明是存储在IPFS这个分布式存储上的json文件，我们继续通过这个IPFS的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ipfs.io/ipfs/QmWGAFtzyzB6A6gYMnb6838hysHuT2rcV8B98Gmj4T4pyY/24.json">网关地址</a>（<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ipfs.io/ipfs/QmWGAFtzyzB6A6gYMnb6838hysHuT2rcV8B98Gmj4T4pyY/24.json">https://ipfs.io/ipfs/QmWGAFtzyzB6A6gYMnb6838hysHuT2rcV8B98Gmj4T4pyY/24.json</a>）访问获取里面的内容：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ea923941dbba3301d900a2168dd27e7bdd3fdff4a64f9c33d425e28f633aa786.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>上面截图中就是这只24号猴的所有信息了，包括名字、图片、描述、编号、创建时间、还有背景颜色、毛颜色、嘴巴衣服等各种各样的属性。（大家点开大图就能看见）</p><p>其中的图片信息又指向了另外一个IPFS地址：ipfs://QmZFnUm3bjSyEPrvxEa3fR9eUxnkfQeLmPTzDhAmCWtbMZ/24.png，通过这个<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ipfs.io/ipfs/QmZFnUm3bjSyEPrvxEa3fR9eUxnkfQeLmPTzDhAmCWtbMZ/24.png">地址</a>（<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ipfs.io/ipfs/QmZFnUm3bjSyEPrvxEa3fR9eUxnkfQeLmPTzDhAmCWtbMZ/24.png">https://ipfs.io/ipfs/QmZFnUm3bjSyEPrvxEa3fR9eUxnkfQeLmPTzDhAmCWtbMZ/24.png</a>）就能看到24号猴的图片了，这也是OpenSea平台中给大家展示的图片样子。</p><p><strong>大家可以看到NFT的所有关键信息都是存在tokenURI这个链接中的，这个链接中的数据包括了你NFT的编号、属性、图片或视频，所以tokenURI这个链下数据能不能正常访问，会不会被篡改非常重要！！！</strong></p><p>这里CryptoApes的TokenURI用的是IPFS协议，IPFS协议可以保证不会被篡改。<strong>但如果是HTTP协议则有被篡改的可能，虽然使用HTTP协议可以通过checksum校验数据等方式验证，但这并不优雅，并且也不是ERC721的协议规范。</strong></p><p>这里插一句，鄙人原本打算写一篇介绍去中心化存储的文章的，<strong>因为感觉这个领域的重要性被大家严重忽视了，它也是区块链生态的基础设施，重要性并不比被热捧的各类公链项目低</strong>。使用IPFS协议存储内容虽然不会被篡改，但是IPFS还存在它自身的问题，这里就不展开了。</p><p>接着说，上面我们查到了编号24的猴的所有关键信息。那如果想把所有6969只CryptoApes的信息查找出来可以吗？</p><p>当然可以！使用上述ERC721规范中的“<strong>tokenByIndex()</strong>”方法进行遍历就行，有兴趣的朋友可以自行试试。</p><p>实际上OpenSea支持所有的ERC721的NFT的信息展示，就是通过上述方法去链上抓取数据，并在OpenSea自己的系统中来建立起来所有资源信息，最后通过Web的形式展现到大家面前，方便大家浏览。</p><p>这里需要吐槽的是OpenSea网站的robust及performance做得实在太烂了，经常动不动就挂掉，所以鄙人也正着手搞一个类似的产品，从blockchain上同步NFT数据，以及从OpenSea上同步交易数据，存储后按用户需求展示给用户，提供稳定、高效以及易用的浏览及查询服务。（做得好的话未来想象空间也很大，哈哈）</p><p>上面通过脚本调用合约给大家展示的都是“<strong>读</strong>”方法，“<strong>写</strong>”方法的话就是在代码中加载钱包，签名之后就可以了。（<strong>所以挺简单的吧，大家也别被所谓“科学家”这个名字唬住了。简单学习一下编程，再熟悉下合约的调用，就可以通过程序操作多个钱包，去薅羊毛还是干点别的事情就都可以了</strong>）</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">需要警惕的一些方法</h3><p>ERC721规范中有<strong>两个方法</strong>需要大家<strong>警惕</strong>一下，<strong>如果不小心也许就会丢掉你的NFT</strong>。</p><p>第一个需要警惕的方法是“<strong>approve(to, tokenId)</strong>”，这个方法是授权“<strong>to</strong>”这个地址有权利可以转走你这个“tokenId”的NFT。<strong>如果你在小狐狸中授权钓鱼网站调用了这个方法，最多会损失一个NFT。</strong></p><p>这个方法ERC20规范中也有，ERC20是授权“<strong>to</strong>”地址最多可以使用<strong>多少数量的token</strong>。鄙人的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://redenvelop.app/">去中心化红包项目</a>，就是在你发红包之前要求你使用“<strong>approve</strong>”授权一下，好让红包合约可以扣你的token并装入红包中给大家去抢。各类Swap的DAPP也是一样，需要先“<strong>approve</strong>”才可以进行swap。</p><p>第二个需要警惕的方法是“<strong>setApprovalForAll(operator, approved)</strong>”，这个方法是授权“<strong>operator</strong>”这个地址可以转走你在这个Collection下所有的NFT。<strong>如果你在小狐狸中授权钓鱼网站调用了这个方法，则可能丢失这个Collection下的所有NFT</strong>。</p><p>在OpenSea平台中，如果我们“<strong>Sell</strong>”一个NFT，小狐狸就会弹出这个方法的授权，见下面截图。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9be7b9cc402c9f4b1f8f06d9da31183b75b161233a9bb1ad1a20d20fd32f0f9e.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><strong>注意红框里的内容</strong>，就是向CryptoApes的<strong>合约地址</strong>调用“<strong>setApprovalForAll(operator, approved)</strong>”这个方法。授权之后，如果有人出价购买，OpenSea的交易合约则可以把你的NFT直接转给买家，转给买家这个操作调用的是ERC721规范中的方法“<strong>transferFrom(from, to, tokenId)</strong>”，from是你自己，to是买家，tokenId是这个NFT的编号。</p><p><strong>所以大家在小狐狸中进行授权的时候，一定有安全意识，看见approve或者setApprovalForAll方法时一定要注意是不是正规的网站，合约地址是不是正确的地址。如果不小心授权错了，你的NFT就可能被转走。</strong></p><p>也许有人会问：我只卖出一个NFT，为什么不单独授权这一个出售的NFT（approve），而是要授权所有的NFT呢（setApproveForAll）？</p><p>答：OpenSea的解释是可以省gas费，一次授权后，再次卖出其余NFT的时就不需要因为再次授权而付更多的gas费用了。</p><p>也许有人会问：那我授权给了OpenSea所有NFT的转移权限，那OpenSea平台会不会悄悄转走我的NFT呢？</p><p>关于这个这个问题可以继续看后面。</p><h2 id="h-opensea" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">OpenSea买卖流程的背后</h2><h3 id="h-nft" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">卖出NFT的背后</h3><p>下图是在你卖出NFT时候的弹窗截图：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/71160ab49289230294509afb69f755aa15add1c8d8fa433b652ffd1d74a10c5d.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>在OpenSea上进行卖出操作时，会弹出窗口第一步让你先初始化钱包（这个是一次性操作），在你付了gas费之后，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xa5409ec958c83c3f309868babaca7c86dcb077c1#code">OpenSea的Registry合约</a>会帮你创建一个钱包合约（<strong>实际上就是一个Proxy合约</strong>），大家如果在etherscan上查的话，可以看到一个RegisterProxy的操作，实际上调用的就是下面代码去创建了一个属于你个人钱包合约：</p><pre data-type="codeBlock" text="function registerProxy()
        public
        returns (OwnableDelegateProxy proxy)
    {
        require(proxies[msg.sender] == address(0));
        // 创建一个新的代理合约
        proxy = new OwnableDelegateProxy(msg.sender, delegateProxyImplementation, abi.encodeWithSignature(&quot;initialize(address,address)&quot;, msg.sender, address(this)));
        proxies[msg.sender] = proxy;
        return proxy;
    }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerProxy</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">returns</span></span> (<span class="hljs-params">OwnableDelegateProxy proxy</span>)
    </span>{
        <span class="hljs-built_in">require</span>(proxies[<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-keyword">address</span>(<span class="hljs-number">0</span>));
        <span class="hljs-comment">// 创建一个新的代理合约</span>
        proxy <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> OwnableDelegateProxy(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, delegateProxyImplementation, <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSignature</span>(<span class="hljs-string">"initialize(address,address)"</span>, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)));
        proxies[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> proxy;
        <span class="hljs-keyword">return</span> proxy;
    }
</code></pre><p>为什么需要创建这个合约呢？主要目的是为了安全，因为第二步“Approve this item for sale”时需要授权一个合约地址可以转移你的NFT（也就是上一段落说的<strong>setApprovalForAll</strong>方法），<strong>授权可以转移你NFT的地址，就是“Initialize your wallet”这一步所创建的钱包合约地址。</strong></p><p><strong>也就是说OpenSea不能直接转走你的NFT，只有你在OpenSea上初次创建的这个钱包地址才可以转走。</strong></p><p>在创建完钱包以及授权NFT之后，如果你去挂出同个Collection下的NFT进行卖出时，不需要额外的手续费，OpenSea仅仅验证你的签名就可以挂出卖单了，这点就是被OpenSea宣传的“<strong>gas-free listing</strong>”。</p><p>所以讲到这里，也许有人又注意到了一个问题：<strong>为什么后续挂出卖单仅仅只需要签名，不需要 transaction呢？OpenSea的卖单信息只是存到它自己的中心化服务器上，没有存在链上吗？</strong></p><p>答：<strong>是的，卖单信息只存在OpenSea的中心化服务器上，没有上链</strong>，具体可以参考<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.opensea.io/reference/terminology">这里</a>OpenSea的解释。</p><blockquote><p>On OpenSea, most actions are <strong>off-chain</strong>, meaning they generate orders that are stored in the our system and can be fulfilled by a matching order from another user.</p></blockquote><blockquote><p>When a user lists an item for sale, they simply sign their intent to swap the item for payment. This intent is stored in the OpenSea system as a <strong>sell order</strong>, and does not create a transaction.</p></blockquote><p><strong>关于很多评论说OpenSea太过中心化这个问题，之后鄙人也会简单谈谈自己观点。</strong></p><h3 id="h-nft" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">买入NFT的背后</h3><p>在OpenSea买入的时候，买卖的撮合其实是发生在OpenSea的中心化系统中，匹配好订单后让用户调用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x7be8076f4ea4a4ad08075c2508e481d6c946d12b#code">OpenSea交易合约地址</a>的<strong>AtomicMatch</strong>方法完成交易，这个方法里完成了一系列复杂操作，这里就不展开讨论了。</p><p><strong>也就是说成交之后，成交订单的信息会上链，毕竟这涉及到了Token和NFT的转移。</strong></p><p>OpenSea使用的交易合约应该是<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wyvernprotocol.com/">Wyvern</a>协议，实际上如果整个交易过程中只有最终交易数据才上链的话，可以不用这么复杂的合约，不过这应该是历史遗留包袱。</p><p>大家只需要知道这次买入交易成功的背后，会完成这两个步骤：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a726df3bd2d02db00cadcb83e38477b7e952948deed0943010dcf03a5c6c67b9.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><ol><li><p><strong>把NFT转给买家</strong></p></li><li><p><strong>把买家的钱转给卖家和OpenSea</strong>（平台手续费）</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">其它交易方式</h3><p>关于Offer报价以及Auction拍卖的交易方式鄙人没有试过，不过我认为跟Listing交易一样，在offer或者bid之前<strong>approve</strong>一下你的WETH就行，<strong>而auction拍卖订单和Offer报价订单的创建与撮合也应该还是通过OpenSea的中心化系统完成。</strong></p><h3 id="h-opensea" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">关于OpenSea太过中心化的评论</h3><p>鄙人在网络上看到很多声讨OpenSea太过中心化的评论，这段时间开始买NFT对OpenSea进行研究了之后才了解这些评论的根本原因，实际上OpenSea也是基于当时条件限制下的产物。</p><p>鄙人这里也为OpenSea的部分中心化技术方案辩驳几句：</p><ul><li><p>首先它降低了手续费，贵族链绝非浪得虚名的。如果所有数据信息上链那必然会导致交易的成本上升，更加提高了用户的交易门槛。（如果所有信息上链的话，我想唯一的好处就是鄙人不会每天收到批量offer的邮件通知了，毕竟每一次offer都需要燃烧gas）</p></li><li><p>其次Etheruem的performance不足以承载大量的transaction，如果每次挂出卖单、修改卖单价格、每次Offer价格等所有信息都上链的话，那会更加进一步推高Ethereum的gas费用。</p></li><li><p>最后关键信息上链也一定程度上确保了交易的安全和公开。比如你NFT和WETH的授权是给到你的钱包合约的，OpenSea不直接触碰。OpenSea也开放了它的API，所有未上链的订单数据可以通过API获取，好心人可以通过API拿到数据后和链上最终的交易数据进行比对验证。（不过我猜测不存在这样的好心人吧）</p></li></ul><p><strong>最近这一年公链的迅猛发展，gas费过高以及性能的问题以及得到了极大的缓解。如果OpenSea不思进取、不做改进的话绝对会被大多数用户抛弃，最后发展成为贵族NFT市场，比如前几天LooksRare平台也来空投抢用户了。当然这个问题也是Ethereum需要面对的。</strong></p><p>关于OpenSea的交易背后本人有两个问题：</p><ol><li><p>本人发现在OpenSea上Cancel一个Listing订单，也是需要写入区块链的。但我觉得直接在OpenSea的中心化系统中直接Cancel就好了，毕竟Listing的订单信息也没上链啊，为什么Cancel Listing订单的操作需要上链呢？这gas费不是白白浪费掉了吗？</p></li><li><p>Listing订单价格的修改，只能往低了修改，不能改高了，如果需要报更高价格的话需要先Cancel Listing订单，然后再重新挂一个，而Cancel的时候又得上链浪费一笔gas费用。同样Listing订单数据没在链上，为什么List price都可以往低了改，而不能直接改高呢？</p></li></ol><p>这两个问题本人没太想明白为什么OpenSea这样做，因为个人觉得在中心化系统上取消订单和随意修改订单价格不是很简单的事情吗？</p><p>个人不怀好意的猜测是OpenSea系统做得太烂，不愿意让用户随意取消订单和修改价格，因为这样会导致系统的不稳定。所以Cancel Listing这个操作硬要上链，让用户消耗gas，这样用户在挂卖单的时候就需要慎重考虑要不要挂，以及要挂的价格了。</p><p><strong>也希望知道原因的朋友不吝赐教！</strong></p><h3 id="h-2022-01-25" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">上面两个问题的解答（2022-01-25更新）</h3><p>昨天opensea出现了一个问题，很多人高价的NFT以特别低的价格被出售了，损失惨重。本人今天研究了下，顺便这个问题同步更新一下。</p><p>在OpenSea官网中有这么<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://support.opensea.io/hc/en-us/articles/4410153816723-How-can-I-cancel-or-lower-the-price-of-NFT-listings-">一篇文章</a>教用户如何取消订单的，注意截图里的这段话：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cef83baf61eff45a23bbecab14a4d8f299be674579c60f54dfe88ee376e87347.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>大意是如果你挂出过你NFT的卖单，这时你又把你NFT转到另外一个钱包地址，这种情况不会自动取消你NFT的卖单，所以你应该在转走你NFT之前先把卖单取消掉。</p><p>而这次出现损失的用户大部分就是这个原因了。例如几个月前， 用户A把他的NFT挂出10ETH的价格出售，当时价格未成交，这时用户A未取消卖单就直接把NFT转到另外的钱包地址（可能是为了安全转到冷钱包）去了。</p><p>过了几个月后发现市场价格不错，就把NFT又转回到之前的钱包地址，准备挂单出售，但当刚转回成功的时候就发现NFT被以之前的卖单价格（10ETH）出售了，当前市场价可能是80ETH，造成了严重损失。</p><p>之所以可以被10ETH的卖单价格出售，就是因为之前的卖单信息被人盯上了，所以在转回NFT的时候被人立马以10ETH的低价购买成功。</p><p><strong>合约分析</strong></p><p>上面说过，opensea的卖单信息是中心化存储的，这个卖单信息中包括了你NFT的信息、价格以及签名，同时这个卖单信息可以通过opensea的API查询到。</p><p>科学家可以通过opensea的API找到一些价值比较高的NFT的卖单信息，同时监控该NFT是否又被转回到与该卖单信息一致的钱包地址，当发现转回时，科学家调用前文所述的atomicMatch这个方法即可完成交易。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f5a4fc48652f2c1d843abae60f70de1fcce4837f75647b02e6ba979eedb8b972.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>截图中可以看到，atomicMatch方法里有个requireValidOrder内部方法，该方法会去校验这个卖单信息是否有效，签名是否正确等。这个卖单信息是存在opensea上并通过API开放出来的，任何人拿到这个卖单信息并且提供能与之匹配的买单，就能完成交易。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e5a6410760f6ddc992e0693bf8d8691906d46620094c2e753c3110690b84d276.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>上面这个截图是购买时，requireValidOrder方法里判断卖单信息是否合法的实现，可以看到有一步是通过cancelledOrFinalized这个map类型变量来判断的。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/94d96e71a298e9bec8f49d654cf1fd9308180d8ae7c0ab6955c9fcbb6ec94205.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>而之前说的取消订单是一个上链操作，就是通过设置cancelledOrFinalized这个状态来控制卖单信息失效的。</p><p>总而言之，就是一个用户之前挂过低价卖单，后来NFT转走了就再也没管过这个卖单，而这个高价值NFT的低价卖单信息（包含签名）又被人盯上了，所以当NFT又转回来的时候就可以以之前的低价成交。</p><p><strong>本人之所以有上一节的两个问题，完全是因为没看过opensea的API，没想到opensea这么open，把卖单的签名信息也开放出来了，任何第三方拿到这个卖单信息都可以自己下单进行NFT的购买。</strong></p><p><strong>如何避免损失？</strong></p><p>对于opensea来说，现在这个交易合约是n年前的产物，我觉得可以重新设计一个，做好迁移过度相关的工作，但这个工作量会比较高，费时比较长。我猜opensea已经在计划了，因为opensea上polygon链的NFT已经不再是当前的交易方式。</p><p>另外对于opensea来说更简单粗暴的方式就是卖单信息中的签名别开放出去，为了安全更加中心化一些，防止不知情的用户造成损失。</p><p>对于用户来说，记得取消卖单，虽然费一些gas费吧。另外如果曾经挂过卖单且没取消过的钱包，就别把NFT往回转了， 就用新钱包直接卖吧。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">最后的最后</h2><p>最近因为PhantaBear的关系对NFT着迷了，鄙人着迷到什么程度呢？<strong>我媳妇告诉我，我小学2年级的孩子都在问她同学有没有爆炸头熊的NFT了。。。</strong></p><p>着迷到已经有点动摇BTC Hodl决心，盘算着是不是拿点BTC去换一个CryptoPunks的程度，毕竟有NFT第一的故事加持，非常想搞一枚。</p><h3 id="h-nft" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">关于NFT的胡思乱想</h3><p>社会的经济活动实际上就是<strong>钱和资产相互兑换的过程</strong>。比如用钱购买衣服、鞋、车、房就是用<strong>钱购买了资产</strong>。同样也可以变卖衣服、鞋、车、房将<strong>资产换取钱</strong>，这就是基本的经济活动。这个交易过程的顺利执行其实是通过<strong>合同</strong>保证的（买衣服、鞋这类小商品虽然没有实体合同，但实际上存在虚拟合同），而<strong>社会法律</strong>强制确保了<strong>合同</strong>可以按预期执行（你要是收了钱但没给我东西，我就可以起诉你）。</p><p>你会看到在区块链的世界中是一模一样的，<strong>BTC、ETH</strong>等各种币就是钱，而玲琅满目的<strong>NFT</strong>就是个各类资产。<strong>crypto可以购买NFT</strong>，<strong>NFT可以变卖换成crypto</strong>，这样就形成了未来元宇宙中的经济活动。交易通过<strong>智能合约</strong>来完成，而<strong>公链的共识机制</strong>又确保了智能合约能够以预期方式执行。</p><p><strong>你会发现区块链行业的发展与现实社会极其相似，NFT的出现也许不是偶然吧。</strong></p><p>不过比较有意思的是，人类社会最早的经济活动只是以物换物，例如我用羊去换你的牛，因为以物换物的不便才出现了货币作为交换的媒介。所以现实社会是<strong>先出现资产，再出现货币</strong>。但区块链行业中先出现的是各类加密货币，然后才出现了代表资产的NFT，<strong>与现实社会相反</strong>。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">投资建议声明</h3><p>以上文章内容全是鄙人胡说八道，不作为任何投资建议。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"></h3>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[科学家NFT批量抢购 | Solidity by Example]]></title>
            <link>https://paragraph.com/@dao4resilience/nft-solidity-by-example</link>
            <guid>SbdIecsOifCuYxi1kk9a</guid>
            <pubDate>Sun, 24 Apr 2022 03:24:17 GMT</pubDate>
            <description><![CDATA[// SPDX-License-Identifier: MIT pragmasolidity ^0.8.1; // 接口合约 interfaceIERC721 { // 总量 functiontotalSupply() external view returns (uint); // 铸造方法 functionmint(uint amount) externalpayable; // 发送方法 functiontransferFrom( address from, address to, uint tokenId ) external; } // 铸造合约 contractERC721Mint { // 构造函数(nft合约地址, 归集地址) constructor(addressERC721, address owner) payable { // 获取总量 uint t = IERC721(ERC721).totalSupply(); // 铸造(0.05购买总价)(5购买数量) IERC721(ERC721).mint{value: 0.05ether}(5); // 归集...]]></description>
            <content:encoded><![CDATA[<pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragmasolidity ^0.8.1;


    // 接口合约
interfaceIERC721 {
    // 总量
    functiontotalSupply() external view returns (uint);


    // 铸造方法
    functionmint(uint amount) externalpayable;


    // 发送方法
functiontransferFrom(
  address from,
  address to,
  uint tokenId
  ) external;
}


// 铸造合约
contractERC721Mint {
  // 构造函数(nft合约地址, 归集地址)
  constructor(addressERC721, address owner) payable {
  // 获取总量
  uint t = IERC721(ERC721).totalSupply();
// 铸造(0.05购买总价)(5购买数量)
IERC721(ERC721).mint{value: 0.05ether}(5);
// 归集
for (uint i = 1; i &lt;= 5; i++) {
  // 发送操作,(当前合约地址,归集地址,tokenId)
  IERC721(ERC721).transferFrom(address(this), owner, t + i);
}
// 自毁(收款地址,归集地址)
selfdestruct(payable(owner));
}
}


// 工厂合约
contractMintFactory {
// 所有者地址
address owner;


constructor() {
// 所有者 = 合约部署者
 owner = msg.sender;
}


// 部署方法,(NFT合约地址,抢购数量)
functiondeploy(addressERC721, uint count) publicpayable {
// 用抢购数量进行循环
for (uint i; i &lt; count; i++) {
  // 部署合约(抢购总价)(NFT合约地址,所有者地址)
  new ERC721Mint{value: 0.05ether}(ERC721, owner);
 }
}
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
pragmasolidity <span class="hljs-operator">^</span><span class="hljs-number">0</span><span class="hljs-number">.8</span><span class="hljs-number">.1</span>;


    <span class="hljs-comment">// 接口合约</span>
interfaceIERC721 {
    <span class="hljs-comment">// 总量</span>
    functiontotalSupply() <span class="hljs-keyword">external</span> <span class="hljs-keyword">view</span> <span class="hljs-keyword">returns</span> (<span class="hljs-keyword">uint</span>);


    <span class="hljs-comment">// 铸造方法</span>
    functionmint(<span class="hljs-keyword">uint</span> amount) externalpayable;


    <span class="hljs-comment">// 发送方法</span>
functiontransferFrom(
  <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> tokenId
  ) <span class="hljs-keyword">external</span>;
}


<span class="hljs-comment">// 铸造合约</span>
contractERC721Mint {
  <span class="hljs-comment">// 构造函数(nft合约地址, 归集地址)</span>
  <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">addressERC721, <span class="hljs-keyword">address</span> owner</span>) <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
  <span class="hljs-comment">// 获取总量</span>
  <span class="hljs-keyword">uint</span> t <span class="hljs-operator">=</span> IERC721(ERC721).totalSupply();
<span class="hljs-comment">// 铸造(0.05购买总价)(5购买数量)</span>
IERC721(ERC721).mint{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span>.05ether}(<span class="hljs-number">5</span>);
<span class="hljs-comment">// 归集</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; i <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">5</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
  <span class="hljs-comment">// 发送操作,(当前合约地址,归集地址,tokenId)</span>
  IERC721(ERC721).transferFrom(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), owner, t <span class="hljs-operator">+</span> i);
}
<span class="hljs-comment">// 自毁(收款地址,归集地址)</span>
<span class="hljs-built_in">selfdestruct</span>(<span class="hljs-keyword">payable</span>(owner));
}
}


<span class="hljs-comment">// 工厂合约</span>
contractMintFactory {
<span class="hljs-comment">// 所有者地址</span>
<span class="hljs-keyword">address</span> owner;


<span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-comment">// 所有者 = 合约部署者</span>
 owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
}


<span class="hljs-comment">// 部署方法,(NFT合约地址,抢购数量)</span>
functiondeploy(addressERC721, <span class="hljs-keyword">uint</span> count) publicpayable {
<span class="hljs-comment">// 用抢购数量进行循环</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i; i <span class="hljs-operator">&#x3C;</span> count; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
  <span class="hljs-comment">// 部署合约(抢购总价)(NFT合约地址,所有者地址)</span>
  <span class="hljs-keyword">new</span> ERC721Mint{<span class="hljs-built_in">value</span>: <span class="hljs-number">0</span>.05ether}(ERC721, owner);
 }
}
}
</code></pre><p>Try on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://remix.ethereum.org/">Remix</a></p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[毛泽东评《水浒》]]></title>
            <link>https://paragraph.com/@dao4resilience/1y7cyDvLu1Id1Y0hHdWV</link>
            <guid>1y7cyDvLu1Id1Y0hHdWV</guid>
            <pubDate>Sat, 23 Apr 2022 13:06:57 GMT</pubDate>
            <description><![CDATA[1975年8月14日毛泽东与身边工作人员谈对中国古典小说《水浒》的看法，说：“《水浒》这部书，好就好在投降。做反面教材，使人民都知道投降派。《水浒》只反贪官，不反皇帝。屏晁盖于一百零八人之外。宋江投降，搞修正主义，把晁的聚义厅改为忠义堂，让人招安了。”1957年3月在南京党员干部会议上，他号召全党同志要有一种拼命精神，参加社会主义建设事业，他说：“我们要保持过去革命战争时期的那么一股劲，那么一股革命热情，那么一种拼命精神，把革命工作做到底。什么叫拼命？《水浒传》上有那么一位，叫拼命三郎石秀，就是那个‘拼命’。我们从前干革命，就是有一种拼命精神。”]]></description>
            <content:encoded><![CDATA[<p>1975年8月14日毛泽东与身边工作人员谈对中国古典小说《水浒》的看法，说：“《水浒》这部书，好就好在投降。做反面教材，使人民都知道投降派。《水浒》只反贪官，不反皇帝。屏晁盖于一百零八人之外。宋江投降，搞修正主义，把晁的聚义厅改为忠义堂，让人招安了。”</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/879049c1649fe06c278fa2de656a935de739abbb1e1f2fb2e5230f6d350a8e8b.png" alt="1957年3月在南京党员干部会议上，他号召全党同志要有一种拼命精神，参加社会主义建设事业，他说：“我们要保持过去革命战争时期的那么一股劲，那么一股革命热情，那么一种拼命精神，把革命工作做到底。什么叫拼命？《水浒传》上有那么一位，叫拼命三郎石秀，就是那个‘拼命’。我们从前干革命，就是有一种拼命精神。”" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">1957年3月在南京党员干部会议上，他号召全党同志要有一种拼命精神，参加社会主义建设事业，他说：“我们要保持过去革命战争时期的那么一股劲，那么一股革命热情，那么一种拼命精神，把革命工作做到底。什么叫拼命？《水浒传》上有那么一位，叫拼命三郎石秀，就是那个‘拼命’。我们从前干革命，就是有一种拼命精神。”</figcaption></figure>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
        <item>
            <title><![CDATA[《水浒传》108将的英文名]]></title>
            <link>https://paragraph.com/@dao4resilience/108</link>
            <guid>hCfpXGRlLdvxJfdPEp89</guid>
            <pubDate>Sat, 23 Apr 2022 08:58:44 GMT</pubDate>
            <description><![CDATA[《水浒传》的英译本，主要有赛珍珠（Pearl S. Buck）译《All Men Are Brothers》，1933年首版； 杰克逊（J·H·Jackson）译《Water Margin》，1937年首版； 沙博理（Sidney Shapiro)译《Outlaws of the Marsh》，1980年首版。 前两种都是七十回本，后一种为百回本。 三个版本在百八将绰号的翻译上，各有千秋，翻译界多有研究和讨论。 比如“及时雨”，赛译：The Opportune Rain，杰译：Welcome Rain，沙译：The Timely Rain。 “青面兽”，赛译和沙译都是The Blue-Faced Beast，杰译为Sallow Faced Brute。天罡1．及时雨宋江：The Opportune Rain & 黑三郎：Black-faced Third Brother 2．玉麒麟卢俊义：The Jade Ch&apos;i Lin 3．智多星吴用：The Great Intelligence 4．入云龙公孙胜：Dragon In The Clouds 5．大刀关胜：The Gr...]]></description>
            <content:encoded><![CDATA[<p>《水浒传》的英译本，主要有<strong>赛珍珠</strong>（Pearl S. Buck）译《All Men Are Brothers》，1933年首版；</p><p><strong>杰克逊</strong>（J·H·Jackson）译《Water Margin》，1937年首版；</p><p><strong>沙博理</strong>（Sidney Shapiro)译《Outlaws of the Marsh》，1980年首版。</p><p>前两种都是七十回本，后一种为百回本。</p><p>三个版本在百八将绰号的翻译上，各有千秋，翻译界多有研究和讨论。</p><p>比如“及时雨”，赛译：The Opportune Rain，杰译：Welcome Rain，沙译：The Timely Rain。</p><p>“青面兽”，赛译和沙译都是The Blue-Faced Beast，杰译为Sallow Faced Brute。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">天罡</h2><p>1．及时雨宋江：The Opportune Rain &amp; 黑三郎：Black-faced Third Brother</p><p>2．玉麒麟卢俊义：The Jade Ch&apos;i Lin</p><p>3．智多星吴用：The Great Intelligence</p><p>4．入云龙公孙胜：Dragon In The Clouds</p><p>5．大刀关胜：The Great Sword</p><p>6．豹子头林冲：The Leopard Headed</p><p>7．霹雳火秦明：The Fire In the Thunder Clap</p><p>8．双鞭呼延灼：The Double Clubs</p><p>9．小李广花荣：Little Li-Kuan</p><p>10．小旋风柴进：The Little Whirl Wind</p><p>11．扑天雕李应：The Eagle Who Smites The Heavens</p><p>12．美髯公朱仝：The Beautiful Bearded</p><p>13．花和尚鲁智深：The Tattooed Priest</p><p>14．行者武松：The Hairy Priest</p><p>15．双枪将董平：Warrior of The Two Spears</p><p>16．没羽箭张清：The Featherless Arrow</p><p>17．青面兽杨志：The Blue-Faced Beast</p><p>18．金枪将徐宁：The Wielder of the Golden Sword</p><p>19．急先锋索超：The Swift Vanguard</p><p>20．神行太保戴宗：The Magic Messenger</p><p>21．赤发鬼刘唐：The Redheaded Devil</p><p>22．黑旋风李逵：The Black Whirl-wind</p><p>23．九纹龙史进：The Nine Dragoned</p><p>24．没遮拦穆弘：He Whom No Obstacle Can Stay</p><p>25．插翅虎雷横：The Winged Tiger</p><p>26．混江龙李俊：The Dragon Who Roils Rivers</p><p>27．立地太岁阮小二：The God of Swift Death</p><p>28．船火儿张横：The Boatman</p><p>29．短命二郎阮小五：The Short Lived</p><p>30．浪里白条张顺：White Stripe In The Waves</p><p>31．活阎罗阮小七：The Fierce King of Devils</p><p>32．病关索杨雄：The Sick Kwan-SO</p><p>33．拼命三郎石秀：The One Who Heeds Not His Life</p><p>34．两头蛇解珍：The Double Headed Snake</p><p>35．双尾蝎解宝：The Double Tailed Scorpion</p><p>36．浪子燕青：The Prodigal</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">地煞</h2><p>37．神机军师朱武：The Wily Warrior</p><p>38．镇三山黄信：He Who Rules the Three Mountains</p><p>39．病尉迟孙立：The Sick Warrior</p><p>40．丑郡马宣赞：The Ugly Warrior</p><p>41．井木犴郝思文：The Guardian Star God</p><p>42．百胜将韩滔：Victor in A Hundred Battles</p><p>43．天目将彭玘：The Eye of Heaven</p><p>44．圣水将军单廷珪：Swift in Water</p><p>45．神火将军魏定国：Warrior of Fire</p><p>46．圣手书生萧让：The Magic Scribe</p><p>47．铁面孔目裴宣：The Iron Faced</p><p>48．摩云金翅欧鹏：Eagle In The Clouds</p><p>49．火眼狻猊邓飞：The Red Eyed Lion</p><p>50．锦毛虎燕顺：The Five-Hued Tiger</p><p>51．锦豹子杨林：The Five-Hued Leopard</p><p>52．轰天雷凌振：Thunder That Shaker The Heavens</p><p>53．神算子蒋敬：The God of Accounting</p><p>54．小温侯吕方：The lesser Duke</p><p>55．赛仁贵郭盛：He Who Is Like Jen-Kuei of Old</p><p>56．神医安道全：The Magic Physician</p><p>57．紫髯伯皇甫端：The Purple Bearded</p><p>58．矮脚虎王英：The Dwarf Tiger</p><p>59．一丈青扈三娘：The Ten Foot Green Snake</p><p>60．丧门神鲍旭：The God of Death</p><p>61．混世魔王樊瑞：The King of the Devils Who Roil Earth</p><p>62．毛头星孔明：The Curly Haired</p><p>63．独火星孔亮：The Lone Fire</p><p>64．八臂哪吒项充：The Eight Armed Lo-Chao</p><p>65．飞天大圣李衮：The Heaven Flying God</p><p>66．玉臂匠金大坚：The Jade Armed Warrior</p><p>67．铁笛仙马麟：The Magic Iron Flautist</p><p>68．出洞蛟童威：The Crocodile Out of The Hold</p><p>69．翻江蜃童猛：The Oyster That Turns The River Over</p><p>70．玉幡竿孟康：The Jade Banner Pole</p><p>71．通臂猿侯健：The Strong Armed Gorilla</p><p>72．跳涧虎陈达：The Gorge Leaping Tiger</p><p>73．白花蛇杨春：The White Spotted Snake</p><p>74．白面郎君郑天寿：The White Faced Goodman</p><p>75．九尾龟陶宗旺：The Nine Tailed Turtle</p><p>76．铁扇子宋清：The Iron Fan</p><p>77．铁叫子乐和：The Iron Whistle</p><p>78．花项虎龚旺：The Spotted Necked Tiger</p><p>79．中箭虎丁得孙：The Arrow Wounded Tiger</p><p>80．小遮拦穆春：The Lesser One Whom No Obstacle Can Stay</p><p>81．操刀鬼曹正：The Dagger Devil</p><p>82．云里金刚宋万：The Guardian God in the Clouds</p><p>83．摸着天杜迁：Eagle Who Flutters Against the Sky</p><p>84．病大虫薛永：The Sick Tiger</p><p>85．金眼彪施恩：The Gold Eyed Tiger Cub</p><p>86．打虎将李忠：The Warrior Who Wars Against Tiger</p><p>87．小霸王周通：The Little Tyrant</p><p>88．金钱豹子汤隆：The Gold Spotted Leopard</p><p>89．鬼脸儿杜兴：The Devil Faced</p><p>90．出林龙邹渊：The Dragon Out of The Wood</p><p>91．独角龙邹润：The One Horned Dragon</p><p>92．旱地忽律朱贵：The Dry Land Water Beast</p><p>93．笑面虎朱富：The Smiling Faced Tiger</p><p>94．铁臂膊蔡福：The Iron Armed</p><p>95．一枝花蔡庆：The Single Flower</p><p>96．催命判官李立：The Pursuing God of Death</p><p>97．青眼虎李云：The Blue-Eyed Tiger</p><p>98．没面目焦挺：The Faceless</p><p>99．石将军石勇：The Stone Warrior</p><p>100．小尉迟孙新：The Lesser Yu-Tse</p><p>101．母大虫顾大嫂：The Female Tiger</p><p>102．菜园子张清：The Gardener</p><p>103．母夜叉孙二娘：The Female Savage</p><p>104．活闪婆王定六：The Living Female</p><p>105．险道神郁保四：The Vanguard God</p><p>106．白日鼠白胜：Rat In The Daylight</p><p>107．鼓上蚤时迁：Flea On A Drum</p><p>108．金毛犬段景住：The Yellow Haired Dog</p><p>额外附送<strong>编外人员</strong>绰号三个：</p><p>109．托塔天王晁盖：The Pagoda Moving Heavenly King</p><p>110．镇关西：The Bully of Kuangsi</p><p>111．蒋门神：The God of the Gate Chiang</p>]]></content:encoded>
            <author>dao4resilience@newsletter.paragraph.com (DAO4Resilience)</author>
        </item>
    </channel>
</rss>