以太坊之最全攻略解析与案例分享

一、“以太坊”简介

① 什么是“区块链”?

(A)“区块链”概念

  • “区块链”是指特定网络中一系列独立计算机处理和维护的所有交易的公开记录本,比起用中心化的方式管理这些交易数据库(就如 Amazon 或 Facebook 控制自己数据的方式),区块链上不存在单个数据所有者,使之去中心化,在这个网络中的计算机遵循着特定的规范和机制,以保存所有交易的记录,这些规范让计算机能够同意网络中发生的所有(交易)行为,或就其达成共识。
  • 网络中的计算机是独立的,因此,计算机 D 和 E(和 F 和 G …)可能不认识计算机 A、B 或 C。区块链的一系列规则意味着,单个计算机无须独自验证其他计算机提供的数据的准确性,就能对区块链历史上发生的交易达成一致。换句话说,计算机之间不用信任彼此就可以达成共识。在网络中的计算机之间,这种去信任共识机制具有举足轻重的重要性。区块链数量十分庞大,每一条链遵循着自行设置的规范达成共识。以太坊区块链致力于为给不同领域中酷炫的、新颖的应用提供基础设施服务与设计空间,比如游戏、艺术、金融和社交媒体类的应用。

(B)共识机制

  • “共识机制”是当区块链上的所有计算机都同意发生在网络中的事实,这就是“达成共识”。单个计算机之间根据区块链的规则达成共识,并且每一次将新交易打包至链上,所有计算机都需要经历达成共识的全过程。一旦这些计算机达成共识,交易区块则被打包到区块链上,成为网络历史记录中的一部分。大致的意思是,假设计算机对每次新交易添加至链上的行为无异议,那就相当于同意了区块链的整个历史记录,因为它们不得不参与其中的每一环。
  • 共识是一个支撑整个区块链世界的重要概念,如何在不信任网络中任何参与者的情况下,验证上面发生的交易,这是一个非常难以解决的人类问题,而区块链是这一问题的最优解。不同规范(或是“共识机制”)可以促使个人计算机在区块链中达成共识。
  • 下面介绍两种主要共识机制:
    • 工作量证明(PoW):在工作量证明机制中,计算机之间以竞赛的方式解决复杂的数学问题。网络会给第一台解决问题的计算机提供经济奖励,这激励着计算机背后的人不断更新并运行节点(换言之,确保网络一直处理交易)。这种竞相解决计算密集型数学问题的过程就叫“挖矿”。基本上,经验证为合法的交易,可以安全地添加到区块链上,这也是比特币区块链以及当前以太坊区块链在实施的规则。工作量证明机制也有它的缺点,主要是最终、最强大的(和最昂贵的)计算机能够更快地解决问题,因此,富者衡富;在计算机上解决高难度的数学问题需要消耗很多精力,这已经成为整个区块链最为人诟病的一点。
    • 权益证明机制(PoS):相对于耗费大量算力来达成共识(如 PoW),权益证明机制则是利用惩罚的风险(和一些经济激励)约束/激励参与者。在权益证明机制中,参与者筹备资金(技术角度而言,他们“质押”自己的资金),并换取进入随机选拔程序的资格。被随机选中的计算机需要验证下一批即将到来的交易。当随机选中的计算机正确地处理交易(在权益证明机制的限制范围内),可以获得奖励。如果被网络随机选中的参与者违反了权益证明机制的规定,那么这个参与者质押的资产就会减少(或“被罚没”)。PoS 区块链不会同时请求网络中的所有计算机破解那些数学难题,而是通过随机选取计算机进行交易验证。略过繁重的计算过程可以减轻 PoW 机制出现的两大主要问题。这也是以太坊在规划2022年部署下一代区块链时,打算启用这套共识机制的部分原因。

(C)节点

  • “节点”是为了以太坊区块链的运作,网络中的参与者需要运行特定的软件,协助他们与区块链进行交互。我倾向于认为,每个节点作为独立计算机运行着以太坊软件。同样地,节点(网络中的参与者)越多就越去中心化,但有时,维护所有节点有些麻烦。
  • 不同的节点针对不同的目的:
    • 全节点:用来储存完整的区块链数据,帮助区块进行验证并打包到链上,这类节点还为过去的交易提供有效性证明;
    • 轻节点:功能在设计上相对少于全节点,比起储存完整的区块链数据,轻节点仅仅储存较少量过去交易的证明,这类节点让更多人参与到网络中,因为它们存储更少的数据,运行起来更加经济;
    • 归档节点:是以太坊世界的程序库/维基百科词典,它们储存全节点所有的数据,甚至更多,分析工具和钱包提供商也许会利用归档节点来拉取很久之前的信息。
  • 客户端:这是以太坊的软件,使得计算机(节点)能够同以太坊网络进行交互。单个节点可以选择他们想用的客户端软件,但多用一些不同类型的客户端对于去中心化至关重要,以免其中某个客户端出现 bug 或者问题。现在有执行客户端和共识客户端两种类型,但这不在指南介绍的范围内。现如今,链上有很多可用的客户端,最近以太坊社区争取让最大的一些节点运行机构多样化其运行节点的客户端。重要的是,任何想要参与运行以太坊网络的用户,都可以创建他们自己的客户端,这意味着用户不必信赖第三方实体为其验证区块链。
  • 状态:以太坊区块链的状态指的是在任何特定时间点区块链上的账户余额情况。一旦有新的事物产生(比如处理一个新的交易区块),那么状态则会更新并精确地反映区块链在打包新交易后的状况。以太坊的状态保存不同账户及其余额的信息。换言之,一旦区块链验证新的交易,状态也会随之更新,利用刚添加的新交易信息反映出新的账户余额。

