作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
John R. 科辛斯基的头像

John R. Kosinski

做了近二十年的全栈开发, 约翰研究过物联网, Blockchain, web, 以及使用C/ c++的移动项目, .. NET, SQL和JS.

以前在

摩根士丹利(Morgan Stanley)
Share

在这三集的第一部分中,我们讨论了 这个小教程给了我们一个简单的契约与oracle配对. 制作的机制和过程(用松露), 编译代码, 部署到测试网络, running, and debugging were described; however, 代码的许多细节都以一种模糊的方式被掩盖了. 现在,正如我承诺的,我们会研究一些语言特性它们是独一无二的 Solidity智能合约开发 并且对于这个特定的合同-oracle场景来说是独一无二的. 虽然我们不能煞费苦心地研究每一个细节(我将把它留给你在进一步的研究中, if you wish), 我们将设法找出最引人注目的, 最有趣的, 以及代码中最重要的特性.

为了方便, 我建议你打开你自己的项目版本(如果你有的话), 或者将代码放在手边以供参考.

完整的代码可以在这里找到: http://github.com/jrkosinski/oracle-example/tree/part2-step1

以太坊和Solidity

Solidity并不是唯一可用的智能合约开发语言, 但我认为可以肯定地说,它是最常见、最受欢迎的, 以太坊智能合约. 当然,它拥有最受欢迎的支持和信息, 在写这篇文章的时候.

以太坊稳固性的关键特性图

固体是面向对象和图灵完备的. That said, 您很快就会意识到其内置的(完全有意的)限制, 是什么让智能合约编程感觉与普通的“让我们做这件事”的黑客有很大的不同.

稳定的版本

以下是每首solid代码诗的第一行:

实用可靠度^0.4.17;

您看到的版本号会有所不同, as Solidity, 仍然年轻, 变化和发展是否迅速. Version 0.4.17 is the version that I’ve used in my examples; the latest version at the time of this publication is 0.4.25.

此时你正在阅读这篇文章的最新版本可能完全不同. 许多不错的功能正在为solid工作(或至少计划), 我们现在要讨论什么.

下面是不同的概述 稳定的版本.

Pro tip: 你也可以指定一系列的版本(虽然我不经常看到这样做),像这样:

pragma solidity >=0.4.16 <0.6.0;

固体编程语言的特点

Solidity有许多现代程序员熟悉的语言特性,也有一些独特的(至少对我来说)不寻常的. 据说它的灵感来自c++, Python, 和javascript——这些我都很熟悉, 然而,solid似乎与这些语言中的任何一种都截然不同.

Contract

The .sql文件是代码的基本单元. In BoxingOracle.sol,注意第9行:

合同甲骨文是可拥有的{

因为类是面向对象语言的基本逻辑单元, 契约是solid的基本逻辑单位. 现在简单地说,契约是Solidity(面向对象程序员)的“类”就足够了, 这是一个简单的飞跃).

Inheritance

稳固性契约完全支持继承, and it works as you’d expect; private contract members are not inherited, 而受保护的和公共的. 如您所愿,它支持重载和多态性.

