课程链接 https://www.coursera.org/learn/smarter-contracts/home/week/3
为自己学习记的笔记,翻译可能存在问题,望谅解。
目录
开发智能合约 Developing Smart Contracts
时间要素 Time Elements (Part 1)
时间要素 Time Elements (Part 2)(BallotV2 Demo)
时间要素 Time Elements (Part 3)
验证和测试 Validation & Test (Part 1) (BallotV3 Demo)
验证和测试 Validation & Test (Part 2) (BallotV4 Demo)
客户端应用程序 client applications
开发智能合约的方法:
1)从问题陈述开始,
2)分析问题以提出基本设计,其状态变量和功能
3)使用类图表示设计。
4)根据问题陈述,定义状态变量和功能的可见性。
5)根据需求,定义函数的访问修饰符,
6)定义函数的输入变量的验证,
7)定义在函数中完成关键操作时必须成立的条件,
8)声明性地表达使用访问修饰符,可见性修饰符的条件,需要使用 assert 类。
我们将根据这个方法使用 Solidity 语言和 Remix IDE 开发和测试一个智能合约。
学习目标:
1)能够应用前两个模块中所学的概念来编写智能合约
2)分析问题陈述来设计和实现智能契约
3)使用 Solidity 语言和 Remix IDE 编写智能合约。
4)向选票智能合约代码添加特性。
ballot problem
我们将使用已定义的 Solidity 编写的投票问题。解决方案中使用的概念和函数很容易适应任何其他问题。
问题描述:
version 1:一个组织希望获得项目提案 proposal ,一个主席来组织这个项目,提案的数量在创建智能合约时指定。主席注册所有的选民 voter,包括主席在内的选民对提案进行投票。我们将主席的投票权重设置为2,其他选民的选票权重为1,获胜的提案 winning proposal 确定后并向世界宣布, 我们将排除在当前Solidity文档中智能合约中存在的委托投票的函数 the delegation of voting function,我们将不考虑此函数。
现在让我们考虑程序所需的数据或状态变量 state variables。提案的细节和提案集将只跟踪提案编号和每个提案的投票。记录主席和其他投票者的地址,投票者的地址将作为映射到投票者详细信息的关键。
与传统的面向对象分析的主要区别在于智能合约的特定数据类型,如地址和消息中心。现在让我们讨论一下函数。
函数:
1)constructor:构造函数是一个用于创建智能合约的函数。 与常规的面向对象的语言不同,在 Solidity 中只能有一个构造函数。 构造函数与合同具有相同的名称。 调用构造函数的消息的发送者是主席 chairperson。
2)register :注册选民。只有主席可以注册选民,注册信息的发送人必须是主席。
3)vote:包括主席在内的选民。可以对提案投票。这个函数将决定获胜的提案,客户端程序将调用它。
阅读材料
Voting
在典型的投票过程中,首先要注册选民。 通常有注册的截止日期,也有投票期限。 例如,对于美国的大多数州,您必须在投票日前30天进行注册,并且订购要在当天进行。 在这种情况下,必须在投票之前完成注册。 当前的投票智能合约没有这些限制。 例如,可以以任何顺序调用功能寄存器和功能表决。 没有任何规则,例如,必须先登记选民才能投票,投票只能在指定的时期内进行,而获胜的建议只能在所有投票完成后才能决定。
现在,让我们将阶段 stages 和阶段的持续时间 the time duration of the stages 添加到选票版本一。我们为 stage 添加了enum,并为函数中的 stage 添加了逻辑修饰符。
我们用枚举来定义阶段 stage,有四个阶段:初始化 Init,注册 Reg,投票 Vote 和完成 Done.
阶段在智能合约部署时初始化为Init。然后,在构造函数中,Init 被更改为 Reg 阶段。Reg 期结束后,转向 vote 阶段。vote 持续时间过后,stage 设置为 Done。Enum, stage, Init, Reg, Vote,完成。
我们还需要添加时间要素,Solidity 定义一个时间变量 now,那是当下的区块链时间戳 timestamp,我们将增加状态变量 startTime ,在构造函数内将 startTime 初始化为 now,
然后,根据分配给 Reg 和 vote 阶段的时间,如图所示改变投票过程的阶段 。
我们已经添加了startTime变量,本例中的注册期为10天。我们还添加了投票期的持续时间为1天。
现在的solidity变量是block的时间戳,block.timestamp函数,在其中确认交易。 因此,现在可能无法准确反映经过的时间。 对于近似的间隔 approximate intervals 和测试简单的概念 testing simple concept,now是一个方便的时间属性。在具有特定截止日期的实际应用中,更好的解决方案是在创建时将截止时间传递给智能合约的构造者,并将其与当前块时间戳进行比较(如果需要)。
版本2有一个用于 voter 的结构,它有一个用于 proposal 的结构
在 enum stage,定义了智能合约的 init、reg、vote 和 done阶段。
变量 stage 被定义为 enum stage 类型,并且它已经被初始化为Init stage。当我们查看web接口中的输出时请记住 Init 是0,Reg是1,Vote是2,Done是3。
address chairman,主席是唯一可以注册其他选民的人。 mapping,地址被映射到选民,proposal [ ] proposals,其中包含提案的每个结构。 那只是一个称为 uint voteCount 的数据字段。
我们定义一个时间要素 uint startTime ,我们有常规的构造函数,除了前面的代码之外,构造函数还有两行代码,它们将 stage 定义为 reg,start startTime 定义为 now。因此,它启动了 Reg 阶段。
只有到 Reg 阶段时,register 函数才会启动,否则函数将简单的返回。
现在,我们在函数的开头在验证中添加了另一项检查,好的。 这是基于我们创建的 enum stage 的。 用同样的方法,只有在 vote 阶段发生时才可以投票,否则该函数将简单地返回。
winning proposal 只有在 Done 阶段时才会返回一些有用的东西。否则,它甚至不需要计算
pragma solidity ^0.4.0;
contract Ballot {
struct Voter {
uint weight;
bool voted;
uint8 vote;
}
struct Proposal {
uint voteCount;
}
enum Stage {Init,Reg, Vote, Done}
Stage public stage = Stage.Init;
address chairperson;
mapping(address => Voter) voters;
Proposal[] proposals;
uint startTime;
/// Create a new ballot with $(_numProposals) different proposals.
function Ballot(uint8 _numProposals) public {
chairperson = msg.sender;
voters[chairperson].weight = 2; // weight is 2 for testing purposes
proposals.length = _numProposals;
stage = Stage.Reg;
startTime = now;
}
/// Give $(toVoter) the right to vote on this ballot.
/// May only be called by $(chairperson).
function register(address toVoter) public {
if (stage != Stage.Reg) {return;}
if (msg.sender != chairperson || voters[toVoter].voted) return;
voters[toVoter].weight = 1;
voters[toVoter].voted = false;
if (now > (startTime+ 10 seconds)) {stage = Stage.Vote; startTime = now;}
}
/// Give a single vote to proposal $(toProposal).
function vote(uint8 toProposal) public {
if (stage != Stage.Vote) {return;}
Voter storage sender = voters[msg.sender];
if (sender.voted || toProposal >= proposals.length) return;
sender.voted = true;
sender.vote = toProposal;
proposals[toProposal].voteCount += sender.weight;
if (now > (startTime+ 10 seconds)) {stage = Stage.Done;}
}
function winningProposal() public constant returns (uint8 _winningProposal) {
if(stage != Stage.Done) {return;}
uint256 winningVoteCount = 0;
for (uint8 prop = 0; prop < proposals.length; prop++)
if (proposals[prop].voteCount > winningVoteCount) {
winningVoteCount = proposals[prop].voteCount;
_winningProposal = prop;
}
}
}
编译运行,Account 使用主席地址,deploy 3个提案,因为 stage 是一个 public 变量,所以也会显示出来。
复制一个Account 里的其他地址,在 register 里填入,例如 "0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C",注意Account 地址要选回主席地址再去注册。用同样的方法再去注册2个选民。点击 stage 显示2,这意味着时间已经可以开始投票了。
现在开始投票,主席投票,投提案1。第二个账户投提案2。现在查看 winningProposal,显示为1,因为主席投了提案1,单个普通选民投了提案2,主席的权重是2。所以获胜的方案是提案1. 可以查看 stage ,看你是否处在正确的 stage。
第二版中添加了阶段和时间元素。这是一个传统的解决方案,使用 if else 语句通过编程方法验证了条件。现在,我们考虑一些改进的机会。时间和阶段的验证是在功能代码中以编程方式完成的。因此,无论验证失败还是成功,都将执行交易并将其记录在区块链上。
有什么办法可以拒绝交易?以类似于拒绝交易的方式,在区块链协议级别,如果它们不符合规则,如果特定问题的条件不满足交易要求,交易不会被记录在区块链上,这浪费了精力和空间。如果有一种方法可以将验证与函数执行的实际代码区分开,则有一种方法可以声明性地指定特定于问题的规则和条件,以便可以独立地指定它们,并进行审核以确保智能合约它应该做什么?
智能合约的审核特别重要,因为一旦部署智能合约,该合约将是自主的和永久的。
在下一课中,我们将通过使用函数修饰符,required子句,revert 和 assert 声明围绕特定于问题的验证来解决这些问题。
阅读材料
Enum
Time units
学习目标:
1)解释基于特定于问题的验证
2)使用 revert 声明还原事务的概念
3)应用函数修饰符 modifier ,require,assert 的概念;
在本课程中,我们将显示使用特定于实体的声明和错误处理程序修改ballotversion2.sol后的执行情况。
Solidity具有 revert 函数,会导致状态还原。 此异常处理将撤消当前调用中对该状态所做的所有更改,并撤消该事务,并向调用者标记一个错误。异常
我们将引入一个带有所需阶段作为参数的函数修饰符。 首先,我们将修饰符添加到函数头中。 我们将添加带有参数的修饰符,Stage reguiredStage。
你可以看到,在上一个例子中,没有使用任何修饰符。这个例子演示了修饰符, require, assert,以及我们在课堂上讨论过的所有gatekeeper元素。
一个简单的修饰符 validStage,不必使用 if-else 语句检查阶段内部的事务(必须在函数内部执行事务),而可以使用修饰符防止在函数外部发生该事件。
每次要执行函数,register, vote, 或 winningProposal 时,我们都根据经过的时间确保我们处于正确的阶段。
函数名 Ballot,提案数量作为参数。chairman是这个消息的发送者,并指定了chairman投票权重是2。提案的数量是作为参数传递的。为了演示,我们将它设置为3。更重要的是,我们正在为 Reg 阶段做准备。我们已经完成了 Init 阶段。
Register 函数,这里为函数指定了一个 validStage 修饰符,validStage是一个自定义访问修饰符。我们还有一个参数。在函数的开头,我们可以清楚地看到,如果validStage 不是 reg,则该函数将不会执行。
在这个代码里,有一个修饰符,validStage。 您不必使用 if-else 语句检查 stage 内部的事务(必须在函数内部执行事务),而可以使用修饰符防止在函数外部发生。
pragma solidity ^0.4.0;
contract Ballot {
struct Voter {
uint weight;
bool voted;
uint8 vote;
address delegate;
}
struct Proposal {
uint voteCount;
}
enum Stage {Init,Reg, Vote, Done}
Stage public stage = Stage.Init;
address chairperson;
mapping(address => Voter) voters;
Proposal[] proposals;
event votingCompleted();
uint startTime;
//modifiers
modifier validStage(Stage reqStage)
{ require(stage == reqStage);
_;
}
/// Create a new ballot with $(_numProposals) different proposals.
function Ballot(uint8 _numProposals) public {
chairperson = msg.sender;
voters[chairperson].weight = 2; // weight is 2 for testing purposes
proposals.length = _numProposals;
stage = Stage.Reg;
startTime = now;
}
/// Give $(toVoter) the right to vote on this ballot.
/// May only be called by $(chairperson).
function register(address toVoter) public validStage(Stage.Reg) {
//if (stage != Stage.Reg) {return;}
if (msg.sender != chairperson || voters[toVoter].voted) return;
voters[toVoter].weight = 1;
voters[toVoter].voted = false;
if (now > (startTime+ 30 seconds)) {stage = Stage.Vote; }
}
/// Give a single vote to proposal $(toProposal).
function vote(uint8 toProposal) public validStage(Stage.Vote) {
// if (stage != Stage.Vote) {return;}
Voter storage sender = voters[msg.sender];
if (sender.voted || toProposal >= proposals.length) return;
sender.voted = true;
sender.vote = toProposal;
proposals[toProposal].voteCount += sender.weight;
if (now > (startTime+ 30 seconds)) {stage = Stage.Done; votingCompleted();}
}
function winningProposal() public validStage(Stage.Done) constant returns (uint8 _winningProposal) {
//if(stage != Stage.Done) {return;}
uint256 winningVoteCount = 0;
for (uint8 prop = 0; prop < proposals.length; prop++)
if (proposals[prop].voteCount > winningVoteCount) {
winningVoteCount = proposals[prop].voteCount;
_winningProposal = prop;
}
assert (winningVoteCount > 0);
}
}
pragma solidity ^0.4.24;
contract Ballot {
struct Voter {
uint weight;
bool voted;
uint8 vote;
address delegate;
}
struct Proposal {
uint voteCount;
}
enum Stage {Init,Reg, Vote, Done}
Stage public stage = Stage.Init;
address chairperson;
mapping(address => Voter) voters;
Proposal[] proposals;
event votingCompleted();
uint startTime;
//modifiers
modifier validStage(Stage reqStage)
{ require(stage == reqStage);
_;
}
/// Create a new ballot with $(_numProposals) different proposals.
constructor(uint8 _numProposals) public {
chairperson = msg.sender;
voters[chairperson].weight = 2; // weight is 2 for testing purposes
proposals.length = _numProposals;
stage = Stage.Reg;
startTime = now;
}
/// Give $(toVoter) the right to vote on this ballot.
/// May only be called by $(chairperson).
function register(address toVoter) public validStage(Stage.Reg) {
//if (stage != Stage.Reg) {return;}
if (msg.sender != chairperson || voters[toVoter].voted) return;
voters[toVoter].weight = 1;
voters[toVoter].voted = false;
if (now > (startTime+ 30 seconds)) {stage = Stage.Vote; }
}
/// Give a single vote to proposal $(toProposal).
function vote(uint8 toProposal) public validStage(Stage.Vote) {
// if (stage != Stage.Vote) {return;}
Voter storage sender = voters[msg.sender];
if (sender.voted || toProposal >= proposals.length) return;
sender.voted = true;
sender.vote = toProposal;
proposals[toProposal].voteCount += sender.weight;
if (now > (startTime+ 30 seconds)) {stage = Stage.Done; votingCompleted();}
}
function winningProposal() public validStage(Stage.Done) constant returns (uint8 _winningProposal) {
//if(stage != Stage.Done) {return;}
uint256 winningVoteCount = 0;
for (uint8 prop = 0; prop < proposals.length; prop++)
if (proposals[prop].voteCount > winningVoteCount) {
winningVoteCount = proposals[prop].voteCount;
_winningProposal = prop;
}
assert (winningVoteCount > 0);
}
}
* 这段代码有一个改动,把 function Ballot(uint8 _numProposals) public 改成了 constructor(uint8 _numProposals) public
编译运行,Deploy 填3,点击 Deploy,stage 显示为1。复制一个账户地址,粘贴到 register,记得加双引号,然后账户换回主席地址,点击 register,注册成功。再用同样的方式再注册一个,现在 stage 是2,然后主席 vote 填2,账户2 vote 填1,账户3 vote填2,然后stage现在是3,查看 winningProposal 是2,获胜的是提案2。
即使没有登记选民或选票,solidity 文档中为 bollet 提供的 base mark contract 也会将提案0表示为获胜提案。 也就是说,默认情况下,第一个投标(编号为零)将被选为获胜投标。 为解决此问题,我们将在winningProposal函数的末尾使用assert子句。
阅读材料
Solidity Learning: Revert(), Assert(), and Require() in Solidity, and the New REVERT Opcode in the EVM
我们将使用称为 Events 的 solidity 函数与客户端应用程序接口。
事件 events 的概念:
定义事件并将事件推送到订阅的监听器,并使用Ballot示例说明事件。
1)事件的定义。通用格式是 event 事件名称(参数)。
通过事件的名称和任何参数来调用 events。在函数 vote 中,当状态更改为 done 时,我们将调用events 。我们通过调用votingCompleted event 来表明这一点。在 bollet 的例子中,我们将在投票期结束时推送此 event 。事件日志记录 event logging有很多好处。与常规函数调用相反,事件被推送,而常规函数调用拉取以执行操作。通常,event 是向客户端应用程序,用户界面或事务监视器发送指示的重要的里程碑。应用程序可以使用监听器代码监听推送的事件,以跟踪事务,通过事件的参数接收结果,发起拉取请求以从智能合约接收信息。
在下一课程中,我们将在讨论分散式应用程序dApps时探索这些事件处理程序。现在,让我们回顾添加了所有这些功能的整个智能合约。总而言之,我们逐步开发了Ballot智能合约,以说明各种功能,包括时间依赖性,使用访问修饰符访问功能之前的功能代码外部验证,assert 和 require 声明以及 event logging。
阅读材料
Technical Introduction to Events and Logs in Ethereum
Capturing Smart Contract Events in our User Interface (Solidity)