② 如何将区块打包到区块链上?

  • 一个用户可能想用以太坊区块链发送一些资金给另一个用户,一旦发起方用户这边发起了交易,这笔交易就要在接收方用户收到这笔钱之前,被打包至交易链上。当这样一笔交易打包到以太坊区块链上,各个节点需要在交易打包上链并变成其一部分历史之前,完成整个达成共识的过程。
  • 如下所示,它讨论的是上述的简单交易,就是一个用户发送资金给另一位用户,这笔交易被打包成区块,等待节点一同达成共识后将它添加至链上:

以太坊之最全攻略解析与案例分享_第1张图片
以太坊之最全攻略解析与案例分享_第2张图片

  • 事实上,区块链只是所有用户对于发生在网络中的历史交易达成共识的方式,而区块链状态是已实时更新过新交易的账户余额:

以太坊之最全攻略解析与案例分享_第3张图片

  • 智能合约在一定程度上,智能合约类似于物理世界中使用的传统合约的电子版。在传统合约中(例如雇佣合同或者公寓租赁),两个及以上的合约方建立一套条款,再通过律师和司法系统执行合约中的条款。而在智能合约中,两个及以上的用户也是创建一套规则,却不是通过司法体系执行合约,而是由程序代码编写成智能合约,发送到区块链(或者部署在区块链上)。智能合约会根据已编好的代码自动运行,而不需要律师执行。
  • 智能合约是经由区块内的交易部署到链上的代码,未来的交易可以“调用”或与智能合约交互。举个简单的例子,A 用户希望和 B 用户就比特币在未来两年内的价值打赌,A 用户认为比特币在 2032 年 1 月 1 日时会超过 100,000 美元,而 B 用户则认为比特币会低于这个价格,那么两位用户可以建立一个智能合约,在合约里放置彼此的资金,再约定一条简单的规则:如果比特币在 2032 年 1 月 1 日超过了 100,000 美元,智能合约则释放这些资金给 A 用户,反之,智能合约将资金打给 B 用户。这个交易过程十分简单、直接且去信任。
  • 智能合约允许任何人以去信任的方式在世界计算机上部署代码,并且,也使得任何人去信任式地验证代码的内容。最终,智能合约技术的存在已为一波涌现的去中心化应用浪潮带来巨大的机遇,而没有区块链技术,这些就无法成为现实。

③ 以太币(ETH)

  • 以太币是支持以太坊区块链的原生货币,在工作量证明机制中,(挖矿)奖励通过以太币支付给解决数学难题的计算机,并且参与者在权益证明机制中质押的资金也是用以太币 (需质押 32 ETH),以太币是加密货币的名字,以太坊是网络的名字。
  • 以太坊虚拟机(EVM): 以太坊虚拟机这个名字是指“虚拟的”计算机,它由所有参与以太坊网络的独立小型计算机组成。这样单个的大型计算机并非实际上处于某个位置的“物理层面上的”计算机,而是尤如一台大型(全球性的)计算机一般工作。以太坊区块链的状态就活跃在这台计算机上,并且,当下一个区块打包至链上时,它负责执行状态更新的规范。如果以太坊网络中的用户想将智能合约代码纳入自己的交易中,那么这个代码便会在 EVM 上运行。

④ 以太坊虚拟机如何运作?

  • 对于新手来说,可能没必要了解EVM运行的复杂性,但它是以太坊区块链的重要组成部分,还能帮助我们大致了解去中心化如何规模运作。如下所示:

以太坊之最全攻略解析与案例分享_第4张图片

  • 首先从以太坊区块链在特定时间点的状态开始,左边的方框称作“ 世界状态σ t ” 一笔交易被打包到链上,例如从一个钱包转移以太币到另一个钱包,在图表顶端上的方框,就是“信息调用交易”。在交易发生之前的以太坊状态(再次提示,左边的方框)加上新交易(顶上的方框)的输入数据,都在 EVM 上运行。在这里,EVM 更新区块链的状态,一旦 EVM 更新了状态,新的状态“ World state σ t+1 ”会被储存起来。

⑤ 代币

  • “代币”通常指区块链上的资产,可以代表许多不同类型的资产。举个例子,一般认为代币是可以当成货币的资产,或者是在具体决策过程中给持有者提供投票权的资产 (治理代币),又或者完全可以作为其他东西。代币是加密世界中代表着不同种类资产的价值的原子单位。
  • 同质化代币(fungible token):指的是可以相互转换的一些商品或是物品,即可替换性。这不是一个加密原生术语,一般的货币指的是同质化货币。例如,我口袋里的1美元可以换成你口袋里的 1 美元,并且这两个 1 美元都可以用来买1美元的东西,它们是等价的,当可替代性应用于加密概念中,它指是否可以与其同一集合中的其他加密资产进行互换,我的以太币和你的以太币可以互换。
  • 非同质化代币(NFTs):非同质化代币指的是所有因独一无二的存在而不可互换的数字资产。
  • 虽然 NFT 主要是因为数字艺术和数字藏品出圈的,但它不止于这种表现形式,它可以是任何独一无二的数字资产。数字艺术和数字藏品恰好是 NFT 最早的用例之一,而已经引起了广泛公众的共鸣。这种代币引起了许多人对加密界的兴趣,但我认为诸如无聊猿和 NBATopShot 此类 NFT 项目的兴起导致广大公众低估了在以太坊区块链这样的可信结算层上部署独一无二的数字资产所带来的其他方面的效用。
  • 从概念上看,NFT 还可以应用于许多数字收藏品以外的其他用例。如果一种产品或者服务需要能够验证某种特定数字资产的所有权和其稀缺性的有效性,那么公共区块链上的 NFT 就派上用场了。例如,音乐会场地可能会用 NFT 替代门票,或者视频游戏的设计师可以将那些难以在游戏中获得的资产转为 NFT,由此用户之间可以转让或交易。这个概念还能玩出新花样:一些资产可以既是同质化的,又是非同质化的,这取决于同它们进行比较的集合。例如,如果我持有一个 19 世纪的 1 美元老币,并将它作为收藏品放置于玻璃罐中。这很明显,这 1 美元(非同质化的!)和被揉作一团后塞在口袋的美元新钞截然不同。
  • 不过,如果我将玻璃罐里的 1 美元掏出来去星巴克消费,他们(可能)愿意收下它。这是因为,从某种程度上看,它跟其他一美元纸币是可互换的,尽管从其他角度而言,它们完全不是一回事。