合同甲骨文是可拥有的{

在上面的语句中,“is”关键字表示继承. BoxingOracle继承自Ownable. 在Solidity中也支持多重继承. 多重继承由逗号分隔的类名列表表示,如下所示:

contract Child是ParentA, ParentB, ParentC {
…

然而(在我看来),在构建继承模型时过于复杂并不是一个好主意, 这里有一篇关于solid的有趣文章,关于所谓的 钻石的问题.

Enums

在Solidity中支持枚举:

    enum MatchOutcome {
        Pending, //match还没有被决定
        正在进行,//比赛已经开始 & is underway
        平局,//除明显的赢家以外的任何东西(例如.g., cancelled)
        决定//获胜者的参与者索引 
    }

正如您所期望的(与熟悉的语言没有什么不同), 每个枚举值被赋一个整数值, 从0开始. 正如Solidity文档中所述,枚举值可以转换为所有整数类型(例如.g., int, uint16, uint32等.),但不允许隐式转换. 这意味着它们必须显式强制转换(例如转换为int)。.

固体文档:枚举 枚举教程

Structs

和枚举一样,结构是创建用户定义数据类型的另一种方式. 所有C/ c++基础都熟悉结构体 coders 还有像我这样的老家伙. 的第17行,一个结构体的例子 BoxingOracle.sol:

//定义匹配及其结果
    struct Match {
        bytes32 id;
        string name;
        字符串的参与者;
        uint8 participantCount;
        uint date; 
        MatchOutcome结果;
        int8 winner;
    }

给所有老C程序员的提示: 在solid中结构“打包”是一件事,但是有一些规则和警告. Don’t necessarily assume that it works the same as in C; check the docs and be aware of your situation, 确定在特定情况下,包装是否对你有帮助.

固体结构包装

一旦创建,结构体就可以在代码中作为本机数据类型寻址. 下面是上面创建的结构类型“实例化”的语法示例:

Match Match = Match(id, "A vs . A ". “B”,“A|B”,2,block.时间戳,MatchOutcome.Pending, 1); 

solididity中的数据类型

这将我们带到了Solidity中数据类型的基本主题. solid支持哪些数据类型? 固体是静态类型的, 在编写本文时,必须显式声明数据类型并将其绑定到变量.

以太坊Solidity中的数据类型

数据类型

Booleans

名称下支持布尔类型 bool and values true or false

Numeric types

支持整数类型, 签名的和未签名的都有, 从int8/uint8到int256/uint256(即8位整数到256位整数), 分别). 类型uint是uint256的简写(同样int是int256的简写).

值得注意的是,浮点类型是 not supported. Why not? Well, for one thing, 在处理货币价值时, 众所周知,浮点变量是一个坏主意(当然一般情况下)。, 因为价值可以消失在稀薄的空气中. 以太值用wei表示, which is 1/1,000,000,000,000,000,以太的万分之一, and that must be enough precision for all purposes; you cannot break an ether down into smaller parts.

目前部分支持定点值. 根据solididity文档: “Solidity还没有完全支持固定点数. 它们可以被声明,但不能被赋值给或从.”

http://hackernoon.com/a-note-on-numbers-in-ethereum-and-javascript-3e6ac3b2fad9

Note: In most cases, 最好只使用int, 将变量的大小减小为uint32, for example), 实际上会增加汽油成本,而不是像你期望的那样降低成本吗. 这是一条经验法则, 除非您确定有很好的理由不这样做,否则请使用int.

String Types

The string data type in Solidity is a funny subject; you may get different opinions depending on who you talk to. 在Solidity中有一个字符串数据类型,这是事实. 我的观点,可能和大多数人一样,是它没有提供太多的功能. 字符串解析, concatenation, replace, trim, 即使计算字符串的长度:您可能期望从字符串类型中得到的这些东西都不存在, 所以他们是你的责任(如果你需要他们). Some people use bytes32 in place of string; that can be done as well.

关于固体弦的有趣文章

我的观点是:编写自己的字符串类型并将其发布以供一般使用可能是一个有趣的练习.

Address Type

也许是独一无二的坚实,我们有一个 address 数据类型,特别是针对以太坊钱包或合约地址. 它是一个20字节的值,专门用于存储特定大小的地址. 此外,它还具有专门用于这类地址的类型成员.

地址内部boxingOracleAddr = 0x145ca3e014aaf5dca488057592ee45305d9b3a22; 

地址数据类型

DateTime类型

Solidity本身没有原生的Date或DateTime类型,例如在JavaScript中就有. (哦,不,每一段都听起来越来越糟!?)日期的原生地址为uint (uint256)类型的时间戳。. 它们通常被处理为unix风格的时间戳, 以秒而不是毫秒计算, 因为块时间戳是unix风格的时间戳. 在您发现自己出于各种原因需要人类可读日期的情况下, 有一些可用的开源库. 你可能会注意到我在BoxingOracle中使用了一个: DateLib.sol. OpenZeppelin也有日期实用程序以及许多其他类型的通用实用程序库(我们将讨论 library 特性的固体很快).

Pro tip: OpenZeppelin 对于可以帮助您构建契约的知识和预写的泛型代码来说,这是一个好的来源(当然不是唯一的好来源)吗.

