cryptozombies13
https://cryptozombies.io/zh/lesson/19/chapter/1
你真是越来越令人印象深刻了,但如果我们要把现实世界及其所有变化、信息与我们心爱的“僵尸”(注:结合上下文可能指特定项目或代码中的“僵尸”相关模块)结合起来,情况会如何呢?
在本节课中,我们将深入探讨Chainlink(柴链)和预言机(Oracles)。
区块链预言机是连接智能合约、“僵尸”模块与现实世界数据及计算能力的工具,能获取的信息包括货币定价数据、随机数生成器数据,以及其他任何我们能想到的数据。区块链本身无法与外部世界交互,因为其设计初衷就是具有隔离性,且本质上是确定性的(注:“确定性”指区块链上的交易和计算结果在给定初始条件下唯一且可验证,不会因外部因素变化)。
那么,我们要做的是不是只要信任其中一个预言机,让它如实传递数据,一切就万事大吉了呢?
简单来说,答案是否定的。至少在形成社会共识信任,或你开发出去中心化版本的预言机之前,事情并非如此。你费尽心力在去中心化环境中构建智能合约,若从中心化预言机获取数据,之前的所有努力基本会付诸东流!这意味着,即便数据是你亲自上传的,它依然属于中心化范畴——因为你本人就是这个数据流程中的中心化节点。
当智能合约包含来自预言机的数据或计算能力时,这类合约被称为“混合智能合约”。目前许多最成功的去中心化应用(DApps),都整合了来自现实世界的外部数据。
准备好深入学习了吗?我们开始吧!
补充说明(针对技术术语)
- Chainlink:区块链领域知名的去中心化预言机网络,中文常译“柴链”,核心功能是解决区块链与现实世界的数据交互问题,也是Web3开发中实现“混合智能合约”的关键工具。
- Oracles(预言机):并非传统意义上的“预言”工具,而是区块链的“数据中介”,负责将链下(现实世界)数据转化为链上(区块链)智能合约可读取、可验证的格式,是连接区块链与外部系统的核心组件。
- Hybrid Smart Contracts(混合智能合约):结合了区块链原生代码(处理链上逻辑,如资产转账)与外部数据/计算(通过预言机获取)的智能合约,相比纯链上合约更具灵活性,能支持去中心化金融(DeFi)、供应链溯源等需现实数据支撑的场景。
第1章:Chainlink数据馈送(Data Feeds)介绍
假设你正在开发一款去中心化金融(DeFi)应用(dapp),希望让用户能够提取价值特定美元金额的以太坊(ETH)。要实现这一需求,你的智能合约(为简化说明,下文统称其为“调用方合约”)必须知晓1个以太币对应的美元价值。
但问题在于:JavaScript应用程序可以轻松获取这类信息——只需向币安(Binance)公开API(或其他提供公开价格馈送的服务)发起请求即可。然而,智能合约无法直接访问外部世界的数据。
或许你会想,那我们自己开发一个JavaScript应用程序不就行了?但这样做会引入中心化的故障点!同理,我们也不能直接调用币安API获取数据——因为这同样是一个中心化的故障点!
因此,我们需要从去中心化预言机网络(DON) 和去中心化数据源 获取数据。
Chainlink是去中心化预言机网络(DON)的核心框架,能通过多个预言机从多源获取数据。该去中心化预言机网络以去中心化方式聚合数据,并将其写入区块链上的智能合约(这类合约通常被称为“价格参考馈送”或“数据馈送”),供我们读取。也就是说,我们只需读取Chainlink网络持续为我们更新的合约数据即可!
在去中心化场景下,使用Chainlink数据馈送能以更低成本、更高精度、更高安全性获取现实世界数据。由于数据来自多个来源,多方可参与该生态系统,其成本甚至低于运行一个中心化预言机。Chainlink网络采用“链下报告(Off-Chain Reporting)”机制:先在链下就数据达成共识,再通过一笔经密码学验证的单一交易将数据上报至链上,供用户使用。
借助这一技术,你可以开发出类似Synthetix、Aave、Compound这样的协议!
Chainlink去中心化预言机网络
你可点击此处查看部分去中心化预言机网络的可视化展示。
这些网络的具体运行机制,我们将在后续课程中详细讲解。
接下来,我们就学习如何读取这类数据馈送!
首先要做的,是初始化合约并导入Chainlink相关代码。
实战演练
其中部分内容你应该已经非常熟悉,唯一的核心差异在于其背后的设计逻辑!
在右侧代码框中,完成以下操作:
- 在文件顶部声明编译指令(pragma)版本为^0.6.7;
- 声明一个新合约,命名为PriceConsumer(注:Chainlink聚合器接口当前版本为v3,且会随功能迭代更新版本号。为便于后续参考,需在合约名中添加版本标识)——最终声明一个名为PriceConsumerV3的新合约,暂将合约主体留空。
术语补充说明
- Data Feeds(数据馈送):Chainlink核心功能模块,特指经去中心化聚合后写入区块链的标准化数据流(如价格数据),是智能合约获取链下数据的核心载体;
- Decentralized Oracle Network (DON):去中心化预言机网络,由多个独立预言机节点组成,避免单一节点故障导致的数据不可靠,是Chainlink实现去中心化数据获取的核心架构;
- Off-Chain Reporting (OCR):链下报告机制,Chainlink的核心共识优化方案——预言机节点先在链下协商达成数据共识,再以单笔交易将结果上链,大幅降低上链成本并提升效率;
- Aggregator Interface(聚合器接口:Chainlink定义的标准化接口,智能合约通过该接口读取聚合后的标准化数据,v3为当前主流版本,会随功能升级迭代。
- pragma版本:Solidity语言的编译版本声明,指定合约兼容的编译器版本,^0.6.7表示兼容0.6.7及以上、0.7.0以下的版本。
第2章:从NPM和Github导入代码
要让我们的合约从其他合约中获取价格数据,首先需要获取目标合约的接口(Interface)/应用二进制接口(ABI)。但目前我们并不知道Chainlink数据馈送合约的接口具体是什么样的——该如何获取呢?
从NPM/Github导入代码
你知道吗?其实可以从合约项目外部导入代码!是不是很神奇?很多时候,你无需把所有代码都直接写在自己的项目里,而是可以借鉴其他应用的代码!
我们需要从Chainlink的GitHub代码仓库中导入AggregatorV3Interface(V3版本聚合器接口)。这个接口包含了与Chainlink数据馈送合约交互所需的全部函数,例如latestRoundData()函数——它能返回我们需要的所有价格相关信息。
以下是该接口的完整代码:
1 | |
你可以在AggregatorV3Interface的GitHub代码仓库中找到这份代码。
我们既可以直接从GitHub导入该合约,也可以通过NPM包导入——具体选择哪种方式,取决于你使用的开发框架(如Truffle、Brownie、Remix、Hardhat),但导入语法大致相同!
实战演练
要对接Chainlink数据馈送合约,请导入合约文件:@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol。
注意:我们导入v0.6版本的接口,是为了匹配当前使用的Solidity编译器版本!
术语补充说明
- ABI(Application Binary Interface):应用二进制接口,是智能合约与外部(如其他合约、前端应用)交互的标准化接口,定义了合约函数的调用方式、参数和返回值格式;
- NPM(Node Package Manager):Node.js的包管理工具,区块链开发中常用来管理Solidity合约依赖、开发工具包(如Chainlink合约包);
- Interface(接口):Solidity中的接口类型,仅声明函数签名(无实现逻辑),用于定义合约间的交互规范,调用外部合约时只需引入其接口即可;
- Truffle/Brownie/Remix/Hardhat:主流的以太坊智能合约开发/部署框架,各自支持不同的依赖管理方式(如NPM或直接从GitHub导入代码);
- **latestRoundData()**:Chainlink数据馈送接口的核心函数,返回最新一轮的聚合数据,包含价格(answer)、数据更新时间(updatedAt)等关键信息;
- SPDX-License-Identifier:Solidity合约的许可证声明,用于标注合约的开源许可协议(如MIT),是区块链开源项目的通用规范。
第3章:AggregatorV3Interface接口使用
做得很棒!现在,要与某一个数据馈送合约交互,既然我们已经有了接口,接下来只需要获取合约地址即可。我们可以借助链上馈送注册表(Feeds Registry) ——这是一个记录所有数据馈送合约地址的链上合约;也可以直接浏览所有合约地址列表,自行选择所需的合约地址。
由于我们需要获取ETH对USD的兑换价格,因此要选择包含该数据的特定数据馈送合约。
⚠️ 重要提示:不同区块链网络中,对应同一类数据的合约地址各不相同。例如,ETH/USD价格合约在以太坊主网、Polygon主网、Rinkeby测试网等网络中的地址均不相同。
本演示将使用Rinkeby测试网的ETH/USD数据馈送合约,你可在《Rinkeby数据馈送文档》中查询所有相关合约地址。我们选择Rinkeby测试网的原因是,在后续课程中,你将学习如何向该测试网部署合约!
实战演练
请完成以下操作:
- 声明一个公共全局变量(即定义在所有函数外部的变量),命名为
priceFeed,类型为AggregatorV3Interface; - 编写一个构造函数(
constructor); - 实例化
AggregatorV3Interface合约:将以太坊主网ETH/USD合约地址0x8A753747A1Fa494EC906cE90E9f37563A8AF630e作为参数传入,并将实例化结果赋值给priceFeed变量。
提示:如需验证你填写的Rinkeby网络ETH/USD数据馈送合约地址是否正确,可查阅Chainlink文档的「以太坊数据馈送」页面。
1 | |
术语补充说明
- Feeds Registry(馈送注册表):Chainlink推出的链上合约注册表,统一管理各数据馈送合约的地址与元数据,方便开发者快速查找不同网络/不同标的的价格馈送合约地址;
- Mainnet/Testnet:分别指区块链“主网”(真实资产流通的正式网络)和“测试网”(用于开发测试的模拟网络,无真实资产风险),Rinkeby是以太坊经典测试网之一;
- Constructor(构造函数):Solidity合约中的特殊函数,仅在合约部署时执行一次,常用于初始化合约变量(如本例中绑定数据馈送合约地址);
- Global variable(全局变量):定义在合约内、所有函数外的变量,生命周期与合约一致,可被合约内所有函数访问;
public修饰符表示该变量可被外部读取。
第4章:元组(Tuples)的使用
接下来,我们需要调用priceFeed合约的latestRoundData函数来获取ETH的最新价格。该函数包含了我们所需的全部信息,以及一些额外字段:
roundId:轮次ID。每一次价格更新都会生成唯一的轮次ID;answer:当前价格;startedAt:该轮价格计算的起始时间戳;updatedAt:该轮价格的更新时间戳;answeredInRound:计算出该价格对应的轮次ID。
元组(Tuples)
不过在调用这个函数之前,先问你一个问题:你知道什么是元组吗?
如果你还记得之前课程里讲过的内容,可以直接跳过本节;如果不记得,跟着我们的讲解来学习就好。
元组是Solidity中用于语法层面组合多个表达式的一种方式。
当一个函数返回多个变量时(比如latestRoundData函数),我们会将其返回类型视为“包含多种类型的元组”。
1 | |
要为每个返回值赋值给对应的变量,我们需要使用元组语法——将变量列表用括号包裹:
1 | |
我们也可以将变量重命名为任意名称。比如,把answer改名为price:
1 | |
此外,如果某些返回值我们用不到,最佳实践是将其留空,示例如下:
1 | |
这样一来,我们能清晰看到哪些变量是核心关注的,同时避免声明多余的变量。
实战演练
请完成以下操作:
- 声明一个公共视图函数(
public view),命名为getLatestPrice,返回值类型为int; - 调用
priceFeed合约的latestRoundData函数,仅将返回值中的answer字段存储到名为price的int类型变量中,忽略其他所有返回值(不要声明这些无用变量)。如果忘记语法可以参考上文示例,但建议先尝试独立完成; - 返回
price变量。
1 | |
术语补充说明
- Tuple(元组):Solidity中的复合数据结构,支持将多个不同类型的值组合成一个整体,常用于处理多返回值函数的结果,是Solidity特有的语法特性;
- View function(视图函数):Solidity中用
view修饰的函数,仅读取链上数据、不修改合约状态,调用此类函数无需消耗Gas; - Timestamp(时间戳):以Unix时间戳格式表示的时间(单位:秒),区块链中常用该格式记录事件发生的时间;
- 空白占位符(,):元组赋值时的语法技巧,用逗号表示忽略对应位置的返回值,可简化代码并减少不必要的变量声明。
第5章:Chainlink数据馈送的小数位数(Decimals)
太棒了!现在我们已经有了能获取以太坊兑美元最新价格的函数!
但你也知道,Solidity对小数的处理并不友好——那么当我们调用这个函数时,会得到什么样的结果呢?你能猜到返回值的格式吗?
其实不用你试了,直接告诉你:调用该函数后,我们会得到类似这样的返回值:310523971888
等等……以太坊的价格真的有这么高吗?
或许在遥远的未来有可能,但在本文撰写时,实际价格远非如此——当前以太坊价格约为3105.52美元。
那我们该如何确定小数点的位置呢?答案很简单:decimals函数会告诉我们!
只看接口代码,你能写出一个getDecimals函数,返回该合约使用的小数位数吗?我相信你肯定可以!
实战演练
请完成以下操作:
- 声明一个公共视图函数(
public view),命名为getDecimals,其返回值需与AggregatorV3Interface中decimals函数的返回值类型一致(注意:返回值类型为uint8); - 函数第一行代码:调用
priceFeed.decimals()函数,并将结果存储到名为decimals、类型为uint8的变量中; - 函数第二行代码:返回这个
decimals变量。
术语补充说明
- Decimals(小数位数):Chainlink数据馈送合约中用于标注价格精度的参数,例如返回值
310523971888若对应18位小数,则实际价格为310523971888 / 10^18 = 3105.23971888美元,该设计是为了规避Solidity不支持浮点数的问题; - uint8:Solidity中的无符号整数类型,取值范围0~255,适用于表示小数位数这类数值范围较小的参数;
- decimals()函数:
AggregatorV3Interface接口内置函数,返回当前数据馈送合约使用的小数位数,是解析价格数据的核心辅助函数。
第6章:Chainlink数据馈送参考资料
太厉害了!现在你已经掌握了如何将去中心化预言机网络(DON)的数据接入智能合约。学完本课后,你可以跟着Chainlink文档中的基础教程,学习如何将这类合约部署到真实的测试网中!
后续我们会深入讲解这些价格馈送的底层工作原理,以及如何搭建一套DON来获取你所需的任意数据。不过在实际开发中,开发者往往更倾向于使用这些“开箱即用”的预言机服务——因为它们上手更简单,几乎无需额外配置就能使用。接下来我们还会学习更多这类“现成可用、开箱即走”的服务。
Chainlink预言机的功能远不止我们目前讲到的这些,后续我们会全面解析其整体工作机制,以及其他一些极具实用性的强大功能。
不过在此之前,先给大家提前介绍几款能让开发能力更上一层楼的工具。很快你就会学习如何使用Truffle、Hardhat等开发框架,以及前端开发、去中心化金融(DeFi)等相关知识,这些内容会让Chainlink数据馈送的应用场景变得更加丰富。等掌握这些知识点后,你可以回过头来使用Truffle入门套件、Hardhat入门套件和Brownie入门套件(Chainlink Mix),在这些开发套件中构建复杂的智能合约应用。
在学习这些内容之前,我们先继续探索Chainlink为我们提供的更多精彩功能吧!
点击“下一章”按钮,进入下一课的学习。
术语补充说明
- Under the hood:技术领域常用俗语,指“底层原理/内部机制”,此处特指价格馈送合约的运行逻辑、DON节点的共识流程等底层实现;
- Out-of-the-box:直译“开箱即用”,在开发领域指无需二次开发/配置即可直接使用的工具/服务,是Chainlink核心优势之一;
- Ready-to-go out-of-the-box:强化版“开箱即用”,强调“现成可用、无需额外准备”,贴合Chainlink数据馈送低接入成本的特点;
- Starter Kit(入门套件):Chainlink官方为主流开发框架(Truffle/Hardhat/Brownie)提供的预制开发模板,包含数据馈送、预言机交互等基础代码,降低开发者入门门槛;
- Development suites(开发套件):整合了开发、编译、部署、测试等功能的一站式开发环境,如Truffle Suite、Hardhat生态等。
第七章:Chainlink VRF 简介
伪随机 DNA
在我们的合约开发过程中,我们一直使用的是伪随机数。keccak256 函数是学习编写智能合约的一个很好的入门工具,但正如我们之前提到的,若智能合约的逻辑依赖伪随机数,可能会引发严重问题。链上机制的所有环节本质上都是确定性的,这其中也包括我们使用的哈希函数。
当我们尝试从一个字符串生成随机 DNA 时会发现,每次得到的 DNA 结果都是完全相同的——这意味着我们的“随机数生成函数”根本不具备真正的随机性!此外,我们也无法保证用户向该函数输入的“字符串”是真实可信的。
1 | |
那么该如何解决这个问题?一种简单的思路是使用全局可用的变量,比如 msg.sender、block.difficulty 和 block.timestamp。你可能见过类似这样的随机数生成方式:
1 | |
在这个例子中,开发者试图混合这些全局变量来降低数字的可预测性,但即便如此,这些数值依然存在可被预判的漏洞:
- msg.sender 由交易发起方提前知晓;
- block.difficulty 受矿工直接影响;
- block.timestamp 可被预测。
由此可见,区块链内的所有数据都是确定性的,若依赖链内生成的随机数,合约极易遭受攻击。那我们该如何获取链外的真随机数?答案正是 Chainlink 预言机!
让我们回到之前的僵尸合约代码——我们曾为僵尸生成伪随机 DNA,现在尝试用 Chainlink 可验证随机函数(Chainlink VRF)修复这个问题,借助其安全的随机性机制来优化合约。
Chainlink VRF 是什么?
Chainlink VRF(Verifiable Randomness Function,可验证随机函数)是一种从链外获取随机数的方式,且该过程具备密码学层面的可验证性。这一点至关重要,因为我们始终希望合约逻辑是绝对不可篡改的。
如果简单粗暴地通过链下 API 调用第三方服务获取随机数,一旦该服务宕机、被贿赂或遭黑客攻击,返回的随机数就可能被篡改。而 Chainlink VRF 内置了链上验证合约,能够通过密码学方式证明返回的随机数是真正随机且未被篡改的。
基础请求模型
接下来我们介绍与预言机交互的基础请求模型:
- 首先,智能合约(称为“调用方合约”)向 Chainlink 节点发起“请求”——该节点由链上合约和对应的链下节点组成。调用方合约发起请求时会触发一个特定事件,而对应的 Chainlink 节点会订阅/监听这个事件。这一步骤在一笔交易中完成。
- 随后,Chainlink 预言机处理该请求(无论是生成随机数、获取数据等),并将结果/计算值返回给调用方合约;也可能先返回给一个“中间合约”(通常称为“预言机合约”),再由该合约将响应转发给调用方合约。
- 返回结果的过程会在第二笔独立交易中完成,因此基础请求模型总共涉及两笔交易,至少需要两个区块才能完成。
这种“两笔交易”的架构意义重大:它能限制对随机数或数据请求的暴力攻击,攻击者若想篡改结果,需付出极高的燃气费成本,使其攻击行为不具备经济可行性。
总结整个流程:
- 调用方合约在一笔交易中发起请求;
- 调用方合约或预言机合约触发事件;
- Chainlink 节点(链下)监听该事件,事件中会记录请求的详细信息;
- Chainlink 节点发起第二笔交易,通过调用方合约指定的函数,将数据返回到链上;
- 对于 Chainlink VRF 而言,还会额外进行随机数验证,确保返回的数值是真随机数。
与以太坊或其他兼容 Solidity 的区块链交易类似,使用预言机时需要支付“预言机燃气费”,即 LINK 代币(Chainlink 代币)。LINK 代币专为预言机生态设计,保障 Chainlink 预言机网络的安全性。每次按照基础请求模型发起请求时,合约必须预存一定数量的 LINK 代币(具体金额由所使用的预言机服务决定,不同服务的预言机燃气费不同)。
为何数据馈送(Data Feeds)无需这样操作?
此时你可能会问:“为什么数据馈送(Data Feeds)不需要这样的流程?”“为什么使用数据馈送时不用支付预言机燃气费?”这些都是非常好的问题。
数据馈送的工作方式不同:它并非向单个 Chainlink 节点发起请求,而是向一整个去中心化预言机网络(Decentralized Oracle Network)的节点群请求数据。但只需一个实体触发对整个网络的请求,我们便能受益于他人已为各类数据馈送发起的预言机请求。
数据馈送由 Aave、Compound、Synthetix 等众多项目联合赞助,当所上报的数据(如价格)发生微小变动时,系统会自动触发网络更新。通过集体协作,不仅能降低交易成本,还能为整个生态创造可共享的公共资源!
简言之,数据馈送的背后,是有人已为我们执行了更高级版本的基础请求模型。
Chainlink VRF 底层原理
Chainlink VRF 遵循上述基础请求模型,并增加了一个核心优势:由于 Chainlink VRF 节点返回的随机数附带链上密码学证明,我们只需对接单个 Chainlink VRF 节点即可保证安全性。尽管随着技术发展,更去中心化的 Chainlink VRF 版本已逐步推出,但目前这种方式已能为智能合约提供足够安全的随机数。
我们不会深入探讨研究人员为验证 Chainlink VRF 节点返回随机数所做的密码学证明,但核心逻辑可概括为:
- 智能合约通过指定一个哈希值(用于唯一标识某个 Chainlink 预言机)发起随机数请求;
- Chainlink 节点利用自身的私钥和该哈希值生成随机数,随后将随机数及密码学证明一同返回至链上合约;
- 链上的 VRF 协调器合约(VRF Coordinator)接收随机数和证明,并通过预言机的公钥完成验证;
- 依托区块链公认的签名和证明验证能力,确保合约仅能使用经过链上环境验证的随机数。
你可查阅 Chainlink VRF 官方合约代码,了解该系统使用的具体函数。
好了,这些概念虽然有一定深度,但现在我们终于可以开始学习如何在智能合约中获取随机数了。首先,我们需要从 NPM/GitHub 引入 Chainlink VRF 合约代码,通过继承 VRFConsumerBase 合约的功能,实现事件触发并定义 Chainlink 节点回调(响应)的函数。
实战演练
从 Chainlink 的 NPM 仓库/GitHub 导入 0.6.6 版本的 VRFConsumerBase.sol 合约文件。
VRFConsumerBase.sol 包含了与 Chainlink VRF 协调器和节点交互所需的所有函数,你也可在 Chainlink 官方文档中找到相关使用示例。
翻译说明
术语统一性:
- Chainlink VRF:统一译为“Chainlink 可验证随机函数”,首次出现标注英文全称(Verifiable Randomness Function);
- Oracle:译为“预言机”(区块链领域通用译法);
- LINK token:译为“LINK 代币”,首次提及补充说明“Chainlink 代币”;
- VRFConsumerBase:保留英文原名(合约名不翻译),中文描述为“VRF 消费者基础合约”;
- VRF Coordinator:译为“VRF 协调器合约”;
- Basic Request Model:译为“基础请求模型”(贴合技术语境);
- Data Feeds:译为“数据馈送”(Chainlink 官方中文文档译法)。
技术语境适配:
- “brute force attacks”译为“暴力攻击”(网络安全通用译法);
- “gas costs”译为“燃气费”(以太坊生态通用译法);
- “deterministic”译为“确定性的”(区块链技术术语);
- “cryptographic proof”译为“密码学证明”;
- “decentralized oracle network”译为“去中心化预言机网络”。
句式优化:
- 英文长句拆解为符合中文阅读习惯的短句(如“However, as we mentioned earlier…”段落);
- 被动语态转主动语态(如“is comprised of”译为“由……组成”而非“被组成”);
- 保留代码块完整性,仅翻译注释和上下文描述。
补充说明:
- 对“block.difficulty”“block.timestamp”等链上变量,补充简要解释(如“受矿工直接影响”“可被预测”),帮助理解其不可靠性;
- 对“two transaction architecture”译为“两笔交易的架构”,并补充说明“至少需要两个区块完成”,贴合区块链交易确认逻辑。
第8章:构造函数嵌套调用
你理解得没错!VRFConsumerBase 合约包含了我们向 Chainlink 预言机发送请求所需的全部代码,其中也涵盖了所有事件日志相关的代码。
正如我们之前所说,要与 Chainlink 节点交互,我们需要知晓几个关键变量:
- Chainlink 代币合约地址:我们的合约需要通过该地址确认是否持有足够的 LINK 代币来支付燃气费;
- VRF 协调器合约地址:用于验证我们获取的数值是否为真随机数;
- Chainlink 节点密钥哈希(keyhash):用于指定我们想要对接的具体 Chainlink 节点;
- Chainlink 节点手续费:代表 Chainlink 节点向我们收取的手续费(燃气费),以 LINK 代币计价。
你可以在 Chainlink 官方文档的「VRF 合约地址」页面找到这些变量。需要再次说明的是,不同网络的地址各不相同,但本课程中我们仍以 Rinkeby 测试网为例进行讲解。
上一节课提到,我们要继承 VRFConsumerBase 合约的功能。但如何实现继承合约的构造函数呢?答案是:我们可以在构造函数中嵌套调用父合约的构造函数(即“构造函数嵌套调用”)。
我们来看这段示例代码:
1 | |
要调用继承合约的构造函数,只需将父合约构造函数的声明写入当前合约的构造函数中即可。
我们可以对 VRFConsumerBase 合约执行同样的操作:
1 | |
动手实践
让
ZombieFactory合约继承VRFConsumerBase合约。如果你忘记了语法,可参考以下示例:1
2
3
4
5
6
7
8
9// 定义名为 `parent` 的父合约
contract parent {
}
// 定义名为 `child` 的子合约,继承自 `parent`
contract child is parent {
}为
ZombieFactory合约编写构造函数,在其中调用VRFConsumerBase合约的构造函数,并传入 Rinkeby 网络下 VRF 协调器和 LINK 代币合约的地址作为参数。构造函数体保持为空即可。你可以直接复制粘贴上述示例中的地址,也可以从 Chainlink 文档的「VRF 合约地址」页面获取。若再次忘记语法,可参考上文给出的示例。
术语说明(补充)
- Constructor in a constructor:直译是“构造函数中的构造函数”,结合 Solidity 语法特性,译为“构造函数嵌套调用”更贴合语境(本质是子合约构造函数中显式调用父合约构造函数);
- VRFConsumerBase:Chainlink VRF(可验证随机函数)的基础消费者合约,无需直译,保留原名称即可;
- VRF Coordinator:VRF 协调器(Chainlink 网络中负责管理随机数请求的核心合约);
- keyhash:密钥哈希(Chainlink 节点的唯一标识,用于指定交互节点);
- Rinkeby:以太坊经典测试网名称,保留原名称(中文可译“林克比”,但行业内通用英文)。
1 | |
这段代码是 Solidity 智能合约中构造函数的核心写法,本质是子合约构造函数显式调用父合约 VRFConsumerBase 的构造函数,并传入关键参数。下面拆解每一部分的含义,帮你彻底理解:
1. 核心语法结构
1 | |
| 部分 | 含义 |
|---|---|
constructor() |
当前合约的构造函数声明: - Solidity 中 constructor 是合约部署时仅执行一次的特殊函数;- 空括号 () 表示该构造函数无入参。 |
VRFConsumerBase(...) |
显式调用父合约 VRFConsumerBase 的构造函数:- 因为当前合约继承了 VRFConsumerBase,必须通过这种方式传参初始化父合约;- 括号内是传给父合约构造函数的参数。 |
public |
构造函数的可见性修饰符(Solidity 0.5.x 及之前版本要求显式声明,0.6.x+ 可省略)。 |
空函数体 {} |
当前合约构造函数无需额外逻辑,仅完成父合约的初始化。 |
2. 传入的两个参数含义
1 | |
这两个地址是 Rinkeby 测试网 下 Chainlink VRF 所需的核心配置,作用分别是:
- VRF Coordinator 地址:
Chainlink 网络中负责管理随机数请求、验证随机数合法性的核心合约地址。VRFConsumerBase必须知道这个地址,才能向 Chainlink 节点发起随机数请求,并验证返回结果是否真实有效。 - LINK 代币合约地址:
Chainlink 生态的原生代币合约地址。VRFConsumerBase通过这个地址:
① 检查当前合约是否持有足够的 LINK 代币(支付 Chainlink 节点的手续费);
② 完成手续费的转账扣减(若有自动扣费逻辑)。
3. 完整业务逻辑
这段代码的作用是:
当你部署当前合约(比如 ZombieFactory)时,会先调用父合约 VRFConsumerBase 的构造函数,把 Rinkeby 测试网的 VRF 协调器和 LINK 代币地址传入并初始化父合约的核心变量。
初始化完成后,当前合约就具备了:
✅ 向 Chainlink 节点发起随机数请求的能力;
✅ 验证随机数合法性的能力;
✅ 用 LINK 代币支付手续费的基础条件。
补充注意点
- 地址的网络适配性:
你用的0x6168499c0cFfCaCD319c818142124B7A15E857ab是 Rinkeby 测试网的 VRF 协调器地址,换网络(如 Sepolia、主网)必须替换成对应地址,否则合约无法正常工作。 - 构造函数调用规则:
Solidity 中如果父合约有带参构造函数,子合约必须显式调用(像这样写在子构造函数后),否则编译报错——这也是这段代码的核心目的。 - 函数体为空的原因:
这里只需要完成父合约的初始化,当前合约(ZombieFactory)的构造逻辑如果没有额外需求(比如初始化僵尸相关的基础变量),就可以留空。