以太坊之最全攻略解析与案例分享_第5张图片

二、如何与创建在以太坊上的应用交互?

① 燃料 (Gas)

  • 与以太坊区块链的每次交互都会消耗成本 (gas),而这个成本取决于以太坊虚拟机运行那段特定代码需要消耗多少算力。由于区块链上的每个区块的空间只能容纳固定数量的交易,而 gas 的概念可以帮助以太坊分配稀缺的区块空间资源。越是复杂的交易可能需要支付越多的 gas 才能完成。比如,从一个钱包发送以太币到另一个也许只需要在虚拟机上运行几行代码,因此,它需要的 gas 少于算力大的交互所需的 gas。
  • Gas 以 ETH 计价,并且用户可以选择支付更多的 gas(通过给计算机支付小费的方式)以加速交易时间,提高交易打包到下一区块的几率。Gwei 在技术上Gas的价格表示为 wei,是 ETH 最小的增量单位。1 wei 等于0.000000000000000001 ETH(1018 wei,也就是用 5 个逗号才能表示 1 ETH),1 gwei 等于1,000,000,000 wei,所以比较 gas 价格时,用 gwei 兑 ETH 的计价方式更为方便。
  • 用户已经习惯了以 gwei 为单位来表示 gas 价格。比如 0.0001 ETH是 1 gwei,这个 gas 费用很低。用户可以使用 Gas.Watch 留意实时的gas价格,Gas 会随着打包进区块链的交易需求上下波动。

② 为什么需要 gas,它如何应用?

  • 负责验证区块链交易的计算机需要在经济上给予激励,如果不发放这些激励,将难以说服他们运维计算机和区块链,而要是链上没有充足的计算机进行运行,就将导致区块链变得过度中心化,仅由几个用户控制。
  • 支付给网络参与者的 gas 会根据打包进区块链的交易需求而波动:

以太坊之最全攻略解析与案例分享_第6张图片

  • Solidity 是一种编程语言,用户可以用它在以太坊区块链中编写智能合约以及创建去中心化应用。重要的是,Solidity 是图灵完备的编程语言,这基本意味着“任何你可以编写成代码的东西都可以用 Solidity 写”。这说明,开发者能够使用 Solidity 在以太坊上开发大量的酷炫玩意。
  • 可组合性:由于智能合约作为开源代码部署在以太坊上,所以,任何人都可以基于这些智能合约构建(或者“分叉”代码并自行改变),这表明以太坊 (以及其他类似区块链) 上的应用是可组合的。可以将可组合性看作是区块链的 API 。尽管按理说早几代前开发者就能够基于其他技术基础设施创建应用,但加密可组合性对比其他领域的不同主要表现在:它所有底层协议都是去中心化的。换言之,开发者无需担忧会有某个中心化实体,掌控全部的底层数据并突然改变平台的规则,或是限制开发者的访问。

③ 可组合性的案例有哪些?在实际中如何应用?

  • 可组合性指的是开发者可以利用已经构建和部署在公链上的其他应用创建新的应用:

以太坊之最全攻略解析与案例分享_第7张图片

  • 以太坊社区已经制定了一套流程以概述社区成员该如何向以太坊协议提出改进建议。这些流程包括提供进行讨论的公共论坛和鼓励社区参与开源,这对于以太坊区块链来说尤为重要,因为它是去中心化的区块链且依赖于全球分布的社区对其进行监督和改进。
  • ERC 是 EIP 的一种类别,具体来说,ERC 是描述“应用级别的标准和协定”的一种 EIP 。这类 EIP 值得在这提一提,因为它是以太坊上最重要且最具知名度的一些使用案例的合约标准的模板。开发者在以太坊上构建时可以使用这些合约标准以节约时间和精力,而不用从头开始。一些广为人知的 ERC 如下:
    • ERC-20 - 这是同质化代币的一种代币标准。
    • ERC-721 - 这是非同质化代币的一种代币标准。
    • ERC-1155 - 这是优化了部分 ERC-20 和 ERC-721 的代币标准,一般应用于碎片化非同质化代币。

④ 为什么想要碎片化(或使其可替代)非同质化代币?

  • 大多数分解后的NFT碎片之间具有可替代性,因此,某个用户持有的蒙娜丽莎面部碎片不会和手部碎片或是背景碎片相斥(即碎片之间是等价的)。这些不同部位的碎片实际上并不是可替代的(比起脸部碎片,我更乐意花更少的钱购买背景碎片)而现实中,用户只会持有整个艺术品的一片小小的可替代碎片。

⑤ 用户和应用如何与以太坊交互?

  • 用户几乎都是通过如 Chrome 等浏览器使用网页应用。这些网页应用使用特定的库(如 web3.js 或者 ethers.js )建成,这些库使得网页应用可以直接同区块链节点实现交互。