Mappings

的第11行 BoxingOracle.sol 定义了一个叫做 mapping:

mapping(bytes32 => uint) matchIdToIndex;

A mapping in Solidity is a special data type for quick lookups; essentially a lookup table or similar to a hashtable, 其中包含的数据存在于区块链本身(当映射被定义时), as it is here, 作为班级成员). 在合同执行过程中, 我们可以向映射中添加数据, 类似于向哈希表中添加数据, 然后查一下我们加的值. 再次注意,在这种情况下, 我们添加的数据被添加到区块链本身, 所以它会持续存在. 如果我们今天把它添加到纽约的地图上,一周后伊斯坦布尔的人就能读到它.

的第71行,添加到映射的示例 BoxingOracle.sol:

matchIdToIndex[id] = newIndex+1

的第51行,从映射中读取的示例 BoxingOracle.sol:

uint index = matchIdToIndex[_matchId]; 

还可以从映射中删除项. 在这个项目中没有使用它,但它看起来像这样:

删除matchIdToIndex [_matchId];

Return Values

你可能已经注意到了, 固体可能与Javascript有一点表面上的相似之处, 但它并没有继承JavaScript的松散类型和定义. 合同代码必须以相当严格和受限的方式定义(这可能是一件好事), 考虑用例). 记住这一点,考虑第40行中的函数定义 BoxingOracle.sol

函数_getMatchIndex(bytes32 _matchId)私有视图返回(uint) { ... }

好,我们先来快速概述一下这里包含的内容. function 将其标记为函数. _getMatchIndex 是函数名(下划线是表示私有成员的约定——稍后讨论). 它有一个参数,名为 _matchId 类型的(这次使用下划线约定来表示函数参数) bytes32. The keyword private 实际上使成员在作用域中为私有, view 告诉编译器这个函数不会修改区块链上的任何数据,最后: ~~~ solidity 收益(单位) ~~~

这表示函数返回一个int(返回void的函数将没有int) returns clause here). 为什么int在括号里? 这是因为Solidity函数可以并且经常返回 tuples.

现在考虑下面第166行中的定义:

getMostRecentMatch(bool _pending)公共视图返回(
        bytes32 id,
        string name, 
        字符串的参与者,
        uint8 participantCount,
        uint date, 
        MatchOutcome结果, 
        Int8 winner) { ... }

看看这个的退货条款! 它返回1 2 7个不同的东西. 这个函数以元组的形式返回这些东西. Why? 在发展过程中, 你会经常发现自己需要返回一个结构体(如果是JavaScript的话), 你想要返回一个JSON对象, probably). Well, 在撰写本文时(尽管将来可能会发生变化), Solidity不支持从公共函数返回结构. 所以你必须返回元组. 如果你是Python爱好者,你可能已经对元组很熟悉了. 然而,许多语言并不真正支持它们,至少不是以这种方式.

参见第159行返回元组作为返回值的示例:

return (_matchId, "", "", 0,0, MatchOutcome ..Pending, -1);

我们如何接受这样的返回值? 我们可以这样做:

var (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false); 

或者,你可以事先显式地声明变量,并使用它们的正确类型:

//声明变量 
bytes32 id; 
string name; 
... etc... 
int8 winner; 

//赋值 
(id, name, part, count, date, outcome, winner) = getMostRecentMatch(false); 

现在我们已经声明了7个变量来保存7个返回值,我们现在可以使用它们了. 否则,假设我们只想要一个或两个值,我们可以说:

//声明变量 
bytes32 id; 
uint date;

//赋值 
(id,,,,date,,) = getMostRecentMatch(false); 

看看我们是怎么做的? 我们只找到了两个我们感兴趣的. 看看这些逗号. 我们必须仔细数一数!

Imports

的第三和第四行 BoxingOracle.sol are imports:

import "./Ownable.sol";
import "./DateLib.sol";

如你所料, 这些是从与BoxingOracle相同的合同项目文件夹中存在的代码文件中导入定义.sol.

Modifiers

注意,函数定义附带了一堆修饰符. 首先是可见性:私人的、公共的、内部的和外部的功能可见性.

