<?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>yueying007</title>
        <link>https://paragraph.com/@yueying007</link>
        <description>Blockchain development Chinese tutorial</description>
        <lastBuildDate>Thu, 25 Jun 2026 14:08:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>yueying007</title>
            <url>https://storage.googleapis.com/papyrus_images/f8d0519d449f75f49bed582866ba7b2a34716538c42f9e392424b06c5abe3c01.png</url>
            <link>https://paragraph.com/@yueying007</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[9月15日以太坊分叉网套利复盘]]></title>
            <link>https://paragraph.com/@yueying007/9-15</link>
            <guid>XnfERSyDp69wYo0OzrcF</guid>
            <pubDate>Sat, 17 Sep 2022 10:17:50 GMT</pubDate>
            <description><![CDATA[前期准备知道以太坊Merge之后要分叉出POW链是一个月前，大概8月中下旬，当时有两个团队准备分叉以太坊，一个是宝二爷的团队EthereumPow，另一个是Classzz团队的EthereumFair。当时预想到，分叉会把以太坊上的所有状态复制一遍，在发生分叉以后，由于除了分叉币ETHW/ETHF有潜在价值，其它币都没有价值，所以一定会有人或者机器人通过事先准备好的前端页面或者程序，将所有token都兑换为分叉币，这种兑换大概率发生在Uniswap/Sushiswap/Curve这些Dex上面，导致分叉币的价格出现巨大波动，这时不同的Dex之间会出现巨大价差。 套利机器人分为两部分，一个是部署在以太坊上的Solidity合约，这个没有任何变动，因为分叉后合约也会复制一份，另一个是本地的监控机器人，代码在原来的基础上重做了一个分支，做了轻微改动： (1) 套利的目标token从原来的任意token变为只要WETH (2)只监控Curve/Uniswap/Suhiswap/Balancer上包含WETH(数量在10个以上)的池子 (3)为了在与其它机器人的竞争中占得先机，把初始的ga...]]></description>
            <content:encoded><![CDATA[<ol><li><p>前期准备</p></li></ol><p>知道以太坊Merge之后要分叉出POW链是一个月前，大概8月中下旬，当时有两个团队准备分叉以太坊，一个是宝二爷的团队EthereumPow，另一个是Classzz团队的EthereumFair。当时预想到，分叉会把以太坊上的所有状态复制一遍，在发生分叉以后，由于除了分叉币ETHW/ETHF有潜在价值，其它币都没有价值，所以一定会有人或者机器人通过事先准备好的前端页面或者程序，将所有token都兑换为分叉币，这种兑换大概率发生在Uniswap/Sushiswap/Curve这些Dex上面，导致分叉币的价格出现巨大波动，这时不同的Dex之间会出现巨大价差。</p><p>套利机器人分为两部分，一个是部署在以太坊上的Solidity合约，这个没有任何变动，因为分叉后合约也会复制一份，另一个是本地的监控机器人，代码在原来的基础上重做了一个分支，做了轻微改动：</p><p>(1) 套利的目标token从原来的任意token变为只要WETH</p><p>(2)只监控Curve/Uniswap/Suhiswap/Balancer上包含WETH(数量在10个以上)的池子</p><p>(3)为了在与其它机器人的竞争中占得先机，把初始的gas_price设置为基础gas_price+50gwei, 在钱包里放0.5个ETH作为支付初始gas的本金</p><p>(4)在发送交易的字段里加入chainId，防止transaction被拿到以太坊主网上进行重放攻击</p><p>(5)准备两套程序部署在美东的服务器上面，把RPCendpoint作为配置参数，等待官方公布时再更改</p><p>2. 插曲</p><p>9月初的时候，EthereumPow团队宣布冻结所有包含WETH数额大的池子，导致套利机器人直接失效，但后来又宣布不冻结，虚惊一场。</p><p>3. EthereumFair上线</p><p>9月15日下午，以太坊成功Merge，EthereumFair团队在16:00左右率先上线了分叉网并公布了RPC endpoint，测试了一下连接正常，但是区块高度一直不更新，官方说是矿工节点还没同步，就一直等到19:00点左右开始正常出块，于是改好配置文件上线机器人。</p><p>上线过程比预想的顺利，看了交易日志，第一个区块就获利2000个ETF，后面一直运行比较丝滑，看了一下每一笔交易的排名，基本上都在区块的前两名，说明这个链的竞争不是很激烈。由于官方给的区块链浏览器打不开，所以没法看到套利合约总共赚了多少个WETH，所以在本地写了个小程序监控合约里的WETH数量, 大概3000个的时候，暂停了一下程序，调用合约的WETHtoETH()换成ETH，然后调用turnOutETH取到钱包里。然后重启程序，这样钱包里就有足够的币支付后来的gas了。</p><p>过程中间偶尔会出现一些交易revert失败的情况，当时比较费解，因为Solidity代码里已经有避免出现revert的机制。由于不影响大局，所以暂时没有管它，准备EthereumPow的上线。</p><p>4. EthereumPow上线</p><p>EthereumPow的官推一直拖延到10:00才公布RPC endpoint, 公布的一瞬间就改了配置文件，上线。这次上线就不顺利了，过程磕磕绊绊。</p><p>首先从日志里了解到有一些交易成功了，然后就一直发送交易失败，日志滚动地飞快，我的脑袋也开始飞快运转。首先，检查一下套利合约里赚了多少个WETH，以及钱包里的0.5个分叉币还剩下多少? 我打开区块链浏览器，无法访问, 然后用刚才那个程序读取一下，也读不出来，连接超时!也就是说我本地无法访问它的RPC endpoint，换上VPN也不行。还有个办法，通过日志去分析，但这样就太耽误时间了。现在完全是黑箱状态。最后只有登录到服务器上写个小程序，这下读出来了，赚了500个WETH，应该是头几笔交易赚的。</p><p>程序还在报错，一直发送交易失败，想了几分钟，大概想明白了: 因为我的程序每发送一笔交易，会用web3.getTransactionReceipt获取交易结果(成功/失败), 如果过去两个区块还获取不到结果，就直接跳过去监控下一次交易机会。因为开盘几分钟的基础gas_price很大，所以我的gas费(基础gas_price+50gwei)也很大，这时由于网络拥堵gas_price持续增加到我的gas费之上，我的这笔交易就变成pending状态了，后来基础gas_price降下来了但是这笔交易不知什么原因还是pending状态，然后第二笔交易报进去使用的是同样的nonce，gas费还比上一笔交易低，这是不允许的(要replace上一笔交易必须gas费要更高)，所以就一直报单不成功。找到问题之后立马暂停程序，把gas费修改为基础gas_price+200gwei，重启，后面的交易就成功了。</p><p>这时，监控套利合约里的WETH数量，以每个区块几个～几十个的速度增长，因为刚才一顿操作浪费了5~10分钟，已经错过了开盘几分钟的黄金时间。</p><p>这样持续了2个小时左右，程序突然掉线，无法连接RPC，不知道什么原因。打开区块链浏览器，还在正常出块，所以不是节点的原因，我推断因为我的机器人访问次数过多触发了PRC节点的保护机制，把我的IP给禁了。这个时候再租一个新的VPS服务器，部署上线又很花时间，所以这边只有放弃，转到EthereumFair那边去。</p><p>5. bug fixed</p><p>EthereumFair的程序依然时不时发出revert的交易。首先，为了防止revert,在套利合约里，在开始套利之前，会先去检查套利机会是否还存在。如果这笔交易上链时排在了其它套利机器人的后面，套利机会就不存在了，这个时候就直接返回不做任何操作，避免了交易被revert白白浪费gas。为什么交易还会revert呢?</p><p>通过日志发现，失败的交易都来自于和UniswapV3的池子进行交互，这时我 想到，在检查UniswapV3的套利机会时，会调用UniswapV3Quote合约计算套利结果，UniswapV3Quote合约里有个try catch的操作，可能跟这个有关。没来的及想具体原因，我先调用套利合约，把事先检查套利机会的开关关掉，这样就不会和UniswapV3Quote发生交互。由于这个链基本没有竞争对手，所以不用担心被其它机器人抢的问题。重新部署上线，果然没有失败的交易了。</p><p>6. 尾声</p><p>过了2个小时之后，EthereumPow的RPC节点又可以访问了，重新上线。每个区块能赚0.1~1.5个WETH，竞争还是十分激烈，交易经常排在几十名开外。EthereumFair的竞争虽然没那么激烈，但是每个区块的奖励也只有个位数，池子在被逐渐掏空。再过10个小时之后，基本上两个链都挖不到币了。</p><p>7. 总结</p><p>经过一夜的奋战，收获13000个ETF和1500个ETHW，冲到交易所直接卖掉，获利4w美刀。</p><p>EthereumFair的整个过程比较丝滑，访问没有出现任何问题，比预想的顺利，抛开币本身的价值，这个团队的技术还是比较过硬的。</p><p>EthereumPow比较可惜，由于自己代码上的bug导致错过了开盘的黄金时间，中途又掉线两个小时，最后只吃了一个鱼尾。</p><p>整个过程给自己打80分，写文章记录一下，虽然这里面的经验教训以后也用不到了，毕竟以太坊分叉一辈子就这一次了，但是以后给别人吹牛逼可以说自己是在以太坊分叉日撸过币的男人了。</p>]]></content:encoded>
            <author>yueying007@newsletter.paragraph.com (yueying007)</author>
        </item>
        <item>
            <title><![CDATA[区块链开发课第六讲 智能合约开发(3)]]></title>
            <link>https://paragraph.com/@yueying007/3</link>
            <guid>lSxexFhxpim2QmuGD4eE</guid>
            <pubDate>Fri, 06 May 2022 09:42:15 GMT</pubDate>
            <description><![CDATA[这节课，我们继续完善SimpleArbi.sol，完成一笔完整的闪电贷套利操作。GitHub - yueying007/blockchainclassContribute to yueying007/blockchainclass development by creating an account on GitHub.https://github.com流程我们在智能合约中实现一个双边套利的操作：发起闪电贷WETH在Curve中把WETH兑换为USDT在Uniswap中把USDT兑换为WETH归还闪电贷WETH结构体在上一节中，我们已经分别实现了Curve和Uniswap的兑换操作，接下来，需要定义一个结构体，来保存一笔兑换的参数信息：struct SwapData { uint function_id; uint256 token_in_id; uint256 token_out_id; address token_in; address token_out; address pool; } 同时，在还款信息RepayData中加入一个SwapData类型的数组SwapDat...]]></description>
            <content:encoded><![CDATA[<p>这节课，我们继续完善SimpleArbi.sol，完成一笔完整的闪电贷套利操作。</p><div data-type="embedly" src="https://github.com/yueying007/blockchainclass.git" data="{&quot;provider_url&quot;:&quot;https://github.com&quot;,&quot;description&quot;:&quot;Contribute to yueying007/blockchainclass development by creating an account on GitHub.&quot;,&quot;title&quot;:&quot;GitHub - yueying007/blockchainclass&quot;,&quot;author_name&quot;:&quot;yueying007&quot;,&quot;thumbnail_width&quot;:1200,&quot;url&quot;:&quot;https://github.com/yueying007/blockchainclass&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png&quot;,&quot;author_url&quot;:&quot;https://github.com/yueying007&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;GitHub&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:600,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1200,&quot;height&quot;:600,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://github.com/yueying007/blockchainclass.git" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>GitHub - yueying007/blockchainclass</h2><p>Contribute to yueying007/blockchainclass development by creating an account on GitHub.</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://github.com</span></div><img src="https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png"/></div></a></div></div><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">流程</h2><p>我们在智能合约中实现一个双边套利的操作：</p><ol><li><p>发起闪电贷WETH</p></li><li><p>在Curve中把WETH兑换为USDT</p></li><li><p>在Uniswap中把USDT兑换为WETH</p></li><li><p>归还闪电贷WETH</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结构体</h2><p>在上一节中，我们已经分别实现了Curve和Uniswap的兑换操作，接下来，需要定义一个结构体，来保存一笔兑换的参数信息：</p><pre data-type="codeBlock" text="struct SwapData {
    uint function_id;        
    uint256 token_in_id;
    uint256 token_out_id;
    address token_in;
    address token_out;
    address pool;
}
"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">SwapData</span> {
    <span class="hljs-keyword">uint</span> function_id;        
    <span class="hljs-keyword">uint256</span> token_in_id;
    <span class="hljs-keyword">uint256</span> token_out_id;
    <span class="hljs-keyword">address</span> token_in;
    <span class="hljs-keyword">address</span> token_out;
    <span class="hljs-keyword">address</span> pool;
}
</code></pre><p>同时，在还款信息RepayData中加入一个SwapData类型的数组SwapData[]，用来告诉合约这些兑换参数的信息。并加入一个标志direct_repay用来区分获得闪电贷后的操作(直接归还/进行套利):</p><pre data-type="codeBlock" text="struct RepayData {
    SwapData[] swap_data;
    address repay_token;
    uint256 repay_amount;
    address recipient;
    bool direct_repay;
}
"><code>struct RepayData {
    SwapData<span class="hljs-section">[]</span> swap_data<span class="hljs-comment">;</span>
    address repay_token<span class="hljs-comment">;</span>
    uint256 repay_amount<span class="hljs-comment">;</span>
    address recipient<span class="hljs-comment">;</span>
    bool direct_repay<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>定义一个execute()函数作为套利的入口函数。</p><pre data-type="codeBlock" text="function execute(bytes[] memory data, uint256 amount_in) public onlyOwner Lock returns(uint256) {
    SwapData[] memory _swap_data = new SwapDataUnsupported embed;
    for (uint i = 0; i &lt;= data.length - 1; i++) {
        _swap_data[i] = abi.decode(data[i], (SwapData));
    }

    uint256 balance_before = IERC20(_swap_data[0].token_in).balanceOf(address(this));

    RepayData memory _repay_data = RepayData(_swap_data, _swap_data[0].token_in, amount_in, liquidityPool, false);
    ILiquidity(liquidityPool).borrow(_swap_data[0].token_in, amount_in,
                                     abi.encodeWithSignature(&quot;receiveLoan(bytes)&quot;, abi.encode(_repay_data)));

    uint256 balance_after = IERC20(_swap_data[0].token_in).balanceOf(address(this));

    require(balance_after &gt; balance_before, &quot;No Profit!&quot;);
    return balance_after - balance_before;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span>(<span class="hljs-params"><span class="hljs-keyword">bytes</span>[] <span class="hljs-keyword">memory</span> data, <span class="hljs-keyword">uint256</span> amount_in</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> <span class="hljs-title">Lock</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    SwapData[] <span class="hljs-keyword">memory</span> _swap_data <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> SwapDataUnsupported embed;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> data.<span class="hljs-built_in">length</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
        _swap_data[i] <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data[i], (SwapData));
    }

    <span class="hljs-keyword">uint256</span> balance_before <span class="hljs-operator">=</span> IERC20(_swap_data[<span class="hljs-number">0</span>].token_in).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));

    RepayData <span class="hljs-keyword">memory</span> _repay_data <span class="hljs-operator">=</span> RepayData(_swap_data, _swap_data[<span class="hljs-number">0</span>].token_in, amount_in, liquidityPool, <span class="hljs-literal">false</span>);
    ILiquidity(liquidityPool).borrow(_swap_data[<span class="hljs-number">0</span>].token_in, amount_in,
                                     <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSignature</span>(<span class="hljs-string">"receiveLoan(bytes)"</span>, <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(_repay_data)));

    <span class="hljs-keyword">uint256</span> balance_after <span class="hljs-operator">=</span> IERC20(_swap_data[<span class="hljs-number">0</span>].token_in).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));

    <span class="hljs-built_in">require</span>(balance_after <span class="hljs-operator">></span> balance_before, <span class="hljs-string">"No Profit!"</span>);
    <span class="hljs-keyword">return</span> balance_after <span class="hljs-operator">-</span> balance_before;
}
</code></pre><p>它接收两个参数：</p><p>data: bytes类型的数组，用来存放兑换参数信息</p><p>amount_in: 需要闪电贷WETH的数量</p><p>首先，定义一个临时变量_swap_data，从data中解析出兑换参数的列表。</p><p>然后记录一下合约中WETH的数量。</p><p>然后定义一个临时变量_repay_data来存储兑换参数、还款信息(还款token、还款数量和还款地址)以及direct_repay标志。这里把direct_repay设为false，表示在获得闪电贷后执行套利操作，而不是直接还款。</p><p>接着调用liquidityPool的borrow()函数发起一笔闪电贷，并告诉它接收闪电贷的回调函数名称及参数类型(receiveLoan/bytes)，以及参数值(把_repay_data加密为一段bytes)</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">回调函数</h2><p>发起闪电贷后，我们收到一笔WETH的贷款，并且回调函数receiveLoan()被调用:</p><pre data-type="codeBlock" text="// callback
function receiveLoan(bytes memory data) public {
    require(!lock, &quot;Locked&quot;);
    RepayData memory _repay_data = abi.decode(data, (RepayData));

    if (_repay_data.direct_repay) {
        IERC20(_repay_data.repay_token).safeTransfer(_repay_data.recipient, _repay_data.repay_amount);
    } else {
        uint _length = _repay_data.swap_data.length;
        uint256 out_amount;

        for (uint i = 0; i &lt;= _length - 1; i++) {
            out_amount = SwapBase(_repay_data.swap_data[i].pool,
                                  _repay_data.swap_data[i].function_id,
                                  i == 0 ? _repay_data.repay_amount : out_amount,
                                  _repay_data.swap_data[i].token_in_id,
                                  _repay_data.swap_data[i].token_out_id,
                                  _repay_data.swap_data[i].token_in,
                                  _repay_data.swap_data[i].token_out);
        }

                IERC20(_repay_data.repay_token).safeTransfer(_repay_data.recipient, _repay_data.repay_amount);
    }
}
"><code><span class="hljs-comment">// callback</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">receiveLoan</span>(<span class="hljs-params"><span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>lock, <span class="hljs-string">"Locked"</span>);
    RepayData <span class="hljs-keyword">memory</span> _repay_data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (RepayData));

    <span class="hljs-keyword">if</span> (_repay_data.direct_repay) {
        IERC20(_repay_data.repay_token).safeTransfer(_repay_data.recipient, _repay_data.repay_amount);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">uint</span> _length <span class="hljs-operator">=</span> _repay_data.swap_data.<span class="hljs-built_in">length</span>;
        <span class="hljs-keyword">uint256</span> out_amount;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span><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-operator">+</span>) {
            out_amount <span class="hljs-operator">=</span> SwapBase(_repay_data.swap_data[i].pool,
                                  _repay_data.swap_data[i].function_id,
                                  i <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> ? _repay_data.repay_amount : out_amount,
                                  _repay_data.swap_data[i].token_in_id,
                                  _repay_data.swap_data[i].token_out_id,
                                  _repay_data.swap_data[i].token_in,
                                  _repay_data.swap_data[i].token_out);
        }

                IERC20(_repay_data.repay_token).safeTransfer(_repay_data.recipient, _repay_data.repay_amount);
    }
}
</code></pre><p>首先，定义临时变量_repay_data，解析出data中的还款信息，如果direct_repay为true，直接还款(在Uniswap兑换的回调中用到)，否则进行如下的套利操作：</p><p>遍历_repay_data.swap_data数组，依次调用SwapBase()函数进行多笔兑换。最后进行还款操作。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">检查利润</h2><p>运行到这里，一笔闪电贷套利就完成了，最后我们在execute()函数的末尾要检查一下是否有利润：</p><pre data-type="codeBlock" text="uint256 balance_after = IERC20(_swap_data[0].token_in).balanceOf(address(this));