以太坊之最全攻略解析与案例分享_第8张图片

  • 开发者建立的应用,通过节点运行客户端软件的方式与以太坊进行交互。在下面的示例中,运行的客户端是 Geth,它是一个用来与以太坊区块链交互的命令行界面。也有像 Infura 这样“节点即服务”的供应商,它让开发者得以便捷地与服务供应商控制的节点进行交互,这和开发者如何利用 AWS 访问服务器空间的过程是类似的。接下来,这些节点可以在以太坊上与智能合约和单个账户余额产生交互。这和当下其他软件产品的“后端” VS.“前端”大不相同。在左下图表中,我们可以看到一个用户如何连接传统的网页应用。
  • 在这个图的旁边,是一个基于以太坊的应用的架构例子。两者极其类似,区别就是,以太坊作为一个后端基础设施服务于加密应用,这使得它具有全球化、无需许可和抗审核的特性。

以太坊之最全攻略解析与案例分享_第9张图片

三、钱包和身份

① 钱包

  • 钱包是将你的资产存在加密钱包中,就好比将现金存在物理钱包里。但是这些加密钱包还储存着代表你和你的行为的信息,例如你交互过的应用以及用该钱包做过的交易。
  • 需要记住的是,根据设计,区块链交易是公开透明的,由此,当你使用钱包在以太坊上做些什么的时候,你的钱包管理着关于这些交易的可追溯、公开的数据。这些可追溯数据强调了 web3 中“持有自己的数据”的理念 —— 你的资产、交易历史、与去中心化应用交互的数据会随着你的钱包移动。而且,与物理钱包相区别的是,许多加密用户会使用多个用途不同的加密钱包。
  • 公钥:这是一行长代码,代表钱包的对外地址。公钥好比你的家庭地址;这个地址是独一无二,不是秘密(公共记录等)。而这个地址对应着一个家庭(或在这个案例中,地址对应着你的一个账户)。
  • 你可能会与想给你寄信或礼物的朋友分享你的地址,但就算有人在当地政府的财产记录中看到你的家庭地址,那也没什么。如果有人看到你的公钥,那也是没问题的。
  • 私钥:在另一方面,私钥是钱包的密码,所以不能让别人知道你的私钥。私钥会对应特定钱包的公钥,因此,如果有人得到了私钥,他们可以完全访问钱包。
  • 私钥就像家里的钥匙,你并不介意有人随机知道了你家的地址,但是如果他们有你家的钥匙,那你必然惴惴不安。再次重申:任何人得到了私钥都可以接入对应的钱包,不要将私钥告诉任何人,也不要储存在别人可以找到的地方。
  • 公钥和私钥的原理:
    • 公钥和私钥是用于加密和验证身份的一种方法,叫做私钥密码学。谨记公钥是面向外部公开的,当用户向其朋友的钱包发起交易时(使用朋友的公钥),就相当于给交易上锁,只有当用户的朋友确实持有接收方钱包的私钥时,才能把锁解开。尽管交易是可见的(因为它存在公链上),但没有特定私钥(私钥对应的持有资产的钱包)的情况下无法“解锁”这些资产。
    • 不管你是以太坊上搭建项目的开发者或者只是用户,一定要了解公钥和私钥的区别,这很重要。误用(或者“放错”)公私钥可能造成严重的资金后果,并且,这和忘记中心化网站上的密码不同,应用开发者无法帮助用户恢复密钥。随着更多用户创建加密钱包并在区块链上交易,这种交易模式将会更为标准化。同时,注意学习曲线以及帮助向其他用户解释也尤为重要。

以太坊之最全攻略解析与案例分享_第10张图片

  • 助记词: 一组助记词(通常是 12 到 24 个随机的词)是钱包在紧急情况下最终的钱包恢复工具。它需要像私钥一样被同等保护起来,因为丢失助记词或者将它保存在会被发现的地方,就意味着钱包的一切都暴露了。用户一定要采取合适的方式保存助记词,保证其安全性和机密性。
  • 钱包应用的开发者无法访问助记词,所以如果读者丢失了自己的密钥和助记词,那么你的钱包就不能再恢复。如果仅仅丢失了私钥,那还可以用助记词来恢复钱包。

② 分类

  • 托管钱包:这类钱包由托管方(任何负责管理钱包资金的中心化实体)负责管理,比如一个常规的 Coinbase 账户就是托管的。这些托管者负责管理钱包(因此,如果用户使用了一个托管钱包,就不必保管自己私钥)中的基本资产,为提供用户更为中心化、更顺畅的用户体验。这种用户体验通常不包括加密原生的身份认证机制,例如,一位用户可以使用 Google 邮箱地址和密码登录 Coinbase 的账户。托管钱包是一种开启加密之旅的好方式,同时也是兑换现金资产为加密货币的实用方法。另一方面,鉴于这些托管者都由中心化机构持有和管理,所以也带来了去中心化旨在解决的一些问题,比如数据所有权、信息流控制以及潜在的监管要求。加密界有句关于托管钱包流行语 —— “无钥即无币”,即便是 Coinbase 的 CEO Brian Armstrong 曾经也提过非托管钱包的重要性,因为托管钱包的提供商会有受到政府监管的风险。对那些倾向于以一种完全去中心化的方式管理自己的资产和交易的用户来说,非托管钱包是更好的选择。
  • 非托管钱包:这类钱包的管理者只是你,软件供应商(如 MetaMask , Argent 和 Rainbow 等)提供用户访问自己钱包的软件,但主要的是,钱包资产存放在链上而不是钱包提供商那里。所以,如果 MetaMask 钱包发生了些什么导致无法访问,那么用户可以跳转到 Rainbow 钱包,导入他们的钱包(不用得到 MetaMask 的允许)并通过 Rainbow 操作自己的资产。还有一种非托管硬件钱包,它的私钥直接保存在物理设备中(通常是看起来像 USB 的小金属物件)。非托管钱包的使用伴随着管理公钥、私钥和助记词的负担,但这种钱包给予用户以自治权(直接持有资产)和访问以太坊世界的唯一身份。以太坊应用允许用户“使用以太坊登录”(Sign in with Ethereum, SIWE ),即“使用自己的非托管钱包登录”。由此,非托管钱包代表了用户的身份,这些钱包扩展了加密界的设计空间,比如关于身份、凭证和所有权的新思维方式。
  • 社会恢复钱包:这是由一些非托管钱包提供商支持的一种钱包恢复策略。这种钱包不需要助记词(有用户丢失过助记词),用户可以委任其社交网络中的其他人,验证钱包是否是否对应于它应该对应的人。通过社交恢复钱包,用户可以基于其社交圈的信任网络作为其非托管钱包的后盾,同时仍然保留非托管钱包的自我托管/去中心化/单点登录的优势,Argent 是社会恢复钱包的一个用例。