此外,您将看到关键字 pure and view. 如果有的话,这将向编译器指示函数将进行哪些类型的更改. 这很重要,因为这样的事情是运行函数的最终gas成本的一个因素. 请看这里的解释: 可靠性文档.

最后,我真正想讨论的是自定义修饰符. 看一下61行 BoxingOracle.sol:

addMatch(字符串, 字符串_participants, uint8 _participantCount, uint _date) onlyOwner public返回(bytes32) {

Note the onlyOwner “public”关键字前的修饰符. 这表明 只有船主 的合约可以调用这个方法! 虽然很重要, 这不是Solidity的原生特性(虽然将来可能会有)。. Actually, onlyOwner 是否有我们自己创建并使用的自定义修饰符的示例. 让我们看一看.

首先,在文件中定义修饰符 Ownable.sol的第3行,您可以看到我们已经导入了 BoxingOracle.sol:

import "./Ownable.sol"

注意,为了使用修饰语,我们做了 BoxingOracle inherit from Ownable. Inside of Ownable.sol在第25行,我们可以在“Ownable”合约中找到修饰符的定义:

modifier onlyOwner() {
	require(msg.Sender == owner);
	_;
}

(顺便说一下,这份所有权合同是从 OpenZeppelin公共合同.)

注意,这个东西被声明为一个修饰符, 表明我们可以随心所欲地使用它, 修改一个函数. 注意,修饰语的核心是一个“require”语句. Require语句有点像断言,但不是用于调试的. 如果require语句的条件失败,则该函数将抛出异常. 所以把这个require语句改写一下:

require(msg.Sender == owner);

我们可以说它的意思是:

if (msg.send != owner) 
	抛出异常; 

事实上,在Solidity 0中.4.22及以上版本,可以在require语句中添加错误信息:

require(msg.sender == owner, "错误:此函数只能由合约的所有者调用"); 

最后,在那句看起来很奇怪的台词中:

_; 

下划线是“在这里,执行修改后函数的全部内容”的简写.所以实际上,require语句将首先执行,然后才是实际的函数. 这就像把这行逻辑放在修改后的函数前面.

当然,你可以用修饰语做更多的事情. 检查文档: Docs.

可靠性库

Solidity有一个语言特性,叫做 library. 我们在项目中有一个例子 DateLib.sol.

solid库实现!

这是一个更容易处理日期类型的库. 它在第4行被导入到BoxingOracle中:

import "./DateLib.sol";

它出现在第13行:

为DateLib使用DateLib.DateTime;

DateLib.DateTime is a struct that’s expored from the DateLib contract (it’s exposed as a member; see line 4 of DateLib.sol),我们在这里声明我们正在为特定的数据类型“使用”DateLib库. 在那个库中声明的方法和操作将应用于我们说过的数据类型. 这就是在solid中使用库的方式.

要获得更清晰的示例,请查看其中的一些 OpenZeppelin的数字库,如 Math, SafeCast和SignedMath. 这些可以应用于原生(数字)solid数据类型(而这里我们将库应用于自定义数据类型), 并且被广泛使用.

Interfaces

与主流面向对象语言一样,支持接口. 在Solidity中接口被定义为契约, 但是函数体被省略了. 有关接口定义的示例,请参见 OracleInterface.sol. 在这个例子中, 接口被用作oracle契约的替代品, 其内容驻留在一个单独的合同与一个单独的地址.

命名约定

Of course, naming conventions are not a global rule; as programmers, 我们知道我们可以自由地遵循吸引我们的编码和命名约定. 另一方面, 我们确实希望其他人在阅读和使用我们的代码时感到舒适, 因此,某种程度的标准化是可取的.

项目概述

现在我们已经讨论了代码文件中出现的一些通用语言特性, 我们可以开始更具体地查看代码本身, 对于这个项目.

那么,让我们再一次阐明这个项目的目的. 这个项目的目的是提供一个使用oracle的智能合约的半现实(或伪现实)演示和示例. 从本质上讲,这只是一个调用另一个独立合同的合同.

这个例子的商业案例可以表述如下:

  • 一个用户想要在拳击比赛中下不同大小的赌注, 支付赌注的钱(以太币),并收集他们的奖金,如果他们赢了.
  • 用户通过智能合约进行这些下注. (在现实生活用例中, this would be a full DApp with a web3 front-end; but we are only examining the contracts side.)
  • 一个独立的智能合约——oracle——由第三方维护. 它的工作是维护具有当前状态(待定)的拳击比赛列表, in progress, finished, etc.),如果完成,则是获胜者.
  • 主合同从oracle获取未决匹配列表,并将这些列表作为“bettable”匹配呈现给用户.
  • 主合同接受下注直到比赛开始.
  • 在比赛决胜之后, 主合约根据一个简单的算法来分配赢钱和赔钱, 为自己分一杯羹, 并根据要求支付奖金(输家将失去全部赌注).