require(balance_after &gt; balance_before, &quot;No Profit!&quot;);
return balance_after - balance_before;
"><code><span class="hljs-keyword">uint256</span> balance_after <span class="hljs-operator">=</span> IERC20(_swap_data[<span class="hljs-number">0</span>].token_in).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));

<span class="hljs-built_in">require</span>(balance_after <span class="hljs-operator">></span> balance_before, <span class="hljs-string">"No Profit!"</span>);
<span class="hljs-keyword">return</span> balance_after <span class="hljs-operator">-</span> balance_before;
</code></pre><p>如果进行套利之后，合约中的WETH数量反而变少了，要进行revert回滚，因为不能允许一笔套利交易是亏损的。</p><p>如果有利润的话，最后返回利润的数值。这里为什么要返回利润值，不是多此一举吗？因为在生产环境下，在发出一笔交易前，我们需要先进行模拟，这里的模拟返回结果可以帮助我们进一步分析套利的成本、净利润等，从而调整gas价格策略，在后面的课程中会详细讲解。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">测试</h2><p>首先搭建mainet-fork测试环境(见第四讲):</p><pre data-type="codeBlock" text="ganache-cli --fork https://eth-mainnet.alchemyapi.io/v2/your_api_key
"><code>ganache<span class="hljs-operator">-</span>cli <span class="hljs-operator">-</span><span class="hljs-operator">-</span>fork https:<span class="hljs-comment">//eth-mainnet.alchemyapi.io/v2/your_api_key</span>
</code></pre><p>编译并部署合约:</p><pre data-type="codeBlock" text="truffle compile
truffle migrate
"><code>truffle <span class="hljs-built_in">compile</span>
truffle migrate
</code></pre><p>然后我们使用一个python脚本test_contract.py来进行测试。</p><p>开始测试前，建立一个python的虚拟环境:</p><pre data-type="codeBlock" text="sudo apt install python-virtualenv
cd ~/Projects/blockchainclass
virtualenv -p /usr/bin/python3.9 venv
"><code>sudo apt install python<span class="hljs-operator">-</span>virtualenv
cd <span class="hljs-operator">~</span><span class="hljs-operator">/</span>Projects<span class="hljs-operator">/</span>blockchainclass
virtualenv <span class="hljs-operator">-</span>p <span class="hljs-operator">/</span>usr<span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>python3<span class="hljs-number">.9</span> venv
</code></pre><p>安装web3.py</p><pre data-type="codeBlock" text="cd ~/Projects/blockchainclass
source venv/bin/activate
pip install web3
"><code>cd <span class="hljs-operator">~</span><span class="hljs-operator">/</span>Projects<span class="hljs-operator">/</span>blockchainclass
source venv<span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>activate
pip install web3
</code></pre><p>然后在test_contract.py中，填入合约部署地址，以及ganache-cli中生成的第一个账户地址和第一个密钥:</p><pre data-type="codeBlock" text="if __name__ == &apos;__main__&apos;:
    test_contract(contract_address=&apos;&apos;,
                  account=&apos;&apos;,
                  private_key=&apos;&apos;)
"><code>if <span class="hljs-attr">__name__</span> == <span class="hljs-string">'__main__'</span>:
    test_contract(<span class="hljs-attr">contract_address</span>=<span class="hljs-string">''</span>,
                  <span class="hljs-attr">account</span>=<span class="hljs-string">''</span>,
                  <span class="hljs-attr">private_key</span>=<span class="hljs-string">''</span>)