③ 用户如何注意钱包的使用安全?

  • 与公钥不同,永远不要将私钥透露给任何人,如果有人获取了你的私钥,那就很麻烦:
    • “地址/公钥:你的邮箱地址(可以共享)
    • 私钥:收件箱的密码(永不共享)
    • 钱包:保存私钥
    • 助记词:私钥恢复系统(永不共享)
    • 密码:可选项:创建新钱包的额外密码(永不丢失)
  • 以太坊域名服务是为以太坊区块链而生的开源域名系统,某种程度上类似于传统网站的域名提供商。ENS 将以太坊上的地址映射为人类可读的名字,因此我才能使用譬如“ brunny.eth ”作为我的地址,而不是这一长串的公钥:0xF67cAEbBbE7b630d137d2901637C02899ED3211b。总的来说,作为公共物品,ENS 域名对以太坊生态系统中的身份而言十分重要,因而它们值得有自己版本的域名系统。
  • 去中心化自治组织(DAOs):DAO是加密原生的组织形式,它可以是基于加密原生规则进行自我管理与组织的公司、非营利机构、社会团体或是其他任何类型的组织。这里的加密原生规则是指类似于社区所有权、透明性和去中心化等概念,而值得注意的是,去中心化有一个频谱,而不是非开即关的两种极端。不似传统公司在实体创建和领导组织架构方面的中心化持有和管理,DAO 则为无中心实体下决策的加密原生项目和商业的经营而设计架构,并致力于争取项目的社区所有权。许多 DAO 的另一个愿景是完全去中心化和民主化的实现。也就是,DAO 的各种决策由主要参与者以民主的方式票选得出。DAO 不仅能针对链上的应用级别产品的变动进行投票,还能发挥奖励和激励系统参与者的作用。

四、去中心化金融

① 去中心化金融(DeFi)

  • 去中心化金融(DeFi):去中心化金融指的是任何的不存在中心把关人且完全在区块链上运行的金融应用、交易所和系统。如今,各式各样的区块链上活跃着数以百计(如果还未达到成千上万的程度)的 DeFi 项目,从去中心化交易所到借贷协议,再到期权和期货合约,应用范围很广。DeFi 应用的首要目标在于重新思考:在没有中央银行掌控权力的世界体系中,如何通过去中心化的形式实现旧式银行系统提供的金融服务。
  • 可以试想一下在股票市场买进股票份额的情景:当 Sally 通过中介(Robinhood、 Charles Schwab 和 Vanguard 等)购买了一股特斯拉股票,这一股会辗转多个中介之手后Sally才能拿到。一般而言,当系统正常运行,这种辗转多个不同中介的行为不会被一般大众发现。但有时会发生糟糕的情况(例如,2008年全球金融危机或是2021年的 Gametop 股票事件),导致系统崩盘(如出现负油价和交易被取消的情况)。系统崩盘后,人们希望寻找这场混乱的罪魁祸首。可当他们开始掘地三尺的时候,却发现传统金融市场远没有他们所想的那样透明。