投注规则:

  • 有一个定义的最小赌注(在wei中定义).
  • There is no maximum bet; users can bet any amount that they like above the minimum.
  • 用户可以一直下注,直到比赛“进行中”.”

分配奖金的算法:

  • 所有收到的投注都放入“锅”中.”
  • 一小部分从锅中取出,供房屋使用.
  • 每位获胜者将获得奖金的一定比例, 与赌注的相对大小成正比.
  • 一旦第一个用户请求结果,奖金就会计算出来, 在比赛决定之后.
  • 奖金是根据用户的要求颁发的.
  • 在平局的情况下,没有人赢——每个人都拿回自己的赌注,庄家不抽钱.

BoxingOracle: Oracle合同

提供的主要功能

oracle有两个接口, you could say: one presented to the “owner” and maintainer of the contract and one presented to the general public; that is, 使用oracle的契约. 维护人员, 它提供了将数据输入合约的功能, 本质上是从外部世界获取数据并将其放在区块链上. 对于公众,它提供对上述数据的只读访问. 值得注意的是,合同本身限制非所有者编辑任何数据, 但对这些数据的只读访问是公开授予的,不受任何限制.

To users:

  • 列出所有匹配项
  • 列出等待匹配项
  • 获取特定匹配的详细信息
  • 获取特定比赛的状态和结果

To owner:

  • 输入匹配项
  • 更改匹配状态
  • 设置比赛结果

用户和所有者访问元素的说明

User story:

  • 5月9日宣布并确认了一场新的拳击比赛.
  • I, 合同的维护者(也许我是一个知名的体育网络或一个新的出路), 将即将到来的匹配添加到区块链上的oracle数据中, 状态为“pending”.“任何人或任何合同现在都可以查询和使用这些数据,无论他们喜欢什么.
  • 当比赛开始时,我将该比赛的状态设置为“进行中”.”
  • 当比赛结束时, 我将比赛的状态设置为“完成”,并修改比赛数据以表示获胜者.

Oracle代码审查

这篇评论完全基于 BoxingOracle.sol; line numbers reference that file.

在第10行和第11行,声明了匹配的存储位置:

	匹配[]; 
	mapping(bytes32 => uint) matchIdToIndex; 

matches 只是一个简单的数组存储匹配实例, 映射只是一种将唯一匹配ID(一个bytes32值)映射到数组中的索引的工具,这样如果有人递给我们一个匹配的原始ID, 我们可以用这个映射来定位它.

在第17行,定义并解释了match结构:

    //定义匹配及其结果
    struct Match {
        bytes32 id;             //unique id
        string name;            //human-friendly name (e.g., Jones vs. Holloway)
        字符串的参与者;    //a delimited string of participant names
        uint8 participantCount; //number of participants (always 2 for boxing matches!) 
        uint date;              //GMT timestamp of date of contest
        MatchOutcome结果;   //the outcome (if decided)
        int8 winner;            //index of the participant who is the winner
    }

    //可能的匹配结果 
    enum MatchOutcome {
        Pending, //match还没有被决定
        正在进行,//比赛已经开始 & is underway
        平局,//除明显的赢家以外的任何东西(例如.g., cancelled)
        决定//获胜者的参与者索引 
    }

第61行:函数 addMatch is for use only by the contract owner; it allows for the addition of a new match to the stored data.