</code></pre><p>最后运行:</p><pre data-type="codeBlock" text="python test_contract.py
"><code>python test_contract.py
</code></pre><p>可以看出，最后返回的结果是revert No Profit! 表明套利交易运行到最后，检查利润小于0，交易回滚了。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>以上我们在智能合约中实现了一个简单的双边套利操作，同样，我们可以继续拓展，实现三边、四边、五边..套利，并且可以在Curve和Uniswap以外的Dex中进行套利。</p><p>在本例中，Curve在前，Uniswap在后，所以我们用闪电贷获取初始资金，如果是Uniswap在前，Curve在后，就可以使用Uniswap的闪电兑功能，即先在Uniswap中发起一笔swap(),然后在回调函数中在Curve中进行兑换。</p><p>这些都可以作为思考题，留给有心的读者进行深入研究。要提醒的是，这里的示例代码只是做演示用，请在进行深入研究并开发出有利可图的策略之前，不要把示例代码部署到生产环境下。</p><p>下一讲，我们将会构建一个python脚本，实时监控以太坊上的套利机会，并调用智能合约进行套利。</p><p><em>欢迎来即刻App与我互动，即刻账号: 月影007</em></p>]]></content:encoded>
            <author>yueying007@newsletter.paragraph.com (yueying007)</author>
        </item>
        <item>
            <title><![CDATA[区块链开发课第五讲 智能合约开发(2)]]></title>
            <link>https://paragraph.com/@yueying007/2</link>
            <guid>QcHrcgALoAIHGFmfSBaq</guid>
            <pubDate>Thu, 21 Apr 2022 05:31:43 GMT</pubDate>
            <description><![CDATA[这节课，我们继续完善SimpleArbi.sol，在合约内部完成Curve和Uniswap的swap操作。 github:GitHub - yueying007/blockchainclassContribute to yueying007/blockchainclass development by creating an account on GitHub.https://github.comDexCurve和Uniswap是以太坊上排名第一和第二的去中心化交易所(DEX)，它们分别都进行过几个版本的迭代升级，从最初的AMM(automatic market maker)发展到后来的 CLMM(concentrated liquidity market maker)模式，具体的swap算法不是本文的讨论重点，我们今天只通过接口来认识这两个协议。Curve首先来看Curve的一个池子：Curve: USDT/WBTC/WETH Pool | Address: 0xD51a44d3...A1bfAAE46 | EtherscanContract: Verified | Balan...]]></description>
            <content:encoded><![CDATA[<p>这节课，我们继续完善SimpleArbi.sol，在合约内部完成Curve和Uniswap的swap操作。</p><p>github:</p><div data-type="embedly" src="https://github.com/yueying007/blockchainclass.git" data="{&quot;provider_url&quot;:&quot;https://github.com&quot;,&quot;description&quot;:&quot;Contribute to yueying007/blockchainclass development by creating an account on GitHub.&quot;,&quot;title&quot;:&quot;GitHub - yueying007/blockchainclass&quot;,&quot;author_name&quot;:&quot;yueying007&quot;,&quot;thumbnail_width&quot;:1200,&quot;url&quot;:&quot;https://github.com/yueying007/blockchainclass&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png&quot;,&quot;author_url&quot;:&quot;https://github.com/yueying007&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;GitHub&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:600,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1200,&quot;height&quot;:600,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://github.com/yueying007/blockchainclass.git" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>GitHub - yueying007/blockchainclass</h2><p>Contribute to yueying007/blockchainclass development by creating an account on GitHub.</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://github.com</span></div><img src="https://storage.googleapis.com/papyrus_images/14df3d9367ff4dd1c02c6b37edbeeaaeecccdec76b9a3ce88a3b9144b747b31c.png"/></div></a></div></div><h2 id="h-dex" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Dex</h2><p>Curve和Uniswap是以太坊上排名第一和第二的去中心化交易所(DEX)，它们分别都进行过几个版本的迭代升级，从最初的AMM(automatic market maker)发展到后来的 CLMM(concentrated liquidity market maker)模式，具体的swap算法不是本文的讨论重点，我们今天只通过接口来认识这两个协议。</p><h2 id="h-curve" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Curve</h2><p>首先来看Curve的一个池子：</p><div data-type="embedly" src="https://etherscan.io/address/0xD51a44d3FaE010294C616388b506AcdA1bfAAE46" data="{&quot;provider_url&quot;:&quot;https://etherscan.io&quot;,&quot;description&quot;:&quot;Contract: Verified | Balance: $16,643,787.06 across 1 Chain | Transactions: 44,685 | As at Oct-24-2025 09:47:20 PM (UTC)&quot;,&quot;title&quot;:&quot;Curve: USDT/WBTC/WETH Pool | Address: 0xD51a44d3...A1bfAAE46 | Etherscan&quot;,&quot;author_name&quot;:&quot;etherscan.io&quot;,&quot;url&quot;:&quot;https://etherscan.io/address/0xD51a44d3FaE010294C616388b506AcdA1bfAAE46&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg&quot;,&quot;thumbnail_width&quot;:288,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;Ethereum (ETH) Blockchain Explorer&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:288,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:288,&quot;height&quot;:288,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://etherscan.io/address/0xD51a44d3FaE010294C616388b506AcdA1bfAAE46" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>Curve: USDT/WBTC/WETH Pool | Address: 0xD51a44d3...A1bfAAE46 | Etherscan</h2><p>Contract: Verified | Balance: $16,643,787.06 across 1 Chain | Transactions: 44,685 | As at Oct-24-2025 09:47:20 PM (UTC)</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://etherscan.io</span></div><img src="https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg"/></div></a></div></div><p>它提供了USDT/WETH/WBTC三种token的兑换，每个token都有一个唯一的编号(0/1/2)，编号和token的对应关系可以从合约的coins()方法获得。来看看它的接口:</p><pre data-type="codeBlock" text="interface ICurveCrypto {
    function exchange(uint256 from, uint256 to, uint256 from_amount, uint256 min_to_amount) external payable;
    function get_dy(uint256 from, uint256 to, uint256 from_amount) external view returns(uint256);
}
"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ICurveCrypto</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">exchange</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">uint256</span> to, <span class="hljs-keyword">uint256</span> from_amount, <span class="hljs-keyword">uint256</span> min_to_amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span></span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_dy</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">uint256</span> to, <span class="hljs-keyword">uint256</span> from_amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)</span>;
}
</code></pre><p>通过exchange()方法，实现两个token的兑换，参数分别表示：</p><p>from: 输入token的编号</p><p>to: 输出token的编号</p><p>from_amount: 输入token的数量</p><p>min_to_amount: 输出token的最小数量</p><p>通过get_dy()方法，获得给定from_amount下可以获得的输出token数量。</p><p>注意get_dy()的view关键字，表明这是一个只读函数，将来我们会它来计算token之间的兑换比例。</p><h2 id="h-uniswap" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Uniswap</h2><p>在来看一个UniswapV3的池子:</p><div data-type="embedly" src="https://etherscan.io/address/0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36" data="{&quot;provider_url&quot;:&quot;https://etherscan.io&quot;,&quot;description&quot;:&quot;Contract: Verified | Balance: $161,439,404.51 across 1 Chain | Transactions: 109 | As at Oct-24-2025 09:47:22 PM (UTC)&quot;,&quot;title&quot;:&quot;Uniswap V3: USDT | Address: 0x4e68Ccd3...3960dFa36 | Etherscan&quot;,&quot;author_name&quot;:&quot;etherscan.io&quot;,&quot;url&quot;:&quot;https://etherscan.io/address/0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg&quot;,&quot;thumbnail_width&quot;:288,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;Ethereum (ETH) Blockchain Explorer&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:288,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:288,&quot;height&quot;:288,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://etherscan.io/address/0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>Uniswap V3: USDT | Address: 0x4e68Ccd3...3960dFa36 | Etherscan</h2><p>Contract: Verified | Balance: $161,439,404.51 across 1 Chain | Transactions: 109 | As at Oct-24-2025 09:47:22 PM (UTC)</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://etherscan.io</span></div><img src="https://storage.googleapis.com/papyrus_images/c3ceb1ec55f2d6c2b4eeb2df764e4bc8936caf048a981c893b5f220aa9d207a0.jpg"/></div></a></div></div><p>它提供了WETH/USDT的兑换:</p><pre data-type="codeBlock" text="interface IUniswapV3Pair {
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);
    function fee() external view returns(uint24);
}
"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IUniswapV3Pair</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swap</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> recipient,
        <span class="hljs-keyword">bool</span> zeroForOne,
        <span class="hljs-keyword">int256</span> amountSpecified,
        <span class="hljs-keyword">uint160</span> sqrtPriceLimitX96,
        <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 class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">int256</span> amount0, <span class="hljs-keyword">int256</span> amount1</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fee</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">uint24</span></span>)</span>;
}
</code></pre><p>通过swap()函数，实现两个token之间的兑换，这是一种闪电兑(flash swap)的模式，比如用WETH兑换USDT，它会先把USDT转给我的合约，然后在调用我的回调函数uniswapV3SwapCallback()，在回调函数中我把WETH还给它。参数分别表示：</p><p>recipient: 接收地址(我的合约)</p><p>zeroForOne: 标志位(用来决定兑换的方向)</p><p>amountSpecified: 输入token 的数量</p><p>sqrtPriceLimitX96: 限价范围</p><p>data: 回调信息</p><h2 id="h-curve" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">封装Curve</h2><p>接下来我们封装一个函数实现Curve池子的兑换:</p><pre data-type="codeBlock" text="// CurveCrypto
function CurveCryptoExchange(address pool, uint256 token_in_id, uint256 token_out_id, address token_in,
    uint256 amount_in) internal {
    ApproveToken(token_in, pool, amount_in);
    ICurveCrypto(pool).exchange(token_in_id, token_out_id, amount_in, 0);
}
"><code><span class="hljs-comment">// CurveCrypto</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CurveCryptoExchange</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> pool, <span class="hljs-keyword">uint256</span> token_in_id, <span class="hljs-keyword">uint256</span> token_out_id, <span class="hljs-keyword">address</span> token_in,
    <span class="hljs-keyword">uint256</span> amount_in</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
    ApproveToken(token_in, pool, amount_in);
    ICurveCrypto(pool).exchange(token_in_id, token_out_id, amount_in, <span class="hljs-number">0</span>);
}
</code></pre><p>pool: Curve池子的地址</p><p>token_in_id: 输入token的编号</p><p>token_out_id: 输出token的编号</p><p>token_in: 输入token的地址</p><p>amount_in: 输入token的数量</p><p>注意在调用Curve池子的exchange()函数之前，我们需要先向Curve授权：</p><pre data-type="codeBlock" text="// approve
function ApproveToken(address token, address spender, uint256 amount) internal {
    uint256 alowance = IERC20(token).allowance(address(this), spender);
    if (alowance &lt; amount) {
        IERC20(token).safeApprove(spender, 0);
        IERC20(token).safeApprove(spender, MAX_INT);
    }
}
"><code><span class="hljs-comment">// approve</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ApproveToken</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> token, <span class="hljs-keyword">address</span> spender, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
    <span class="hljs-keyword">uint256</span> alowance <span class="hljs-operator">=</span> IERC20(token).allowance(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), spender);
    <span class="hljs-keyword">if</span> (alowance <span class="hljs-operator">&#x3C;</span> amount) {
        IERC20(token).safeApprove(spender, <span class="hljs-number">0</span>);
        IERC20(token).safeApprove(spender, MAX_INT);
    }
}
</code></pre><p>Curve获得了我的合约的授权后，才可以通过transferFrom()方法，从我的合约把输入token转走。然后就可以调用exchange()进行兑换了。</p><h2 id="h-uniswap" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">封装Uniswap</h2><p>接下来我们封装一个函数实现Uniswap的兑换:</p><pre data-type="codeBlock" text="// UniSwapV3
function UniswapV3Swap(address pool, address token_in, address token_out, uint256 amount_in) internal {
    bool zeroForOne = token_in &lt; token_out;
    RepayData memory repay_data = RepayData(token_in, amount_in, pool);
    IUniswapV3Pair(pool).swap(address(this), zeroForOne, int256(amount_in),
        (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1), abi.encode(repay_data));
}
"><code><span class="hljs-comment">// UniSwapV3</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UniswapV3Swap</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> pool, <span class="hljs-keyword">address</span> token_in, <span class="hljs-keyword">address</span> token_out, <span class="hljs-keyword">uint256</span> amount_in</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
    <span class="hljs-keyword">bool</span> zeroForOne <span class="hljs-operator">=</span> token_in <span class="hljs-operator">&#x3C;</span> token_out;
    RepayData <span class="hljs-keyword">memory</span> repay_data <span class="hljs-operator">=</span> RepayData(token_in, amount_in, pool);
    IUniswapV3Pair(pool).swap(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), zeroForOne, <span class="hljs-keyword">int256</span>(amount_in),
        (zeroForOne ? TickMath.MIN_SQRT_RATIO <span class="hljs-operator">+</span> <span class="hljs-number">1</span> : TickMath.MAX_SQRT_RATIO <span class="hljs-operator">-</span> <span class="hljs-number">1</span>), <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(repay_data));
}
</code></pre><p>首先通过zeroForOne标志确定兑换的方向。</p><p>然后定义一个RepayData结构来存储还款信息(还款token、还款数量和还款地址)，然后使用abi.encode()加密成一段bytes类型的data。然后调用Unisawp池子的swap()函数进行兑换。这里的address(this)表示我的合约地址。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">回调</h2><p>在Uniswap发起兑换后，它会把输出token转给我的合约，然后调用我的uniswapV3SwapCallback()函数:</p><pre data-type="codeBlock" text="function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) public {
    receiveLoan(_data);
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uniswapV3SwapCallback</span>(<span class="hljs-params"><span class="hljs-keyword">int256</span> amount0Delta, <span class="hljs-keyword">int256</span> amount1Delta, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> _data</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    receiveLoan(_data);
}
</code></pre><p>在函数的实现中，我们继续调用receiveLoan()，并在其中进行还款操作:</p><pre data-type="codeBlock" text="// callback
function receiveLoan(bytes memory data) public {
    require(!lock, &quot;Locked&quot;);
    RepayData memory _repay_data = abi.decode(data, (RepayData));
    IERC20(_repay_data.repay_token).safeTransfer(_repay_data.recipient, _repay_data.repay_amount);
}
"><code><span class="hljs-comment">// callback</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">receiveLoan</span>(<span class="hljs-params"><span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>lock, <span class="hljs-string">"Locked"</span>);
    RepayData <span class="hljs-keyword">memory</span> _repay_data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (RepayData));
    IERC20(_repay_data.repay_token).safeTransfer(_repay_data.recipient, _repay_data.repay_amount);
}
</code></pre><p>这里我们加了一个require条件要求合约是非锁定状态，来防止receiveLoan()被其他人恶意调用。</p><p>然后将还款信息解码，将token转给Uniswap池子。</p><p>注意在生产环境下尽量减少使用IERC20的low level call，因此我们把approve()和transfer()替换成了更加安全的safeApprove()和safeTransfer()。</p><h2 id="h-swapbase" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">封装Swapbase</h2><p>为了方便，我们需要有一个统一的入口进行兑换，因此我们封装一个SwapBase()函数，通过function_id来识别兑换的协议:</p><pre data-type="codeBlock" text="// SwapBase
function SwapBase(address pool, uint256 function_id, uint256 amount_in, uint256 token_in_id, uint256 token_out_id,
    address token_in, address token_out) public returns(uint256) {
    uint256 balance = IERC20(token_out).balanceOf(address(this));
    if (function_id == 1) {
        UniswapV3Swap(pool, token_in, token_out, amount_in);
    } else if (function_id == 2) {
        CurveCryptoExchange(pool, token_in_id, token_out_id, token_in, amount_in);
    }
    return IERC20(token_out).balanceOf(address(this)) - balance;
}
"><code><span class="hljs-comment">// SwapBase</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SwapBase</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> pool, <span class="hljs-keyword">uint256</span> function_id, <span class="hljs-keyword">uint256</span> amount_in, <span class="hljs-keyword">uint256</span> token_in_id, <span class="hljs-keyword">uint256</span> token_out_id,
    <span class="hljs-keyword">address</span> token_in, <span class="hljs-keyword">address</span> token_out</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">uint256</span> balance <span class="hljs-operator">=</span> IERC20(token_out).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
    <span class="hljs-keyword">if</span> (function_id <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>) {
        UniswapV3Swap(pool, token_in, token_out, amount_in);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (function_id <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">2</span>) {
        CurveCryptoExchange(pool, token_in_id, token_out_id, token_in, amount_in);
    }
    <span class="hljs-keyword">return</span> IERC20(token_out).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)) <span class="hljs-operator">-</span> balance;
}
</code></pre><p>根据function_id参数的不同，我们选择调用Curve还是Uniswap。同时还增加了一个返回值，用来返回最终得到的输出token的数量。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">测试</h2><p>下面就可以在truffle中进行编译和测试了(测试环境搭建参考上一讲)。</p><p>进入truffle控制台:</p><pre data-type="codeBlock" text="truffle console
"><code>truffle <span class="hljs-variable language_">console</span>
</code></pre><p>定义一些基本地址:</p><pre data-type="codeBlock" text="WETH = &apos;0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2&apos;;
USDT = &apos;0xdAC17F958D2ee523a2206206994597C13D831ec7&apos;;
Curvepool = &apos;0xD51a44d3FaE010294C616388b506AcdA1bfAAE46&apos;;
Uniswappool = &apos;0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36&apos;;
"><code><span class="hljs-attr">WETH</span> = <span class="hljs-string">'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'</span><span class="hljs-comment">;</span>
<span class="hljs-attr">USDT</span> = <span class="hljs-string">'0xdAC17F958D2ee523a2206206994597C13D831ec7'</span><span class="hljs-comment">;</span>
<span class="hljs-attr">Curvepool</span> = <span class="hljs-string">'0xD51a44d3FaE010294C616388b506AcdA1bfAAE46'</span><span class="hljs-comment">;</span>
<span class="hljs-attr">Uniswappool</span> = <span class="hljs-string">'0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36'</span><span class="hljs-comment">;</span>
</code></pre><p>定义合约实例:</p><pre data-type="codeBlock" text="instance = await SimpleArbi.deployed();
"><code><span class="hljs-attr">instance</span> = await SimpleArbi.deployed()<span class="hljs-comment">;</span>
</code></pre><p>向合约转入10个ETH，并将其中5个兑换为WETH:</p><pre data-type="codeBlock" text="instance.send(web3.utils.toWei(&apos;10&apos;, &apos;ether&apos;));
instance.ETHtoWETH(web3.utils.toWei(&apos;5&apos;, &apos;ether&apos;));
"><code>instance.<span class="hljs-built_in">send</span>(web3.utils.toWei(<span class="hljs-string">'10'</span>, <span class="hljs-string">'ether'</span>));
instance.ETHtoWETH(web3.utils.toWei(<span class="hljs-string">'5'</span>, <span class="hljs-string">'ether'</span>));
</code></pre><p>把合约锁打开:</p><pre data-type="codeBlock" text="instance.setLock(false);
"><code>instance.setLock(<span class="hljs-literal">false</span>);
</code></pre><p>通过Curve把1个WETH兑换为USDT:</p><pre data-type="codeBlock" text="instance.SwapBase(Curvepool, 2, web3.utils.toWei(&apos;1&apos;), 2, 0, WETH, USDT);
"><code>instance.SwapBase(Curvepool, <span class="hljs-number">2</span>, web3.utils.toWei(<span class="hljs-string">'1'</span>), <span class="hljs-number">2</span>, <span class="hljs-number">0</span>, WETH, USDT);
</code></pre><p>检查一下合约中的USDT数量:</p><pre data-type="codeBlock" text="usdt = await instance.getTokenBalance(USDT, instance.address);
usdt.toString();
"><code>usdt <span class="hljs-operator">=</span> await instance.getTokenBalance(USDT, instance.<span class="hljs-built_in">address</span>);
usdt.toString();
</code></pre><p>再把USDT全部换回WETH:</p><pre data-type="codeBlock" text="instance.SwapBase(Curvepool, 2, usdt, 0, 2, USDT, WETH);
"><code>instance.SwapBase(Curvepool, <span class="hljs-number">2</span>, usdt, <span class="hljs-number">0</span>, <span class="hljs-number">2</span>, USDT, WETH);
</code></pre><p>通过Uniswap把1个WETH兑换为USDT:</p><pre data-type="codeBlock" text="instance.SwapBase(Uniswappool, 1, web3.utils.toWei(&apos;1&apos;), 0, 0, WETH, USDT);
"><code>instance.SwapBase(Uniswappool, <span class="hljs-number">1</span>, web3.utils.toWei(<span class="hljs-string">'1'</span>), <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, WETH, USDT);
</code></pre><p>检查一下合约中的USDT数量:</p><pre data-type="codeBlock" text="usdt = await instance.getTokenBalance(USDT, instance.address);
usdt.toString();
"><code>usdt <span class="hljs-operator">=</span> await instance.getTokenBalance(USDT, instance.<span class="hljs-built_in">address</span>);
usdt.toString();
</code></pre><p>再把USDT全部换回WETH:</p><pre data-type="codeBlock" text="instance.SwapBase(Uniswappool, 1, usdt, 0, 0, USDT, WETH);
"><code>instance.SwapBase(Uniswappool, <span class="hljs-number">1</span>, usdt, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, USDT, WETH);
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>至此，我们在合约中分别使用Curve和Uniswap进行了Swap操作，下一讲我们继续把两个操作串联起来，实现一个套利操作。</p><p><em>欢迎来即刻App与我互动，即刻账号: 月影007</em></p>]]></content:encoded>
            <author>yueying007@newsletter.paragraph.com (yueying007)</author>
        </item>
        <item>
            <title><![CDATA[区块链开发课第四讲 使用Truffle测试智能合约]]></title>
            <link>https://paragraph.com/@yueying007/truffle</link>
            <guid>Cuhe9xHRIe5RY06pfBlZ</guid>
            <pubDate>Mon, 18 Apr 2022 04:20:30 GMT</pubDate>
            <description><![CDATA[在这一讲中，我们将用Truffle对上一讲中的SimpleArbi.sol进行测试。web3 provider首先，我们需要从Alchemy.com申请一个免费的web3 provider：Alchemy DashboardWhether you&#x27;re a beginner developer, startup, web3 market leader, or a large enterprise, Alchemy makes multichain web3 development easy with reliable and scalable node infrastructure, enhanced APIs, and developer tools. Get started for free!https://dashboard.alchemy.com注册成功后，点击Create App，选择Ethereum Mainnet，然后在生成的App后面找到view key，将里面的http url保存到本地，以后我们与以太坊进行交互，都要通过这个http url来发送JS...]]></description>
            <content:encoded><![CDATA[<p>在这一讲中，我们将用Truffle对上一讲中的SimpleArbi.sol进行测试。</p><h2 id="h-web3-provider" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">web3 provider</h2><p>首先，我们需要从Alchemy.com申请一个免费的web3 provider：</p><div data-type="embedly" src="https://dashboard.alchemyapi.io/" data="{&quot;provider_url&quot;:&quot;https://dashboard.alchemy.com&quot;,&quot;description&quot;:&quot;Whether you&apos;re a beginner developer, startup, web3 market leader, or a large enterprise, Alchemy makes multichain web3 development easy with reliable and scalable node infrastructure, enhanced APIs, and developer tools. Get started for free!&quot;,&quot;title&quot;:&quot;Alchemy Dashboard&quot;,&quot;mean_alpha&quot;:240.984126984,&quot;thumbnail_width&quot;:2400,&quot;url&quot;:&quot;https://dashboard.alchemy.com/&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/b17d27678a0ce837588bef0f832e74e2ec81e04ff911f8e9fd343cf76060f91a.png&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;Alchemy&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:1260,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:2400,&quot;height&quot;:1260,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/b17d27678a0ce837588bef0f832e74e2ec81e04ff911f8e9fd343cf76060f91a.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/b17d27678a0ce837588bef0f832e74e2ec81e04ff911f8e9fd343cf76060f91a.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://dashboard.alchemyapi.io/" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>Alchemy Dashboard</h2><p>Whether you&#x27;re a beginner developer, startup, web3 market leader, or a large enterprise, Alchemy makes multichain web3 development easy with reliable and scalable node infrastructure, enhanced APIs, and developer tools. Get started for free!</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://dashboard.alchemy.com</span></div><img src="https://storage.googleapis.com/papyrus_images/b17d27678a0ce837588bef0f832e74e2ec81e04ff911f8e9fd343cf76060f91a.png"/></div></a></div></div><p>注册成功后，点击Create App，选择Ethereum Mainnet，然后在生成的App后面找到view key，将里面的http url保存到本地，以后我们与以太坊进行交互，都要通过这个http url来发送JSON-RPC请求。</p><h2 id="h-truffle" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Truffle</h2><p>如果还没有安装Truffle：</p><pre data-type="codeBlock" text="npm install -g truffle
"><code>npm install <span class="hljs-operator">-</span>g truffle
</code></pre><p>我们建立一个Deploy目录，在里面初始化一个Truffle工程：</p><pre data-type="codeBlock" text="mkdir ~/Projects/Deploy
cd ~/Projects/Deploy
truffle init
"><code>mkdir <span class="hljs-operator">~</span><span class="hljs-operator">/</span>Projects<span class="hljs-operator">/</span>Deploy
cd <span class="hljs-operator">~</span><span class="hljs-operator">/</span>Projects<span class="hljs-operator">/</span>Deploy
truffle init
</code></pre><p>我们看到它自动生成了几个目录：</p><p>build: 存放编译好的智能合约abi</p><p>contracts: 存放智能合约的源代码</p><p>migrations: 存放部署脚本</p><p>首先我们打开truffle-config.js，做一些基本配置：</p><p>在networks中的development项，配置测试环境下的IP、端口以及network_id，同时把skipDryRun设置为true。</p><pre data-type="codeBlock" text="networks: {
 development: {
   host: &quot;127.0.0.1&quot;,     
   port: 8545,            
   network_id: &quot;*&quot;,       
   skipDryRun: true
 },
},
"><code><span class="hljs-attr">networks:</span> {
 <span class="hljs-attr">development:</span> {
   <span class="hljs-attr">host:</span> <span class="hljs-string">"127.0.0.1"</span>,     
   <span class="hljs-attr">port:</span> <span class="hljs-number">8545</span>,            
   <span class="hljs-attr">network_id:</span> <span class="hljs-string">"*"</span>,       
   <span class="hljs-attr">skipDryRun:</span> <span class="hljs-literal">true</span>
 },
}<span class="hljs-string">,</span>
</code></pre><p>在compilers项，设置solidity的版本号(0.8.0)，optimizer enabled设置为true。</p><pre data-type="codeBlock" text="compilers: {
  solc: {
     version: &quot;0.8.0&quot;,    
     settings: {         
      optimizer: {
        enabled: true,
        runs: 200
      },
    }
  }
}
"><code><span class="hljs-attr">compilers:</span> {
  <span class="hljs-attr">solc:</span> {
     <span class="hljs-attr">version:</span> <span class="hljs-string">"0.8.0"</span>,    
     <span class="hljs-attr">settings:</span> {         
      <span class="hljs-attr">optimizer:</span> {
        <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>,
        <span class="hljs-attr">runs:</span> <span class="hljs-number">200</span>
      },
    }
  }
}
</code></pre><p>然后，在migrations目录下，新建一个脚本2_deploy_contracts.js，通过这个脚本告诉truffle如何部署我们的合约:</p><pre data-type="codeBlock" text="var SimpleArbi = artifacts.require(&quot;../contracts/SimpleArbi.sol&quot;);module.exports = function(deployer, network, accounts) {
 deployer.deploy(SimpleArbi);
};
"><code><span class="hljs-keyword">var</span> SimpleArbi <span class="hljs-operator">=</span> artifacts.require(<span class="hljs-string">"../contracts/SimpleArbi.sol"</span>);module.exports <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">deployer, network, accounts</span>) </span>{
 deployer.deploy(SimpleArbi);
};
</code></pre><p>接下来，把SimpleArbi.sol文件拷贝到contracts目录下。</p><p>下面，我们开始进行编译:</p><pre data-type="codeBlock" text="cd ~/Projects/Deploy
truffle compile
"><code>cd <span class="hljs-operator">~</span><span class="hljs-operator">/</span>Projects<span class="hljs-operator">/</span>Deploy
truffle compile
</code></pre><p>当出现Compiled susccesfully using:…的提示时，就表示编译成功了。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">部署</h2><p>下面我们把编译好的智能合约部署在本地的测试环境下。</p><p>如果还没有安装Ganache-cli:</p><pre data-type="codeBlock" text="npm install -g ganache-cli
"><code>npm install <span class="hljs-operator">-</span>g ganache<span class="hljs-operator">-</span>cli
</code></pre><p>打开另外一个Terminal，建立一个以太坊主网的fork环境(后面的https://…是你刚才从Alchemy申请的http url)：</p><pre data-type="codeBlock" text="ganache-cli --fork https://eth-mainnet.alchemyapi.io/v2/你的api_key
"><code>ganache<span class="hljs-operator">-</span>cli <span class="hljs-operator">-</span><span class="hljs-operator">-</span>fork https:<span class="hljs-comment">//eth-mainnet.alchemyapi.io/v2/你的api_key</span>
</code></pre><p>它为我们自动建立了十个测试账户，每个里面有100ETH，在与合约交互时，它会默认使用第一个账户。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1aa7ecc9dc8be19c05a360217e47c94b32bf883b60f039984c7783fdcb874549.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>回到第一个Terminal中进行部署：</p><pre data-type="codeBlock" text="cd ~/Projects/Deploy
truffle migrate
"><code>cd <span class="hljs-operator">~</span><span class="hljs-operator">/</span>Projects<span class="hljs-operator">/</span>Deploy
truffle migrate
</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/9abf481339933f7c1357352ce9c25670623af4ab39bdb1a559fa6cdb8e98493e.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>下面，我们就可以进行测试了。</p><p>进入truffle控制台：</p><pre data-type="codeBlock" text="cd ~/Projects/Deploy
truffle console
"><code>cd <span class="hljs-operator">~</span><span class="hljs-operator">/</span>Projects<span class="hljs-operator">/</span>Deploy
truffle console
</code></pre><p>先定义WETH的地址:</p><pre data-type="codeBlock" text="WETH = &apos;0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2&apos;;
"><code><span class="hljs-attr">WETH</span> = <span class="hljs-string">'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'</span><span class="hljs-comment">;</span>
</code></pre><p>用instance代表合约的实例：</p><pre data-type="codeBlock" text="instance = await SimpleArbi.deployed();
"><code><span class="hljs-attr">instance</span> = await SimpleArbi.deployed()<span class="hljs-comment">;</span>
</code></pre><p>查看一下合约的所有者是谁:</p><pre data-type="codeBlock" text="owner = await instance.getOwner()
owner
"><code>owner <span class="hljs-operator">=</span> await instance.getOwner()
owner
</code></pre><p>向合约转10个ETH:</p><pre data-type="codeBlock" text="instance.send(web3.utils.toWei(&apos;10&apos;, &apos;ether&apos;))
"><code>instance.<span class="hljs-built_in">send</span>(web3.utils.toWei(<span class="hljs-string">'10'</span>, <span class="hljs-string">'ether'</span>))
</code></pre><p>调用合约的ETHtoWETH()函数，把5个ETH转换为WETH:</p><pre data-type="codeBlock" text="instance.ETHtoWETH(web3.utils.toWei(&apos;5&apos;, &apos;ether&apos;))
"><code>instance.ETHtoWETH(web3.utils.toWei(<span class="hljs-string">'5'</span>, <span class="hljs-string">'ether'</span>))
</code></pre><p>调用合约的getTokenBalance()函数，检查一下合约中WETH的余额:</p><pre data-type="codeBlock" text="weth = await instance.getTokenBalance(WETH, instance.address);
weth.toString();
"><code>weth <span class="hljs-operator">=</span> await instance.getTokenBalance(WETH, instance.<span class="hljs-built_in">address</span>);
weth.toString();
</code></pre><p>调用合约的turnOutToken()函数，把WETH转出到所有者的钱包里:</p><pre data-type="codeBlock" text="instance.turnOutToken(WETH, weth)
"><code>instance.turnOutToken(WETH, weth)
</code></pre><p>检查一下所有者钱包里的WETH数量:</p><pre data-type="codeBlock" text="weth = await instance.getTokenBalance(WETH, accounts[0]);
weth.toString();
"><code>weth <span class="hljs-operator">=</span> await instance.getTokenBalance(WETH, accounts[<span class="hljs-number">0</span>]);
weth.toString();
</code></pre><p>最后，我们来测试一下flashLoan()函数，发起一笔闪电贷，:</p><pre data-type="codeBlock" text="result = await instance.flashLoan(WETH, web3.utils.toWei(&apos;100&apos;))
result
"><code>result <span class="hljs-operator">=</span> await instance.flashLoan(WETH, web3.utils.toWei(<span class="hljs-string">'100'</span>))
result
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4a237f0f4c6b646bc625e4292804eed26b21d18d0de350ccebc2cef878105ee0.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>交易成功，如果想看交易日志：</p><pre data-type="codeBlock" text="result.receipt.rawLogs
"><code>result.receipt.rawLogs
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>至此，我们在本地的主网fork环境下，对SimpleArbi.sol合约的函数进行了测试，由于测试环境是完全fork主网的，所以就可以放心地部署到主网上面了。下一讲中，我们继续丰富合约代码，在其中加入Dex套利操作。</p><p>这一讲的代码在github上同步更新:</p><p><em>欢迎来即刻App与我互动，即刻账号: 月影007</em></p>]]></content:encoded>
            <author>yueying007@newsletter.paragraph.com (yueying007)</author>
        </item>
        <item>
            <title><![CDATA[区块链开发课第三讲 智能合约开发(1)]]></title>
            <link>https://paragraph.com/@yueying007/1</link>
            <guid>caFStPLutlilBLDKb7fC</guid>
            <pubDate>Sat, 16 Apr 2022 05:09:12 GMT</pubDate>
            <description><![CDATA[这节课中，我会带你使用Solidity编写一个简单的智能合约，实现闪电贷的功能。 在开始之前，你可以在本地建立一个目录，从github上下载代码，打开IDE，对照着代码学习。mkdir ~/Projects cd ~/Projects git clone https://github.com/yueying007/blockchainclass.git 打开SimpleArbi.sol，我们来学习一下Solidity的基本用法。SimpleArbi.sol首先，在头部我们定义solidity的版本号：pragma solidity 0.8.0; 接口在执行一笔交易时，合约往往需要调用外部合约，因此需要定义外部合约的接口(interface)，最典型的是ERC20标准token的接口：interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); functio...]]></description>
            <content:encoded><![CDATA[<p>这节课中，我会带你使用Solidity编写一个简单的智能合约，实现闪电贷的功能。</p><p>在开始之前，你可以在本地建立一个目录，从github上下载代码，打开IDE，对照着代码学习。</p><pre data-type="codeBlock" text="mkdir ~/Projects
cd ~/Projects
git clone https://github.com/yueying007/blockchainclass.git
"><code><span class="hljs-built_in">mkdir</span> ~/Projects
<span class="hljs-built_in">cd</span> ~/Projects
git <span class="hljs-built_in">clone</span> https://github.com/yueying007/blockchainclass.git
</code></pre><p>打开SimpleArbi.sol，我们来学习一下Solidity的基本用法。</p><h2 id="h-simplearbisol" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">SimpleArbi.sol</h2><p>首先，在头部我们定义solidity的版本号：</p><pre data-type="codeBlock" text="pragma solidity 0.8.0;
"><code><span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.0;</span>
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">接口</h2><p>在执行一笔交易时，合约往往需要调用外部合约，因此需要定义外部合约的接口(interface)，最典型的是ERC20标准token的接口：</p><pre data-type="codeBlock" text="interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function decimals() external view returns (uint8);
}
"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IERC20</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">totalSupply</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">balanceOf</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> account</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> recipient, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span 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">allowance</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">address</span> spender</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">approve</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> spender, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>)</span>;
    <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> sender, <span class="hljs-keyword">address</span> recipient, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span 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">decimals</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">uint8</span></span>)</span>;
}
</code></pre><p>一个接口包含function关键字、函数名、参数列表、external关键字以及返回值类型。通过接口，合约可以与外部合约进行交互，而不需要知道外部合约具体的实现细节。</p><p>带view关键字的函数，表示只读函数，即只可以读取区块链的状态，而不可以改变状态，属于静态调用；不带view关键字的函数，可以进行改写状态变量、发送事件、转账ETH等等这些可以改变状态的调用，这类调用也可以称作一笔交易(transaction)。</p><p>在IERC20接口中，可以通过总供应量(totalSupply)、余额(balanceOf)、数位(decimals)等只读函数获取token的信息，也可以通过转账(transfer)、请求转账(transferFrom)、授权(approve)等函数发起交易。</p><p>下面看看WETH接口：</p><pre data-type="codeBlock" text="interface IWETH {
    function deposit() external payable;
    function withdraw(uint wad) external;
}
"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IWETH</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deposit</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span></span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> wad</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span></span>;
}
</code></pre><p>WETH(Wrapped Ether)，是一种将以太坊原生代币ETH与ERC20token互相转换的合约。在WETH接口中，定义了两个函数：</p><p>deposit: 将ETH转换为WETH</p><p>withdraw: 将WETH转换为ETH</p><p>由于要实现闪电贷，我们需要与KeeperDao的LiquidityPool合约交互，所以需要加上LiquidityPool的接口:</p><pre data-type="codeBlock" text="interface ILiquidity {
    function borrow(address _token, uint256 _amount, bytes calldata _data) external;
}
"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ILiquidity</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">borrow</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _token, <span class="hljs-keyword">uint256</span> _amount, <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><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">库</h2><p>在写合约时经常需要用到一些库，比如最常用的SafeMath库。</p><p>在solidity中，通常会用uint256类型定义token的数量。uint256是一种非负整型变量，在进行加减乘数/取余/取模运算时，如果不小心就会溢出，所以在对uint256进行数学运算时，应当尽量用add/sub/mul/div来代替+-*/。</p><pre data-type="codeBlock" text="library SafeMath {
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    return sub(a, b, &quot;SafeMath: subtraction overflow&quot;);
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    require(b &lt;= a, errorMessage);
    uint256 c = a - b;

    return c;
}
"><code><span class="hljs-class"><span class="hljs-keyword">library</span> <span class="hljs-title">SafeMath</span> </span>{
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sub</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b</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">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> sub(a, b, <span class="hljs-string">"SafeMath: subtraction overflow"</span>);
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sub</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> errorMessage</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">uint256</span></span>) </span>{
    <span class="hljs-built_in">require</span>(b <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> a, errorMessage);
    <span class="hljs-keyword">uint256</span> c <span class="hljs-operator">=</span> a <span class="hljs-operator">-</span> b;

    <span class="hljs-keyword">return</span> c;
}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">合约</h2><p>下面来到主体部分，合约(contract)。</p><p>solidity是一种面向对象的编程语言，用contract关键字定义一个合约，它类似于我们熟悉的类(class)，而部署一个合约相当于为这个类实例化一个对象。</p><p>一个类包含属性与方法，一个合约包含状态变量(state variable)与函数(function)。在函数内部定义的变量以及函数的参数称为局部变量(local variable)。状态变量与局部变量的区别在于，状态变量存储在区块链上，因此任何改写状态变量的操作都是一笔transaction，需要消耗gas，而局部变量只在内存中。因此，为了节省交易成本，我们应尽量少地去更改状态变量的值，而多用传参或者定义局部变量来完成计算。</p><p>首先我们定义一个结构体类型，用来存储借的token以及借的数量：</p><pre data-type="codeBlock" text="struct RepayData {
    address repay_token;
    uint256 repay_amount;
}
"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">RepayData</span> {
    <span class="hljs-keyword">address</span> repay_token;
    <span class="hljs-keyword">uint256</span> repay_amount;
}
</code></pre><p>然后定义一些基本的地址：</p><pre data-type="codeBlock" text="address owner;
address liquidityPool = 0x4F868C1aa37fCf307ab38D215382e88FCA6275E2;
address borrowerProxy = 0x17a4C8F43cB407dD21f9885c5289E66E21bEcD9D;
address WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
"><code>address owner<span class="hljs-comment">;</span>
address <span class="hljs-attr">liquidityPool</span> = <span class="hljs-number">0</span>x4F868C1aa37fCf307ab38D215382e88FCA6275E2<span class="hljs-comment">;</span>
address <span class="hljs-attr">borrowerProxy</span> = <span class="hljs-number">0</span>x17a4C8F43cB407dD21f9885c5289E66E21bEcD9D<span class="hljs-comment">;</span>
address <span class="hljs-attr">WETH</span> = <span class="hljs-number">0</span>xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2<span class="hljs-comment">;</span>
</code></pre><p>然后来到构造函数：</p><pre data-type="codeBlock" text="constructor () public {
    owner = address(tx.origin);
}
"><code><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-keyword">address</span>(<span class="hljs-built_in">tx</span>.<span class="hljs-built_in">origin</span>);
}
</code></pre><p>构造函数只有在合约部署时被调用，在里面初始化一些状态变量。在这里，我们定义合约的所有者owner是部署合约这笔交易的源头(tx.origin)</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">修改器</h2><p>修改器(modifier)是一种用来修改其它函数的函数，它可以包在其它函数外面，实现额外的功能。</p><p>我们定义一个onlyOwner()函数， 它要求函数的调用者(msg.sender)只能是合约的所有者(owner)。</p><pre data-type="codeBlock" text="modifier onlyOwner(){
    require(address(msg.sender) == owner, &quot;No authority&quot;);
    _;
}
"><code><span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyOwner</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-keyword">address</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-string">"No authority"</span>);
    <span class="hljs-keyword">_</span>;
}
</code></pre><p>我们注意到这里有一个require函数，它类似于:</p><pre data-type="codeBlock" text="if (address(msg.sender) != owner) revert(&quot;No authority&quot;);
"><code><span class="hljs-keyword">if</span> (<span class="hljs-keyword">address</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-keyword">revert</span>(<span class="hljs-string">"No authority"</span>);
</code></pre><p>意思是如果不满足某个条件，则立即回滚到调用前的初始状态。如果一笔交易回滚，就相当于交易没有发生，这是一种原子操作，即要么成功，要么失败，没有中间状态。我们通常用require对函数的传参进行检查。记住，回滚的交易仍然会消耗gas。</p><h2 id="h-fallback" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">fallback</h2><p>在solidity 0.6.x版本以后，fallback函数分为两种：</p><p>fallback(): 当从外部调用此合约时，在合约中没有找到函数名，就会自动调用该函数</p><p>receive(): 用来接受空的外部调用(call())或者接收ETH</p><p>记住，如果不加上receive()，我们的合约是无法接收外部的ETH转账的：</p><pre data-type="codeBlock" text="receive() external payable {}
"><code><span class="hljs-function"><span class="hljs-keyword">receive</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{}
</code></pre><p><em>注：payable关键字：在调用函数的时候可以附带发送ETH。</em></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">访问权限</h2><p>你可能注意到，无论是状态变量，还是函数，都有一个关键字来定义访问权限：</p><p>external: 只允许从合约外部访问</p><p>public: 既可以从合约外部访问，也可以从合约内部访问</p><p>internal: 只能从合约内部，或者从继承合约访问</p><p>private: 只能从合约内部访问，不能从继承合约访问</p><h2 id="h-getset" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">get/set</h2><p>接下来我们定义一些函数用来读取信息或者发起交易：</p><pre data-type="codeBlock" text="// 返回合约的所有者
function getOwner() public view returns(address) {
    return owner;
}// 返回某个账户的某个token的余额
function getTokenBalance(address token, address account) public view returns(uint256) {
    return IERC20(token).balanceOf(account);
}// 从合约转出ETH
function turnOutETH(uint256 amount) public onlyOwner {
    payable(owner).transfer(amount);
}// 从合约转出token
function turnOutToken(address token, uint256 amount) public onlyOwner {
    IERC20(token).transfer(owner, amount);
}// WETH转换为ETH
function WETHToETH(uint256 amount) public onlyOwner {
    IWETH(WETH).withdraw(amount);
}// ETH转换为WETH
function ETHtoWETH(uint256 amount) public onlyOwner {
    IWETH(WETH).deposit{value:amount}();
}
"><code><span class="hljs-comment">// 返回合约的所有者</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOwner</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 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> owner;
}<span class="hljs-comment">// 返回某个账户的某个token的余额</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTokenBalance</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> token, <span class="hljs-keyword">address</span> account</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> IERC20(token).balanceOf(account);
}<span class="hljs-comment">// 从合约转出ETH</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">turnOutETH</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    <span class="hljs-keyword">payable</span>(owner).<span class="hljs-built_in">transfer</span>(amount);
}<span class="hljs-comment">// 从合约转出token</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">turnOutToken</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> token, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    IERC20(token).<span class="hljs-built_in">transfer</span>(owner, amount);
}<span class="hljs-comment">// WETH转换为ETH</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">WETHToETH</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    IWETH(WETH).withdraw(amount);
}<span class="hljs-comment">// ETH转换为WETH</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ETHtoWETH</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    IWETH(WETH).deposit{<span class="hljs-built_in">value</span>:amount}();
}
</code></pre><p>注意，在get函数中，我们用view关键字表示只读。而在set函数中，我们加上了onlyOwner修改器，防止函数被所有者以外的人调用。</p><p>在任何时候编写合约，我都建议你加上turnOutETH和turnOutToken这两个函数，如果没加，合约里如果有ETH或者token，就会被永远锁在里面出不来了。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">实现闪电贷</h2><p>在实现闪电贷之前，我们先来熟悉一下KeerDao的合约：</p><h2 id="h-liquiditypoolsol" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">LiquidityPool.sol</h2><pre data-type="codeBlock" text="function borrow(address _token, uint256 _amount, bytes calldata _data) external nonReentrant whenNotPaused {
    require(address(kTokens[_token]) != address(0x0), &quot;Token is not registered&quot;);
    uint256 initialBalance = borrowableBalance(_token);
    _transferOut(_msgSender(), _token, _amount);
    borrower.lend(_msgSender(), _data);
    uint256 finalBalance = borrowableBalance(_token);
    require(finalBalance &gt;= initialBalance, &quot;Borrower failed to return the borrowed funds&quot;);

    uint256 fee = finalBalance - initialBalance;
    uint256 poolFee = calculateFee(poolFeeInBips, fee);
    emit Borrowed(_msgSender(), _token, _amount, fee);
    _transferOut(feePool, _token, poolFee);
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">borrow</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _token, <span class="hljs-keyword">uint256</span> _amount, <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 class="hljs-title">nonReentrant</span> <span class="hljs-title">whenNotPaused</span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-keyword">address</span>(kTokens[_token]) <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0x0</span>), <span class="hljs-string">"Token is not registered"</span>);
    <span class="hljs-keyword">uint256</span> initialBalance <span class="hljs-operator">=</span> borrowableBalance(_token);
    _transferOut(_msgSender(), _token, _amount);
    borrower.lend(_msgSender(), _data);
    <span class="hljs-keyword">uint256</span> finalBalance <span class="hljs-operator">=</span> borrowableBalance(_token);
    <span class="hljs-built_in">require</span>(finalBalance <span class="hljs-operator">></span><span class="hljs-operator">=</span> initialBalance, <span class="hljs-string">"Borrower failed to return the borrowed funds"</span>);

    <span class="hljs-keyword">uint256</span> fee <span class="hljs-operator">=</span> finalBalance <span class="hljs-operator">-</span> initialBalance;
    <span class="hljs-keyword">uint256</span> poolFee <span class="hljs-operator">=</span> calculateFee(poolFeeInBips, fee);
    <span class="hljs-keyword">emit</span> Borrowed(_msgSender(), _token, _amount, fee);
    _transferOut(feePool, _token, poolFee);
}
</code></pre><p>首先我通过调用LiquidityPool的borrow()发起一笔闪电贷，通过参数_token和_amount告诉它我要借什么token以及借多少。可以看到，在进行参数检查后，它会首先通过_transferOut()向我发送数量为_amount的_token，这时我就已经收到了这笔贷款。然后它会调用borrower的lend()。我们再来看看这个lend()函数是什么。</p><h2 id="h-borrowerproxysol" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">BorrowerProxy.sol</h2><pre data-type="codeBlock" text="function lend(address _caller, bytes calldata _data) external payable  {
    require(msg.sender == liquidityPool, &quot;BorrowerProxy: Caller is not the liquidity pool&quot;);
    (bool success,) = _caller.call{ value: msg.value }(_data);
    require(success, &quot;BorrowerProxy: Borrower contract reverted during execution&quot;);
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">lend</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _caller, <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 class="hljs-title"><span class="hljs-keyword">payable</span></span>  </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> liquidityPool, <span class="hljs-string">"BorrowerProxy: Caller is not the liquidity pool"</span>);
    (<span class="hljs-keyword">bool</span> success,) <span class="hljs-operator">=</span> _caller.<span class="hljs-built_in">call</span>{ <span class="hljs-built_in">value</span>: <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> }(_data);
    <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"BorrowerProxy: Borrower contract reverted during execution"</span>);
}
</code></pre><p>在lend()函数中，它首先检查调用方必须是LquidityPool，然后向_caller(就是我)发起一个回调call(_data)。要知道向其它合约发送call()就相当于调用其它合约的函数，而这里的_data是一段加密的bytes，包含了函数名和参数的信息。我收到回调后，会完成一系列套利操作，然后立即归还这笔贷款，因为接下来在borrow()函数中，它会检查贷款是否还清：</p><pre data-type="codeBlock" text="uint256 finalBalance = borrowableBalance(_token);
require(finalBalance &gt;= initialBalance, &quot;Borrower failed to return the borrowed funds&quot;);
"><code><span class="hljs-keyword">uint256</span> finalBalance <span class="hljs-operator">=</span> borrowableBalance(_token);
<span class="hljs-built_in">require</span>(finalBalance <span class="hljs-operator">></span><span class="hljs-operator">=</span> initialBalance, <span class="hljs-string">"Borrower failed to return the borrowed funds"</span>);
</code></pre><p>如果没有还清，它会立即回滚，使整个交易失败。</p><p>此时这笔闪电贷的流程就明确了：</p><p>我调用LiquidityPool发起闪电贷 =&gt; LiquidityPool释放贷款 =&gt;BorrowerProxy回调我=&gt;我归还贷款</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">发起闪电贷</h2><p>熟悉了流程之后，我们首先定义一个flashLoan函数发起闪电贷:</p><pre data-type="codeBlock" text="function flashLoan(address token, uint256 amount) public {
    RepayData memory _repay_data = RepayData(token, amount);
    ILiquidity(liquidityPool).borrow(token, amount,
        abi.encodeWithSelector(this.receiveLoan.selector, abi.encode(_repay_data)));
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">flashLoan</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> token, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    RepayData <span class="hljs-keyword">memory</span> _repay_data <span class="hljs-operator">=</span> RepayData(token, amount);
    ILiquidity(liquidityPool).borrow(token, amount,
        <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSelector</span>(<span class="hljs-built_in">this</span>.receiveLoan.<span class="hljs-built_in">selector</span>, <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(_repay_data)));
}
</code></pre><p>函数有两个参数，要借的token的地址(token)以及数量(amount)。然后定义一个局部变量_repay_data存储还款信息。我们用abi.encodeWithSelector把回调函数的名字以及还款信息加密成一串bytes类型的data，连同token,amount作为参数调用LiquidityPool的borrow()，发起一笔闪电贷。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">回调函数</h2><p>接下来我需要在合约里定义一个回调函数receiveLoan用来进行接收到贷款后的操作:</p><pre data-type="codeBlock" text="// callback
function receiveLoan(bytes memory data) public {
    require(msg.sender == borrowerProxy, &quot;Not borrower&quot;);
    RepayData memory _repay_data = abi.decode(data, (RepayData));
    IERC20(_repay_data.repay_token).transfer(liquidityPool, _repay_data.repay_amount);
}
"><code><span class="hljs-comment">// callback</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">receiveLoan</span>(<span class="hljs-params"><span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></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> borrowerProxy, <span class="hljs-string">"Not borrower"</span>);
    RepayData <span class="hljs-keyword">memory</span> _repay_data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (RepayData));
    IERC20(_repay_data.repay_token).<span class="hljs-built_in">transfer</span>(liquidityPool, _repay_data.repay_amount);
}
</code></pre><p>首先检查一下调用者必须是BorrorwerProxy合约，防止被其他人恶意调用。</p><p>然后将data解码，获得还款token和还款数量。</p><p>这里，我们先不做任何操作(以后需要在这里执行套利操作)，直接将token转给LiquidityPool，一笔闪电贷就完成了。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>至此，我们完成了一个简单的智能合约，实现了闪电贷的功能，在下一讲中，我会带你对这个合约进行测试。</p><p><em>欢迎来即刻App与我互动，即刻账号: 月影007</em></p>]]></content:encoded>
            <author>yueying007@newsletter.paragraph.com (yueying007)</author>
        </item>
        <item>
            <title><![CDATA[区块链开发课第二讲 MEV套利原理]]></title>
            <link>https://paragraph.com/@yueying007/mev</link>
            <guid>KlcuNIRKGQYsIKS1mln5</guid>
            <pubDate>Fri, 15 Apr 2022 04:22:52 GMT</pubDate>
            <description><![CDATA[MEV最大可提取价值 (Maximal Extractable Value, 简称MEV) 矿工 (或验证者、序列器) 在其生产的区块中通过其能力任意打包、排除或重新排序交易可以获得一定的利润，而 MEV 便是衡量该利润的一种度量。MEV的详细解释请参考：Maximal extractable value (MEV) | ethereum.orgAn introduction to maximal extractable value (MEV)https://ethereum.orgMEV有两个参与方，搜寻者(searcher)和矿工(miner)。 searcher搜索以太坊上面有利可图的交易(transaction)，例如Dex套利、三明治套利、清算交易、NFT minting等等，他们通过智能合约实现一笔复杂的交易，发送给到mem pool中，矿工将mem pool中的所有交易排序、打包、执行，searcher会将交易利润的一部分作为燃油费(gas fee)支付给矿工，矿工则根据燃油价(gas price)的大小决定在下一个区块中这些交易的顺序(indexing)。 注：g...]]></description>
            <content:encoded><![CDATA[<h2 id="h-mev" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">MEV</h2><blockquote><p>最大可提取价值 (Maximal Extractable Value, 简称MEV)</p><p>矿工 (或验证者、序列器) 在其生产的区块中通过其能力任意打包、排除或重新排序交易可以获得一定的利润，而 MEV 便是衡量该利润的一种度量。</p></blockquote><p>MEV的详细解释请参考：</p><div data-type="embedly" src="https://ethereum.org/en/developers/docs/mev/" data="{&quot;provider_url&quot;:&quot;https://ethereum.org&quot;,&quot;description&quot;:&quot;An introduction to maximal extractable value (MEV)&quot;,&quot;title&quot;:&quot;Maximal extractable value (MEV) | ethereum.org&quot;,&quot;thumbnail_width&quot;:1504,&quot;url&quot;:&quot;https://ethereum.org/developers/docs/mev/&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/a2f4796307f924bab5bfc9c7cc5d2e56c6d4cd00a64075af07b94771f8b35745.png&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;ethereum.org&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:940,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1504,&quot;height&quot;:940,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/a2f4796307f924bab5bfc9c7cc5d2e56c6d4cd00a64075af07b94771f8b35745.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/a2f4796307f924bab5bfc9c7cc5d2e56c6d4cd00a64075af07b94771f8b35745.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://ethereum.org/en/developers/docs/mev/" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>Maximal extractable value (MEV) | ethereum.org</h2><p>An introduction to maximal extractable value (MEV)</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://ethereum.org</span></div><img src="https://storage.googleapis.com/papyrus_images/a2f4796307f924bab5bfc9c7cc5d2e56c6d4cd00a64075af07b94771f8b35745.png"/></div></a></div></div><p>MEV有两个参与方，搜寻者(searcher)和矿工(miner)。</p><p>searcher搜索以太坊上面有利可图的交易(transaction)，例如Dex套利、三明治套利、清算交易、NFT minting等等，他们通过智能合约实现一笔复杂的交易，发送给到mem pool中，矿工将mem pool中的所有交易排序、打包、执行，searcher会将交易利润的一部分作为燃油费(gas fee)支付给矿工，矿工则根据燃油价(gas price)的大小决定在下一个区块中这些交易的顺序(indexing)。</p><p><em>注：gas_fee = gas_price * gas_use</em></p><p>还有一种特殊类型的searcher，他们不会自己去搜索机会，而是会观察mem pool中别的searcher提交的交易，把交易的签名者变成自己的钱包地址，以更高的gas price发送给矿工抢先成交，这种人统称为抢跑者(front runner，有办法可以避免自己的交易被front running，在后面的教程中会详细说明)。</p><p>gas price非常重要，因为它决定了交易的顺序。在以太坊黑暗森林里不只一个猎人，当一个机会出现时会有许多searcher发现并提交自己的交易，在下一个区块中如果你的交易没有排名第一，那么就会失败，因为利润已经被其它的searcher抢走了。这就不可避免的出现一场gas战争(gas war)，seacher会把交易利润的90%以上变成gas fee支付给矿工，以提高自己的排名。如果交易失败，矿工并不会把gas fee返还给searcher。最后造成恶性竞争，网络拥堵，而矿工成了这场战争的唯一赢家。</p><h2 id="h-flashbots" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Flashbots</h2><p>为了解决这种乱象，Flashbots应运而生。Flashbots提供这样一种服务，searcher不用把交易发送到mem pool，而是把一笔或者多笔交易捆绑(bundle)直接发送给矿工，如果成功开采下一个区块的矿工恰好也运行了Flashbots的客户端(Flashbots的哈希率目前大概是80%)，他就会按照如下规则执行所有bundle：</p><ol><li><p>模拟独立执行所有的bundle，记录每个bundle的price，bundle_price =矿工从bundle中获得的总收益/bundle的总gas_use，具体计算公式参考：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.flashbots.net/flashbots-auction/searchers/advanced/bundle-pricing">https://docs.flashbots.net/flashbots-auction/searchers/advanced/bundle-pricing</a></p></li><li><p>将bundle_price从高到低排序，按照顺序模拟执行一遍，把包含失败(revert)交易的bundle剔除掉，剩下成功的bundle按照既定顺序打包上链(include)，在一下个区块中，这些bundle会排在最前面</p></li><li><p>如果一个bundle中包含多笔交易，只要其中一笔交易失败，整个bundle都不会被include，searcher也不会有任何损失(但是有5%的概率发生uncle bandit攻击，bundle会被泄露到mem pool中被其它矿工执行，如何避免被uncle，参考<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.flashbots.net/flashbots-protect/rpc/uncle-bandits">https://docs.flashbots.net/flashbots-protect/rpc/uncle-bandits</a>)</p></li></ol><p>由于交易不发送到mem pool，searcher就不用担心被抢跑，由于失败的交易不会上链，也不用担心交易失败造成的燃油费损失，避免了大规模gas war造成的网络拥堵。接下来，searcher只需关心如何提高自己的bundle_price获得排名第一的位置。</p><ol><li><p>提高矿工的总收益，即把交易利润中的更多比例变成矿工费</p></li><li><p>减少gas_use，通过优化代码减少交易中gas使用量</p></li></ol><p>支付矿工费有两种方式：</p><ol><li><p>maxPriorityFeePerGas(tip price)，在EIP-1559中，gas_price = base_price + tip_price，其中base_price直接燃烧掉，tip_price支付给矿工。</p></li><li><p>在智能合约中直接通过coinbase.transfer向矿工转账ETH</p></li></ol><p>以上两种方式是完全等价的。</p><h2 id="h-mev" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">MEV套利</h2><p>目前主流的mev套利有三种方式:</p><ol><li><p><strong>三明治夹击(Sandwich Attack)</strong></p></li></ol><p>监控mem pool，发现其他用户提交的swap交易。比如有一笔交易，调用Uniswap Router合约，发送了一笔WETH兑换为USDT的交易，并设置了10%的滑点(slippage)，searcher会在它之前，提前在Uniswap的WETH/USDT池子中将WETH兑换为USDT，然后在它之后将USDT换回WETH，把三笔交易捆绑成一个bundle发送给矿工:</p><p><em>(1) searcher: WETH=&gt;USDT</em></p><p>(2) <em>用户: WETH=&gt;USDT</em></p><p><em>(3) searcher: USDT=&gt;WETH</em></p><p>由于用户的兑换行为提高了USDT的价格，所以searcher会得到更多的WETH，除去本金以及交易成本，获得利润。</p><p>searcher会精心计算WETH的初始数量，保证用户的兑换结果恰好在10%滑点范围之内，因为如果超出了10%，用户的交易就会失败，从而整个bundle不会被矿工include。</p><p><strong>2. 背后套利(BackRun)</strong></p><p>监控mem pool，发现其它用户提交的swap交易。比如有一笔交易，调用Uniswap Router合约，发送了一笔WETH兑换为USDT的交易，由于数额巨大，该交易如果完成，将会显著抬高WETH/USDT池子中的USDT价格，和其它的Dex(比如Curve)出现价差。searcher会在它之后，在Uniswap和Curve之间进行套利，两笔交易捆绑成一个bundle发送给矿工：</p><p><em>(1) 用户: Uniswap WETH=&gt;USDT</em></p><p><em>(2) searcher: Curve(WETH=&gt;USDT) =&gt; Uniswap(USDT=&gt;WETH)</em></p><p>由于Uniswap中USDT的价格比Curve中要高，所以searcher低买高卖，获得更多了WETH，除去本金以及交易成本，获得利润。</p><p><strong>3. 普通套利</strong></p><p>由于有些用户提交的swap交易是很隐蔽的(比如使用private transaction，或者通过dex aggregator进行)，所以背后套利的机会并不能全部被发现，会有一些漏网之鱼被mev searcher所忽略。这就导致在一个区块结束后，仍然会出现Dex之间的套利机会，这时searcher只要发现这些机会，同样可以把一笔套利交易捆绑成bundle发送给矿工，获得利润。</p><h2 id="h-mev" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">MEV套利的风险</h2><p>理论上mev套利是无风险的，但是对于三明治夹击，有一种情况需要注意。</p><p>2021年4月，有人部署了一种称为沙氏门菌的token合约(Salmonella token)，在Uniswap中添加流动性，并发送一笔钓鱼swap交易，吸引机器人进行三明治夹击，机器人的第一笔交易用100个ETH买入token，第二笔交易卖出token失败，导致100个ETH留在池子中。</p><p>三明治机器人一般会通过两种措施防止这种瘸腿事故的发生：</p><ol><li><p>在发送bundle之前会先在测试环境模拟，如果模拟失败则不发送</p></li><li><p>在第二笔卖出token的交易成功后再向矿工支付贿赂，这样如果交易失败，矿工不会收到贿赂，从而不会include bundle</p></li></ol><p>但是这个Salmonella token有效地绕过了这两项防御措施，实施了对机器人的攻击，具体可参考：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/bertcmiller/status/1381296074086830091"><em>https://twitter.com/bertcmiller/status/1381296074086830091</em></a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">闪电贷</h2><p>在背后套利和普通套利中，由于套利是在一笔交易中完成的，所以可以通过闪电贷获得初始本金。</p><blockquote><p><strong>闪电贷(Flashloan)</strong>，是一种不需要抵押品的贷款，贷款和还款在同一笔交易内完成。</p></blockquote><p>一些Defi协议会提供这种闪电贷，通过乐观转账(optimistic transafer)，先向用户发放贷款，在回调函数中用户实现套利交易并归还贷款，协议只收取一部分手续费(AAVE)，或者0手续费(KeeperDao, 提供WETH/DAI/USDC/renBTC四种token的闪电贷)。这样一笔套利交易就会变成：</p><p><em>(1) searcher: 从KeeprDao闪电贷WETH</em></p><p><em>(2) searcher: Curve(WETH=&gt;USDT) =&gt; Uniswap(USDT=&gt;WETH)</em></p><p><em>(3)searcher: 向KeeperDao归还WETH</em></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">闪电兑</h2><p>有一些Dex提供闪电兑(Flashswap)，比如通过Uniswap把 USDT兑换为WETH，可以调用Uniswap池子的swap()函数，先通过乐观转账从池子中获得WETH，在回调函数中，用户实现套利交易并归还一定数量的USDT，这样相当于通过Uniswap完成了一次USDT=&gt;WETH的兑换，并且账户中不需要有USDT作为本金。</p><p><em>searcher: Uniswap(USDT=&gt;WETH) =&gt; Curve(WETH=&gt;USDT)</em></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>在解释了mev套利的基本概念之后，在下一讲我会带你编写一个简单的Solidity智能合约，实现闪电贷的功能。</p><p><em>欢迎来即刻App与我互动，即刻账号: 月影007</em></p>]]></content:encoded>
            <author>yueying007@newsletter.paragraph.com (yueying007)</author>
        </item>
        <item>
            <title><![CDATA[区块链开发课第一讲 引言与准备]]></title>
            <link>https://paragraph.com/@yueying007/NpailCi4YplaG68bCFea</link>
            <guid>NpailCi4YplaG68bCFea</guid>
            <pubDate>Thu, 14 Apr 2022 16:18:54 GMT</pubDate>
            <description><![CDATA[区块链开发课第一讲 引言与准备引言大家好，我是月影007。 2021年5月，我从一家对冲基金辞职，在家做全职奶爸。我第一次打开以太坊浏览器etherscan.io，看着不断跳动的区块，以及一笔笔晦涩难懂的transaction，从此不小心闯入了区块链的世界，并被这里深深吸引。我带着强烈的好奇心，搜集区块链开发的技术文档，在Youtube上聆听国外技术大神的课程，一步步的从小白成长为开发者。我开发的以太坊套利机器人，帮助我在短短的半年时间内实现的财务自由，赚取了比过去十年还多的财富(将近500个ETH)。 回顾这一年，经历过大大小小的失败，也收获了许多惊喜，作为一个从quant转行crypto的程序员，我深知国人探索区块链开发是一件多么不容易的事情，几乎找不到系统的中文资料、中文视频，只有靠自己摸索。为了避免后来的人走弯路、踩坑，我决定将我在这个领域的经验分享出来，制作成一系列教程，以连载的方式更新，同时将源代码同步在Github上更新。 我会从易到难，带你一起开发一个可以用于生产的套利机器人。在学习完此教程之后，我希望你可以在我的基础上迭代更新，并实现盈利。即使你以后不想做套利，...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">区块链开发课第一讲 引言与准备</h2><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">引言</h2><p>大家好，我是月影007。</p><p>2021年5月，我从一家对冲基金辞职，在家做全职奶爸。我第一次打开以太坊浏览器etherscan.io，看着不断跳动的区块，以及一笔笔晦涩难懂的transaction，从此不小心闯入了区块链的世界，并被这里深深吸引。我带着强烈的好奇心，搜集区块链开发的技术文档，在Youtube上聆听国外技术大神的课程，一步步的从小白成长为开发者。我开发的以太坊套利机器人，帮助我在短短的半年时间内实现的财务自由，赚取了比过去十年还多的财富(将近500个ETH)。</p><p>回顾这一年，经历过大大小小的失败，也收获了许多惊喜，作为一个从quant转行crypto的程序员，我深知国人探索区块链开发是一件多么不容易的事情，几乎找不到系统的中文资料、中文视频，只有靠自己摸索。为了避免后来的人走弯路、踩坑，我决定将我在这个领域的经验分享出来，制作成一系列教程，以连载的方式更新，同时将源代码同步在Github上更新。</p><p>我会从易到难，带你一起开发一个可以用于生产的套利机器人。在学习完此教程之后，我希望你可以在我的基础上迭代更新，并实现盈利。即使你以后不想做套利，你在教程中掌握的技能也会帮助你快速建立一个DAPP，或者启动一个NFT项目。</p><p>教程分为三大块:</p><ol><li><p>以太坊套利机器人的原理及实现</p></li></ol><p>2. Solidity智能合约的开发、测试及部署</p><p>3. 基于web3.py的开发、测试及部署</p><p>我会假设你是一个完全没有编程经验的小白，通过每一步的讲解，带你了解以太坊、DAPP、DEFI、DEX、ERC20、NFT等等知识。在开发的过程中用到的工具软件、第三方库以及第三方服务，我会在讲解的同时提示如何使用。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">准备</h2><p>首先，准备一台安装有Linux系统的电脑，教程中使用的是Ubuntu 18.04。</p><p>打开Terminal，更新软件:</p><pre data-type="codeBlock" text="sudo apt update
sudo apt upgrade
"><code>sudo apt <span class="hljs-keyword">update</span>
sudo apt upgrade
</code></pre><p>安装软件: Git/nodejs/Truffle/Ganache</p><pre data-type="codeBlock" text="sudo apt install git nodejs
npm install -g truffle
npm install -g ganache-cli
"><code>sudo apt install git nodejs
npm install <span class="hljs-operator">-</span>g truffle
npm install <span class="hljs-operator">-</span>g ganache<span class="hljs-operator">-</span>cli
</code></pre><p>安装IDE(看个人喜好, vscode/pycharm都可以，教程中使用pycharm)</p><p>pycharm下载: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.jetbrains.com/zh-cn/pycharm/download/#section=linux">https://www.jetbrains.com/zh-cn/pycharm/download/#section=linux</a></p><p>选择Community版本,下载到~/Downloads</p><pre data-type="codeBlock" text="cd Downloads
tar -xvzf pycharm-community-2022.1.tar.gz
cd ./pycharm-community-2020.1/bin
./pycharm.sh
"><code>cd Downloads
tar <span class="hljs-operator">-</span>xvzf pycharm<span class="hljs-operator">-</span>community<span class="hljs-number">-2022.1</span>.tar.gz
cd ./pycharm<span class="hljs-operator">-</span>community<span class="hljs-number">-2020.1</span><span class="hljs-operator">/</span>bin
./pycharm.sh
</code></pre><p>在登录界面中点击Configure-Create Desktop Entry,创建桌面快捷方式</p><p>最后，请收藏以下两个网站：</p><div data-type="embedly" src="https://docs.soliditylang.org/en/latest/" data="{&quot;provider_url&quot;:&quot;https://docs.soliditylang.org&quot;,&quot;title&quot;:&quot;Solidity - Solidity 0.8.31-develop documentation&quot;,&quot;url&quot;:&quot;https://docs.soliditylang.org/en/latest/&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;Soliditylang&quot;,&quot;type&quot;:&quot;link&quot;}" format="small"></div><div data-type="embedly" src="https://web3py.readthedocs.io/en/stable/" data="{&quot;provider_url&quot;:&quot;https://web3py.readthedocs.io&quot;,&quot;description&quot;:&quot;Python Web3 SDK for Ethereum and EVM blockchains&quot;,&quot;title&quot;:&quot;gm - web3.py 7.14.0 documentation&quot;,&quot;url&quot;:&quot;https://web3py.readthedocs.io/en/stable/&quot;,&quot;thumbnail_width&quot;:1500,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/c0ccaa9d276385951119ee0ed12a089ca844b3b4034337795bdf558c241b11e6.jpg&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;Web3py&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:500,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1500,&quot;height&quot;:500,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/c0ccaa9d276385951119ee0ed12a089ca844b3b4034337795bdf558c241b11e6.jpg&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/c0ccaa9d276385951119ee0ed12a089ca844b3b4034337795bdf558c241b11e6.jpg"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://web3py.readthedocs.io/en/stable/" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>gm - web3.py 7.14.0 documentation</h2><p>Python Web3 SDK for Ethereum and EVM blockchains</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://web3py.readthedocs.io</span></div><img src="https://storage.googleapis.com/papyrus_images/c0ccaa9d276385951119ee0ed12a089ca844b3b4034337795bdf558c241b11e6.jpg"/></div></a></div></div><p>准备工作完成了，下一讲我将会讲解以太坊套利的基本原理，并开始编写一个简单的Solidity智能合约。</p><p><em>欢迎来即刻App与我互动，即刻账号: 月影007</em></p>]]></content:encoded>
            <author>yueying007@newsletter.paragraph.com (yueying007)</author>
        </item>
    </channel>
</rss>