② 去中心化交易所(DEXs)

  • 它是首个主要的 DeFi 构建块,区块链激活了一种新型交易所,它无须经过不透明的中介环节和半官方机构,就能直接和智能合约进行交易。
  • 还是以 Sally 购买特斯拉股票作为例子,她不再需要通过中介经纪公司(例如 Charles Schwab )买入股票,这种中介会和做市商(比如 Citadel )进行交易,两者都受到于美国清算所(如 DTCC)施加的约束。而是和 Uniswap 智能合约做交易,智能合约的代码都是透明公开的,因此,她可以看到资金流动的过程,不会被非透明的中介蒙蔽双眼。这些去中心化交易所运用区块链技术和经济激励,基本上为任意两种货币搭建了市场(比如 BTC 和 ETH,或者美元和欧元等)。
  • 作为占据市场份额最多的 DEX,Uniswap 如何进行运作。为了解这些去中心化交易所的运作方式,需要先界定一些额外的术语:
    • 流动性提供者(LPs):在上面的 Sally 案例中,它所描述的不透明中介在传统金融系统中确实起到了有效作用:为系统提供流动性。而在传统的金融体系中,Sally 可以随时卖掉自己的股票,几乎任何时间或至少在常规的交易时间内都可以,因为中介便是雇来为 Sally 和其他股民提供流动性的人。那么,去中心化交易所协议中的智能合约哪里来资产给它进行交易?答案是流动性提供者。DEX 给予个体通过提供流动性而获利的机会,当有用户与智能合约交易资产时,系统会给流动性提供者返利一小部分由交易产生的手续费。对 LP 来说,最为知名的是 Unswap 的模式,他们需要在智能合约中存款两种具有相同价值的代币对。再度重申,LP 把存款放进智能合约中,以获取一部分交易手续费。LP 可以将作为流动性存放进去的代币随时提取出来,但这样的话,他们显然无法获得未来交易费用上涨的分红。
    • 自动做市商(AMMs):这是 DEX 的一种类别,自动做市商是指运用算法设置价格的智能合约。在此, Uniswap 的恒定乘积公式 ( x*y=k ) 最有知名度,然而这超出了这篇指南的范围。AMM只是一种无需人为设置价格的公式或机制。
    • 稳定币是现实货币的数字化代表,它们代表着与其挂钩的货币的价值,只不过仅作为数字货币在区块链上流通。DeFi 使得用户能够使用加密资产大展拳脚,却难以使用户和投资者在固定的价格范畴内管理自己的资产,这是由于加密资产的价格并不稳定。在去信任和去中心化的区块链上,稳定币作为一种波动较小的资产存在,同时还作为对比加密资产的参考价格。通常来说,稳定币与美元挂钩,但也有其他的稳定币。不管是中心化还是去中心化的稳定币,每一种都有自己的机制,以维持它们与其锚定的货币在价格上的1:1挂钩关系。诚然,加密货币正在颠覆全球金融系统,然而主要的全球货币(如美元、欧元和日元等)作为参考价格依旧有效。
    • 总锁仓价值(TVL):TVL 是指锁定在特定平台的智能合约中的总额价值。TVL 概念也能应用在 DEX 智能合约以外的语境,因为除交易所以外的其他应用也可能会有流动性提供机制(比如借贷平台)。Uniswap 的总锁价值达几十亿美元,而 2022 年初以太坊上的各种应用加起来 TVL 超过了 1000 亿美元。

③ Uniswap 怎么运作?

  • 当用户想用 Uniswap(或其他交易所)兑换代币时,该用户只需在一个简易的前端界面进行操作即可,这个界面是 Uniswap 基于更加复杂的智能合约构建的。如下图所示,用户可以把ETH(或其他代币)兑换成其他资产,犹如使用自动贩卖机。用户可以连接钱包并将任意一种代币换成其他代币:

以太坊之最全攻略解析与案例分享_第11张图片

  • 然而,幕后到底发生了什么?一起看下图中的蓝色方框,这是 Uniswap 的智能合约,是流动性提供者存放其代币的地方(例子中用代币 A 和代币 B )。蓝色方框的左边描述了 LP 和质押池的关系;LP 存进两种资产,作为交换,他们会收到质押池代币,这种代币相当于流动性提供者可以赎回他们的质押资产的一个凭证。质押池代币可以随时赎回LP最初质押在智能合约的资产(在这里,交易者要警惕接下来提到的“无常损失”)
  • 上图中的另一端是用户,用户在无需接触质押池 LP 的情况下,进入界面并在质押池中将一种代币兑为另一种。并且,用户会支付一小笔手续费,它会均等地分给质押池中的所有 LP 。

以太坊之最全攻略解析与案例分享_第12张图片

五、Layer2 和权益证明机制

① 区块链三角悖论

  • 每一条区块链都涉及三种概念之间的权衡:去中心化、可扩展性以及安全性,一般的共识是( 2022 年早期),以太坊在去中心化和安全性上做得较好,但在可扩展性上稍逊一筹。希望在近期会有一些改进计划可以解决以太坊的区块链三角悖论。
  • 去中心化:比特币白皮书准确地解释了去中心化概念:“只需要一个基于加密学证明而非信任的电子支付系统,允许任何两个意愿方在无需信任第三方的情况下,直接与彼此进行交易”,区块链扮演了基础设施层的角色,使得全世界的用户可以使用自己计算机与彼此互动,而不用经过中介环节。区块链的去中心化就好比一个频谱;如果区块链可以被少数用户关停,或者网络的参与成本过高( gas 费用或配置计算机参与网络的成本),那么区块链则会向中心化的一端倾斜。中心化程度越高,权力垄断和剥削的风险也越高。
  • 安全性:是指基础链被外界攻击或控制的难度。有效的经验法则是 51% 的大多数原则;如果有人能够控制特定链上 51% 的处理交易的计算机,那他们也许可以非法入侵并损害网络的安全性。这里有更深层的技术考量,但 51%的占比帮助用户厘清安全性、去中心化和可扩展性之间的权衡关系。为特定区块链打包交易的独立计算机越多,表明其去中心化和安全性程度越高(更多计算机=有人控制51%的网络节点的概率很低)。然而,网络中的独立计算机越多,也意味着每台计算机需要同更大的计算机网络进行交流,从而导致运行速度下降。
  • 可扩展性:网络运行速度下降意味着需要找到提高可扩展性的方案,当区块链上的交易需求变多时,网络也会随之变得异常拥堵。例如,以太坊也曾有过 gas 费疯涨的时期,尤其是网络需求爆满的时候,这些需求致使交易打包上链的成本水涨船高,同时造成网络拥堵、网络运行速度下降。

② 零知识证明

  • 这个概念并非具体的扩容方案,但它是展开探讨扩容方案之前所要阐明的一个重要概念,零知识证明是一种无需获取特定信息就能验证事物有效性的加密学方法。比如,假设我是一名 Craigslit 的买家,正打算从网络中的任意用户手里购买一台电视。这时,有人私信告诉我,他们手上有我正在找的电视,而他们的资料是匿名的。作为一名买家,我希望在与卖家碰面之前,能确保他们真的有电视。但是卖家却不想将他们的个人信息(驾照、居家地址、室内的图片)泄露给网络中的随机用户。最重要的是,卖家也想要知道我是不是一个真人!但双方都不想分享个人信息。
  • 通过零知识证明,我可以向卖家证明我是一个真实的人,在不告诉他们我是谁的前提下验证身份。另一方面,卖家也能证明他们确实拥有一台电视并且是合法的卖家,同样不用泄露任何敏感的个人信息。这其中包含着错综复杂的加密学基元,因此,上文只是非常简概的介绍。大多情况下,零知识证明能够解决加密界中的安全性、可扩展性和隐私挑战问题。

