<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>智能合约 &#8211; 张三太爷</title>
	<atom:link href="https://www.somedoc.net/tag/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.somedoc.net</link>
	<description>看前面，黑洞洞</description>
	<lastBuildDate>Wed, 30 Sep 2020 09:16:41 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.7.1</generator>

<image>
	<url>https://www.somedoc.net/wp-content/uploads/2016/12/cropped-dandycheung-1-32x32.jpg</url>
	<title>智能合约 &#8211; 张三太爷</title>
	<link>https://www.somedoc.net</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>智能合约的迁移</title>
		<link>https://www.somedoc.net/2020/09/30/%e6%99%ba%e8%83%bd%e5%90%88%e7%ba%a6%e7%9a%84%e8%bf%81%e7%a7%bb/</link>
					<comments>https://www.somedoc.net/2020/09/30/%e6%99%ba%e8%83%bd%e5%90%88%e7%ba%a6%e7%9a%84%e8%bf%81%e7%a7%bb/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Wed, 30 Sep 2020 09:16:41 +0000</pubDate>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[区块链]]></category>
		<category><![CDATA[智能合约]]></category>
		<guid isPermaLink="false">https://www.somedoc.net/?p=4618</guid>

					<description><![CDATA[近期有若干代币都发生了被盗的事件。被盗的原因有的是自身的管理 <a href="https://www.somedoc.net/2020/09/30/%e6%99%ba%e8%83%bd%e5%90%88%e7%ba%a6%e7%9a%84%e8%bf%81%e7%a7%bb/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>近期有若干代币都发生了被盗的事件。被盗的原因有的是自身的管理问题，有的是合作伙伴的管理问题。但是被盗之后如何处理，是个需要解决的客观存在。</p>
<p>一个办法是，部署新的合约。在新的合约里，1. 把所有持币者的余额妥善设置，通常是选定一个时间点/块高度，将老合约里的数据作为快照保存下来，以便将之同步到新合约（显然，盗币者/黑客的余额要么保留在新合约的大池子里，要么划拨到一个特殊的账户地址下）；2. 把盗币者/黑客的地址列入黑名单（显然，合约需要增加这种机制）；3. 与相关交易所进行接洽，使其更新代币合约地址为新的。</p>
<p>看上去不算复杂，而且最后一步其实并非技术性工作。然而在实际的操作中，难点很可能是第一时间都想象不到的：得到持币者列表。出于能耗/资源方面的考虑，合约实现时，通常会将用户余额存储到一个 map 数据结构内，key 为 address，value 为余额。听上去信息倒是完整，只可惜这个 map 数据类型，在 Solidity 的实现中（事实上在 EVM 层面也一样）是不可遍历的。</p>
<p>如果按照踏实的路线，那恐怕就是从链上的区块数据进行整理了。但也不是一点巧劲也借不上。etherscan 站点上会有 holder 的披露，其数量有限制，但对于有些代币来说恐怕已经足够使用。</p>
<p>那还有没有其他的更新/升级智能合约的方式呢？随着智能合约应用的广泛和深入，许多人对于智能合约初期的“一旦部署、无法更改”的这一看似最大的优点也产生了不同的看法。没有什么的完美的，也没有什么是不朽的，修订永远是需要的。智能合约无论怎么说，也是程序，对于程序，那个“永远的中间层”似乎更接近不朽。所以，智能合约可以使用代理合约的模式，来包裹实现，呈现出可以更改和升级的特征。这个代理合约，对于程序员来理解的话，可以认为是 C 语言里面的指针。</p>
<p>很显然，随着进一步的发展，业务模块仍将朝更明确、更独立的方向发展。管理员也好，预言机也好，业务的真实实现也好，都有可能成为可插拔的模块。</p>
<hr />
<p>附，两年前的一篇文章，作者：Trail of Bits Blog。</p>
<h2>如何实现智能合约的迁移</h2>
<p>虽然相比其他互联网技术，智能合约等区块链技术相对安全，但是并非绝对安全，即使是零漏洞的合约也有可能被窃取的私钥劫持。先前的 Bancor 和 KICKICO 黑客事件表明：攻击者可以损害智能合约钱包。在这些攻击中，即使合约具备可升级性机制，也可能无法修复已部署的智能合约。**唯一的解决办法是重新部署并正确初始化新的合约实例，以便为用户恢复功能。**</p>
<p>因此，所有智能合约开发者必须在合约设计阶段整合一个迁移程序。此外，企业必须做好在合约损害事件发生时实施迁移的准备。</p>
<p>迁移过程有两个步骤：<br />
1、恢复要迁移的数据<br />
2、将数据写入新合约</p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong><span class="md-plain">第一步：数据恢复</span></strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">你需要从区块链的某个特定区块中读取数据。</span><span class="md-pair-s "><strong><span class="md-plain">要想从损害事件（黑客攻击或故障）中恢复数据，你需要在事件发生之前使用这个区块，或者过滤攻击者的操作。</span></strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">如果可以的话，请暂停合约。这对于用户来说更加透明公平，并能阻止攻击者盯上那些对迁移不知情的用户。</span></p>
<p class="md-end-block md-p"><span class="md-plain">数据恢复的具体操作取决于你的数据结构。</span></p>
<p class="md-end-block md-p"><span class="md-plain">对于简单类型的公共变量（public variables，例如 uint 或 address）来说，通过它们的 getter 来检索特定值就可以了。而对于私有变量（private variables），你可以依赖事件，也可以计算变量的内存偏移量，然后使用 getStorageAt </span><span class="md-pair-s "><span class="md-plain">[4]</span></span><span class="md-plain"> 函数检索它的值。</span></p>
<p class="md-end-block md-p"><span class="md-plain">由于元素的数量是已知的，因此数组也很容易恢复。</span></p>
<p class="md-end-block md-p"><span class="md-plain">至于映射（mappings）的话，情况有点复杂。由于键（Keys）在映射过程中不会被存储，所以你需要将它们进行恢复才能访问对应的值（Values）。</span><span class="md-pair-s "><strong><span class="md-plain">为了简化链下追踪的过程，建议在值被存储在映射中时触发事件（emit events）。</span></strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">在 ERC20 代币合约中，可以通过追踪代币的 Transfer 事件的地址来获取代币持有者列表。这个过程很难。</span></p>
<p class="md-end-block md-p"><span class="md-plain">对此，有两个帮助方案：</span><span class="md-pair-s "><strong><span class="md-plain">第一，扫描区块链并自行检索持有者；第二，依靠以太坊区块链的公开 Google BigTable 存档。</span></strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">如果不熟悉 web3 API 而无法从区块链中提取信息，那么可以使用 ethereum-etl，其提供了一系列脚本来简化数据提取的过程。</span></p>
<p class="md-end-block md-p"><span class="md-plain">如果没有已经完成同步的区块链，还可以使用 Google BigQuery API。图 1 展示了如何通过 BigQuery 来收集某个特定代币的所有地址：</span></p>
<p></p><pre class="crayon-plain-tag">SELECT from_address FROM `bigquery-public-data.ethereum_blockchain.token_transfers` AS token_transfers 
WHERE token_transfers.token_address=0x41424344
Union 
DISTINCT SELECT to_address FROM `bigquery-public-data.ethereum_blockchain.token_transfers` AS token_transfers 
WHERE token transfers.token address=0x41424344</pre><p></p>
<p class="md-end-block md-p"><span class="md-plain">图 1：利用 Google BigQuery 来恢复那些与在 <pre class="crayon-plain-tag">0x41424344</pre> 这个地址中的代币相关联的 <pre class="crayon-plain-tag">Transfer</pre> 事件的所有地址</span></p>
<p class="md-end-block md-p"><span class="md-plain">BigQuery 提供对区块号的访问，因此可以将查询结果调整为返回特定区块的交易。</span></p>
<p class="md-end-block md-p"><span class="md-plain">一旦恢复了所有代币持有者的地址，就可以离线查询 <pre class="crayon-plain-tag">balanceOf</pre> 函数以恢复与每个持有者相关的余额，同时过滤余额为零的帐户。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">现在知道了如何检索将要迁移的数据，接下来要将数据写入新合约。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong><span class="md-plain">第二步：数据写入</span></strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">完成数据收集后，需要开启新合约。</span></p>
<p class="md-end-block md-p"><span class="md-plain">对于简单变量，可以通过合约的构造函数来设置相应的值。</span></p>
<p class="md-end-block md-p"><span class="md-plain">如果你的数据无法保存在单笔交易中，那么情况会有点复杂，成本也会略高。每笔交易都包含在某个区块中，该区块限制了其交易可以使用的 gas 总量（即所谓的 GasLimit）。如果某笔交易的 gas 成本接近或超过此限制，那么矿工将不会将其打包进该区块内。因此，如果想要迁移大量数据，那么必须将数据迁移拆分成多笔交易。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong><span class="md-plain">这类情况的解决方案是：在合约中添加初始化状态，只有合约拥有者才能更改状态变量，并且用户无法执行任何操作。</span></strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">对于 ERC20 代币，上述过程将需要以下步骤：</span></p>
<p class="md-end-block md-p"><span class="md-plain">1、在初始化状态下部署合约；<br />
</span><span class="md-plain">2、迁移余额；<br />
</span><span class="md-plain">3、将合约的状态移至生产状态。</span></p>
<p class="md-end-block md-p"><span class="md-plain">初始化状态可以通过使用 OpenZeppelin 提供的 Pausable 功能和指示初始化状态的布尔值（boolean）来实现。</span></p>
<p class="md-end-block md-p"><span class="md-plain">为了降低成本，我们可以使用 <pre class="crayon-plain-tag">batchTransfer</pre>（批量传输）函数（该函数允许在单笔交易中设置多个帐户）来实现余额的迁移：</span></p>
<p></p><pre class="crayon-plain-tag">/**
* @dev Initiate the account of destinations[i] with values[i].The function must only be called before
* any transfer of tokens (duringInitialization). The caller must check that destinations are unique addresses.
* For a large number of destinations, separate the balances initialization in different calls to batchTransfer.
* @param destinations List of addresses to set the values
* @param values List of values to set
function batchTransfer(address[] destinations, uint256[] values)
    duringInitialization onlyOwner external {
    require(destinations.length == values.length);

    uint256 length = destinations.length;
    uint i;
    for(i=0; i&lt;length; i++) {
        balances[destinations[i]] = values[i];
        emit Transfer(0x0, destinations[i], values[i]);
    }
}</pre><p></p>
<p class="md-end-block md-p"><span class="md-plain">图 2：<pre class="crayon-plain-tag">batchTransfer</pre> 函数示例</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong><span class="md-plain">建议</span></strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">在合约部署之前做好迁移程序的功课。</span></p>
<p class="md-end-block md-p"><span class="md-plain">使用事件（events）来提高数据追踪的效率。</span></p>
<p class="md-end-block md-p"><span class="md-plain">如果想要部署可升级合约，那么必须准备好迁移程序，因为你的密钥可能会受到损害，或者你的合约可能会受到错误且不可逆转的操纵。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">智能合约带来了新的开发范式——其不可变性要求用户重新思考搭建应用的方式，并且需要更透彻全面的设计和开发过程。</span></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2020/09/30/%e6%99%ba%e8%83%bd%e5%90%88%e7%ba%a6%e7%9a%84%e8%bf%81%e7%a7%bb/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