第80行:函数 declareOutcome 允许合约所有者设置一场“已决定”的比赛,设置获胜的参与者.

第102-166行:以下函数都可以被公共调用. 这是一般对公众开放的只读数据:

  • Function getPendingMatches 返回当前状态为“pending”的所有匹配的id列表.”
  • Function getAllMatches 返回所有匹配项的id列表.
  • Function getMatch 返回由ID指定的单个匹配的完整详细信息.

第193-204行声明了主要用于测试、调试和诊断的函数.

  • Function testConnection 只是测试我们是否能够调用契约.
  • Function getAddress 返回此合同的地址.
  • Function addTestData 向匹配列表添加一堆测试匹配.

在继续下一步之前,请随意探索一下代码. 我建议在调试模式下再次运行oracle契约(如本系列的第1部分所述), 调用不同的函数, 然后检查结果.

BoxingBets:客户合同

定义客户合同(博彩合同)负责什么,不负责什么是很重要的. 客户合同是 not 负责维护真实拳击比赛的列表或宣布其结果. 我们“信任”(是的,我知道, there’s that sensitive word—uh oh—we will discuss this in Part 3) the oracle for that service. 客户合同负责接受投注. 它负责分配奖金并根据比赛结果(从oracle接收)将其转移到获胜者的帐户的算法。.

此外,所有内容都是基于拉动,不存在事件或推动. 契约从oracle中提取数据. 合约从oracle中提取比赛结果(响应用户请求),合约计算奖金并将其转移到用户请求中.

提供的主要功能

  • 列出所有挂起的匹配
  • 获取特定匹配的详细信息
  • 获取特定比赛的状态和结果
  • Place a bet
  • 请求/获得奖金

客户端代码审查

这篇评论完全基于 BoxingBets.sol; line numbers reference that file.

第12和13行, 合同中的第一行代码, 定义一些映射,我们将在其中存储合约的数据.

第12行将用户地址映射到id列表. 这是将用户映射到属于该用户的赌注id列表. So, 对于任何给定的用户地址, 我们可以快速获得该用户所下的所有赌注的列表.

    mapping(address => bytes32[]) private userToBets;

第13行将一个比赛的唯一ID映射到一个下注实例列表. 有了这个,对于任何给定的比赛,我们可以得到那场比赛的所有投注的列表.

    mapping(bytes32 => Bet[]) private matchToBets;

第17行和第18行与到oracle的连接有关. 首先,在 boxingOracleAddr 变量,我们存储oracle合约的地址(默认设置为零). 我们可以硬编码预言者的地址,但这样我们就永远无法改变它了. (不能更改oracle的地址可能是好事也可能是坏事——我们可以在第3部分讨论这个问题). 下一行创建oracle接口的实例(定义在 OracleInterface.sol)并将其存储在一个变量中.

    //拳击结果oracle 
    oracleaddr = 0;
    OracleInterface internal boxingOracle = OracleInterface(boxingOracleAddr); 

如果跳到第58行,将看到 setOracleAddress 函数,在该函数中可以修改此oracle地址 boxingOracle 实例用新地址重新实例化.

第21行定义最小下注大小,单位为wei. 这当然是一个非常小的量,只有0.000001 ether.

    int内部minimumBet = 1000000000000;

在第58行和第66行,我们分别有 setOracleAddress and the getOracleAddress functions. The setOracleAddress has the onlyOwner 修饰符,因为只有合同的所有者可以将oracle切换为另一个oracle(可能) not 这是一个好主意,但我们将在第3部分详细说明). The getOracleAddress function, on the other hand, is publicly callable; anyone can see what oracle is being used.