③ Layer2 扩容方案

  • 用户非常希望可以在以太坊上大展拳脚,因为它是世界上最为去中心化且最为资深的智能合约计算平台。以太坊已经吸引了分布最广的开发者网络,进行基于区块链的应用创建。但这些创建活动带来的后果是,打包交易到以太坊区块链的需求有时会造成 gas 价格过高,这也意味着以太坊用起来既慢又贵。
  • 区块链的三角悖论暗示着,任何优化过安全性和去中心化程度的区块链,将在可扩展性上做出让步。由于去中心化和安全性对区块链的愿景承诺具有重要推动作用,所以可扩展性就成了最难解决的部分。以太坊将赌注压在了一大波改进浪潮上,希望由此解决可扩展性问题。
  • 其中一种改进是,从用户先与以太坊区块链本身(即“ Layer1 ”)互动,改为与 Layer2 扩容方案互动。从根本上,这表明大部分以太坊主网上的交易和应用会转移到 Layer2,它继承了以太坊的安全性和去中心化,但却比以太坊本身的吞吐量高几个数量级。以太坊 Layer1 将会专门负责共识问题,而它的 Layer2 则会负责执行交易和代码。
  • Rollups - Rollup 会在其独立的区块链中处理一批交易,在自己的链上执行这些交易后,Rollup 将所有的交易压缩成一个小型的信息数据包。这些小数据包会被“发送”到以太坊的 Layer1,这表示 Rollup 在继承了 Layer1 安全性的同时扩大可以处理的交易数量(因为信息被压缩)。

六、智能合约和 Solidity 案例分享

  • 这是一个构建在以太坊上的游戏,有如下脚本:
    • zombiefactory.sol:定义 zombie 和生成 zombie;
    • zombiefeeding.sol:定义小猫接口,给 zombie 吃小猫;
    • zombieattack.sol:zombie 打架的功能;
    • erc721.sol:ERC721 代币的接口;
    • ownable.sol:用户认证的接口;
    • safemath.sol:运算时安全检查;
    • zombieownership.sol:zombie 的所属功能,transfer,approve,takeOwnership 等功能;
    • zombiehelper.sol:zombie 的辅助功能,该名字,改 DNA,提升等级,设置 levelUpFee,提现,查看僵尸军团等功能;
    • index.html:前端交互调用;
    • cryptozombies_abi.js:ABI 文档。
  • zombiefactory.sol:
pragma solidity ^0.4.19;

import "./ownable.sol";
import "./safemath.sol";

contract ZombieFactory is Ownable {

  using SafeMath for uint256;

  // 事件是合约和区块链通讯的一种机制
  // 当僵尸创造出来时,前端能监听到这个事件,并将它显示出来
  event NewZombie(uint zombieId, string name, uint dna);

  /**
    僵尸的NDA只有16个字符
    dnaModulus等于10^16,DNA可以用模运算符 % 把一个整数变成16位
    冷却时间长达1天
  **/
  uint dnaDigits = 16;
  uint dnaModulus = 10 ** dnaDigits;
  uint cooldownTime = 1 days;

  // 使用struct节省存储空间,节约gas。readyTime实现“冷却定时器”
  struct Zombie {
    string name;
    uint dna;
    uint32 level;
    uint32 readyTime;
    uint16 winCount;
    uint16 lossCount;
  }

  // 公共可变长数组
  Zombie[] public zombies;

  // 给数据库中的僵尸指定主人,支持多玩家模式
  // mapping和address,通过僵尸id查到拥有者的address
  mapping (uint => address) public zombieToOwner;
  // 通过address查询到有多少只僵尸
  mapping (address => uint) ownerZombieCount;

  // 内部方法以 _ 开头,函数里面的变量以 _ 开头,区别全局变量。
  function _createZombie(string _name, uint _dna) internal {
    uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
    // msg.sender 是合约当前调用者的address或者智能合约的address
    zombieToOwner[id] = msg.sender;
    ownerZombieCount[msg.sender]++;
    NewZombie(id, _name, _dna);
  }

  // 函数没有改变Solidity里的状态,没有改变任何值或者写任何东西,把函数定义为view,
  // 意味着只能读取数据不能更改数据
  // keccak256是以太坊提供的SHA3散列函数,把一个字符串转换成一个256位的16进制数字。
  // 只能用它造一个伪随机数。
  function _generateRandomDna(string _str) private view returns (uint) {
    uint rand = uint(keccak256(_str));
    return rand % dnaModulus;
  }

  function createRandomZombie(string _name) public {
    // require使函数在执行过程中,当不满足某些条件时抛出错误,并停止执行
    require(ownerZombieCount[msg.sender] == 0);
    uint randDna = _generateRandomDna(_name);
    randDna = randDna - randDna % 100;
    _createZombie(_name, randDna);
  }
}
  • zombiefeeding.sol:
pragma solidity ^0.4.19;

import "./zombiefactory.sol";

// 定义一个借口,使用contract关键字,在接口里定义getKitty函数
contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

// 继承ZombieFactory合约
contract ZombieFeeding is ZombieFactory {

  KittyInterface kittyContract;

  // modifier关键字告诉编译器,这是个modifier修饰符,而不是function,不能像函数直接调用,只能添加到函数定义的末尾,用以改变函数的行为
  modifier onlyOwnerOf(uint _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    _;
  }

  // external 函数只能在合约之外调用,不能被合约内的其他函数调用
  // 通过程序更改CryptoKities合约地址
  // onlyOwner是指定合约的所有权,指定一个主人,只有主人(合约部署者)对它享有特权
  function setKittyContractAddress(address _address) external onlyOwner {
    kittyContract = KittyInterface(_address);
  }

  // internal 合约可访问父合约中定义的内部函数
  function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(now + cooldownTime);
  }

  function _isReady(Zombie storage _zombie) internal view returns (bool) {
      return (_zombie.readyTime <= now);
  }

  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal onlyOwnerOf(_zombieId) {
    // storage变量永久存储在区块链中
    Zombie storage myZombie = zombies[_zombieId];
    require(_isReady(myZombie));
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    // 如果是kitty变过来的,用99替换新僵尸DNA的最后两位数字
    if (keccak256(_species) == keccak256("kitty")) {
      newDna = newDna - newDna % 100 + 99;
    }
    _createZombie("NoName", newDna);
    _triggerCooldown(myZombie);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }
}
  • zombieattack.sol:
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

/**
  僵尸战斗功能,继承自ZombieHelper
**/
contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  // 拿now,msg.sender,自增的nonce,转成一个哈希值,再转uint,% 100 取最后两位,生成一个0到100的随机数
  // 当然最后拿到的是一个伪随机数,但在具体的项目中,不会吸引×××者来×××就是现实安全的。
  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  // 选择自己的僵尸,选择对手的一个僵尸去×××
  // ×××方有70%的获胜概率
  // 所有的僵尸都有一个winCount和lossCount,记录输赢
  // ×××方获胜,僵尸升级并产生一个新僵尸,成功次数累加1
  // ×××方失败,失败次数累加1
  // 无论输赢,当前僵尸的冷却时间将被激活
  function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } else {
      myZombie.lossCount++;
      enemyZombie.winCount++;
      _triggerCooldown(myZombie);
    }
  }
}
  • erc721.sol:
/**
  ERC20代币,一个代币只是一个追踪谁拥有多少该代币的合约,和一些可以让那些用户将它们的代币转移到其他地址的函数

  ERC721 适合CryptpZombies这样的加密收藏品,是ERC721代币
  ERC721代币是不能互换的,因为每个代币都被认为是唯一且不可分割的。只能以整个单位交易它们。

  搞出一个ERC721的标准,好处显而易见:我们不必在合约中实现拍卖和托管逻辑,符合其规范,其他人可以为
  加密可交易的ERC721资产搭建交易平台,ERC721僵尸也可在上面使用和交易。
**/
contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

  function balanceOf(address _owner) public view returns (uint256 _balance);
  function ownerOf(uint256 _tokenId) public view returns (address _owner);
  function transfer(address _to, uint256 _tokenId) public;
  function approve(address _to, uint256 _tokenId) public;
  function takeOwnership(uint256 _tokenId) public;
}
  • ownable.sol:
pragma solidity ^0.4.19;
/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address public owner;

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public {
    owner = msg.sender;
  }


  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }


  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }

}
  • safemath.sol:
pragma solidity ^0.4.18;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}
  • zombieownership.sol:
pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  using SafeMath for uint256;

  mapping (uint => address) zombieApprovals;

  // 传入address函数,返回address拥有多少ERC721代币,整数不可分割
  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  // 传入一个代币ID,也是僵尸ID,返回该代币拥有者的address
  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

  // 代币的拥有者调用transfer方法,传入需要转移到的address和他想转移的代币的_tokenId。
  // 合约安全增强:溢出和下溢
  // 溢出(overflow),uint8的变量,存储的最大数就是二进制11111111,也就是十进制的255,加1就出现溢出。
  // 下溢(underflow),一个等于0的uint8,减去1就出现下溢,变成255,uint是无符号的,不能等于复数。
  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
    ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

  // 都有一个修饰符onlyOwnerOf
  function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    _transfer(msg.sender, _to, _tokenId);
  }

  // 代币的拥有者调用approve,传入允许提取代币的address和允许提取的代币_tokenId。
  function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _to;
    Approval(msg.sender, _to, _tokenId);
  }

  // require检查确保msg.sender已经被批准来提取这个代币,也就是僵尸。
  function takeOwnership(uint256 _tokenId) public {
    require(zombieApprovals[_tokenId] == msg.sender);
    address owner = ownerOf(_tokenId);
    _transfer(owner, msg.sender, _tokenId);
  }
}
  • zombiehelper.sol:
pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

/**
  辅助方法
**/
contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  // 修饰符modifier的最后一行为 _ ,表示修饰符调用结束后返回,并执行调用函数余下的部分。
  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 提现功能,只能是合约主人享有
  // 通过transfer函数向一个地址发送以太
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }

  // 合约主人设置levelUpFee
  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  // payable修饰符,是一种可以接收以太的特殊函数
  // 通过支付ETH来升级僵尸,ETH将存储在你拥有的合约中
  function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

  // 2级以上的僵尸可以改名
  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  // 20级以上的僵尸可以定制DNA
  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

  // 查看某个某家的整个僵尸军团,只需要从区块链中读取数据,view函数。
  function getZombiesByOwner(address _owner) external view returns(uint[]) {
    // memory 关键字是使用临时存储,调用完就释放
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }
}

你可能感兴趣的:(Blockchain技术,“以太坊”的概念和简介,如何与创建在以太坊上的应用交互,去中心化金融,钱包和身份,Layer2,和权益证明机制)