函数setOracleAddress(地址_oracleAddress)外部onlyOwner返回(bool) {...

getOracleAddress()外部视图返回(地址){ ....

在72行和79行,我们有 getBettableMatches and getMatch 函数,分别. 请注意,这些只是将调用转发给oracle,并返回结果.

getBettableMatches()公共视图返回(bytes32[]) {...

getMatch(bytes32 _matchId) ....

The placeBet 函数非常重要(第108行).

函数placeBet(bytes32 _matchId, uint8 _chosenWinner) public payable { ...

它的一个显著特点是 payable modifier; we’ve been so busy discussing general language features that we have not yet touched upon the centrally important feature of being able to send money along with function calls! 这就是它的基本功能——它是一个可以接受一定数量的钱以及任何其他参数和发送的数据的函数.

我们需要这个,因为这是用户同时定义他们要下什么赌注的地方, 他们打算在这个赌注上押多少钱, 然后把钱寄出去. The payable 修饰符允许. 在接受赌注之前,我们做了一系列检查以确保赌注的有效性. 第111行第一次检查是:

require(msg.value >= minimumBet, "Bet amount must be >= minimum bet");

汇出的钱存放在 msg.value. 假设所有的检查都通过了, on line 123, 我们将把这笔钱转移到神谕的所有权上, 从用户手中夺走这笔钱的所有权, 并为合同所有:

address(this).transfer(msg.value);

Finally, on line 136, 我们有一个测试/调试辅助函数,它将帮助我们知道合约是否连接到一个有效的oracle:

    函数testOracleConnection()公共视图返回(bool) {
        返回boxingOracle.testConnection (); 
    }

Wrapping Up

And this is actually as far as this example goes; just accepting the bet. 分割奖金和支付的功能, 还有一些其他的逻辑被故意省略,以保持示例足够简单,以达到我们的目的, 哪一个是简单地演示oracle与契约的使用. 更完整和复杂的逻辑目前存在于另一个项目中, 哪个是这个示例的扩展,并且仍在开发中.

现在我们对代码库有了更好的理解, 并以它为载体和出发点来讨论Solidity提供的一些语言特性. 这个由三部分组成的系列文章的主要目的是演示和讨论在oracle中使用契约. 这一部分的目的是为了更好地理解这段特定的代码, 并将其作为理解Solidity和智能合约开发的一些特性的起点. 第三部分也是最后一部分的目的是讨论oracle使用的策略和哲学,以及它如何在概念上适应智能合约模型.

其他可选步骤

我强烈鼓励希望了解更多信息的读者使用这段代码并使用它. 实现新功能. Fix any bugs. 实现未实现的特性(例如支付接口). 测试函数调用. 修改它们并重新测试,看看会发生什么. 添加web3前端. 增加了移除比赛或修改比赛结果的功能(在错误的情况下). 取消的比赛怎么办?? 实现第二个oracle. 当然,合约可以自由地使用任意多的oracle,但这会带来什么问题呢? Have fun with it; that’s a great way to learn, 当你这样做(并从中获得乐趣)时,你肯定会记住更多你所学到的东西.

一个可以尝试的样例,不全面的列表:

  • 在本地测试网(在松露中)运行合约和oracle, 如第1部分所述),并调用所有可调用函数和所有测试函数.
  • 增加计算奖金的功能,并在比赛结束时支付奖金.
  • 增加了在平局情况下退还所有赌注的功能.
  • 在比赛开始前增加一个要求退款或取消投注的功能.
  • 添加一个功能,考虑到比赛有时会被取消(在这种情况下每个人都需要退款)。.
  • 实现一个特性,以保证当用户下注时所使用的oracle与将用于确定该比赛结果的oracle是相同的.
  • 实现另一个(第二个)oracle, 它有一些不同的特征, 或者可能服务于拳击以外的运动(注意参与者的数量和列表允许不同类型的运动), 所以我们实际上并不局限于拳击).
  • Implement getMostRecentMatch 它返回最近添加的匹配, 或者是与当前日期最接近的匹配就发生的时间而言.
  • 实现异常处理.

一旦你熟悉了契约和神谕之间关系的机制, 在本系列的第3部分中, 我们将讨论一些战略, design, 以及这个例子引发的哲学问题.

聘请Toptal这方面的专家.
Hire Now
John R. 科辛斯基的头像
John R. Kosinski

Located in 泰国清迈

Member since 2016年2月9日

作者简介

做了近二十年的全栈开发, 约翰研究过物联网, Blockchain, web, 以及使用C/ c++的移动项目, .. NET, SQL和JS.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

以前在

摩根士丹利(Morgan Stanley)

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® community.