This is the old Algorand Developer Portal. Please head over to dev.algorand.co to explore our newly rebuilt documentation site. Please excuse us as we continue to transition content to the new portal

创建文章

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Article Thumbnail

用 Python 基于 PyTeal 创建有状态智能合约

今年早些时候,我们推出了 PyTeal,这种 Python 语言绑定专用于编写 Algorand 智能合约。现在 PyTeal 经过更新,能够创建 TEAL v2 智能合约,包括有状态智能合约。如果您不熟悉有状态智能合约,可查看此概述。PyTeal 可通过以下 GitHub 网页获取:https://github.com/algorand/pyteal

新功能

新版 PyTeal(即 v0.6.0)增加了多种新操作,并可在 Algorand 区块链上存储状态。让我们来看看!

命令式编程

TEAL v1 与 v2 的一大差异是 TEAL 程序不仅能够通过返回码表示成功或失败,还可对 Algorand 区块链上存储的数据产生副作用。因此,我们为 PyTeal 的函数式编程接口扩增了命令式编程支持。PyTeal v0.6.0 增强了编程人员以更细粒度管理控制流的能力,新增了:

  • Seq,用于创建表达式序列的新表达式。
  • Assert,用于断言某条件为真的新表达式。
  • Return,用于立即退出程序并发出返回码的新表达式。
  • 单分支 If语句。

此外,编程人员还可利用新的 PyTeal 操作来操纵状态:

  • 使用 App.globalPutApp.globalGetApp.globalDel 对应用程序全局状态执行读写。
  • 使用 App.localPutApp.localGetApp.localDel 对账户本地状态执行读写。
  • 使用 App.localGetExApp.globalGetEx 执行扩展读取。
  • 使用 ScratchLoadScratchStore 对暂存时隙执行读写。

其他改进

我们还新增了一些功能,以支持更轻松地编写更强大的 TEAL v2 应用:

  • 位算术表达式:&|^~
  • 能够使用 Bytes 从 UTF-8 字符串创建字节字符串。
  • 使用 Mode.SignatureMode.Application 定义要编写的智能合约类型。
  • 编译 PyTeal 程序的新方法:compileTeal(program, mode)

PyTeal 文档中包含关于现有和新功能的更多信息。

有状态实例

新版 PyTeal 的最大新功能是能够创建有状态智能合约,此类合约可在 Algorand 区块链上读取和写入键值对。这可支持智能合约执行复杂任务,例如举行拍卖、管理众筹活动、组织调查或投票。让我们来看看一个使用 PyTeal 实现基本投票应用的智能合约实例。

本例包含两个主要部分:approval_programclear_state_program 函数。在有状态智能合约中,批准程序负责处理大部分应用调用,包括账户选择加入合约。账户有两种方式来退出智能合约:关闭和清除状态。批准程序用于关闭账户,并可控制是否允许关闭。清除状态程序负责清除账户的状态,且这种退出方式一旦开始就无法被智能合约停止。

在本例中,关闭和清除状态的处理方式相同,因此清除状态程序的内容与批准程序的 on_closeout 分支相同。

from pyteal import *
def approval_program():
on_creation = Seq([
App.globalPut(Bytes("Creator"), Txn.sender()),
Assert(Txn.application_args.length() == Int(4)),
App.globalPut(Bytes("RegBegin"), Btoi(Txn.application_args[0])),
App.globalPut(Bytes("RegEnd"), Btoi(Txn.application_args[1])),
App.globalPut(Bytes("VoteBegin"), Btoi(Txn.application_args[2])),
App.globalPut(Bytes("VoteEnd"), Btoi(Txn.application_args[3])),
Return(Int(1))
])

is_creator = Txn.sender() == App.globalGet(Bytes("Creator"))

get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))

on_closeout = Seq([
get_vote_of_sender,
If(And(Global.round() <= App.globalGet(Bytes("VoteEnd")), get_vote_of_sender.hasValue()),
App.globalPut(get_vote_of_sender.value(), App.globalGet(get_vote_of_sender.value()) - Int(1))
),
Return(Int(1))
])

on_register = Return(And(
Global.round() >= App.globalGet(Bytes("RegBegin")),
Global.round() <= App.globalGet(Bytes("RegEnd"))
))

choice = Txn.application_args[1]
choice_tally = App.globalGet(choice)
on_vote = Seq([
Assert(And(
Global.round() >= App.globalGet(Bytes("VoteBegin")),
Global.round() <= App.globalGet(Bytes("VoteEnd"))
)),
get_vote_of_sender,
If(get_vote_of_sender.hasValue(),
Return(Int(0))
),
App.globalPut(choice, choice_tally + Int(1)),
App.localPut(Int(0), Bytes("voted"), choice),
Return(Int(1))
])

program = Cond(
[Txn.application_id() == Int(0), on_creation],
[Txn.on_completion() == OnComplete.DeleteApplication, Return(is_creator)],
[Txn.on_completion() == OnComplete.UpdateApplication, Return(is_creator)],
[Txn.on_completion() == OnComplete.CloseOut, on_closeout],
[Txn.on_completion() == OnComplete.OptIn, on_register],
[Txn.application_args[0] == Bytes("vote"), on_vote]
)

return program

def clear_state_program():
get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))
program = Seq([
get_vote_of_sender,
If(And(Global.round() <= App.globalGet(Bytes("VoteEnd")), get_vote_of_sender.hasValue()),
App.globalPut(get_vote_of_sender.value(), App.globalGet(get_vote_of_sender.value()) - Int(1))
),
Return(Int(1))
])

return program

with open('vote_approval.teal', 'w') as f:
compiled = compileTeal(approval_program(), Mode.Application)
f.write(compiled)

with open('vote_clear_state.teal', 'w') as f:
compiled = compileTeal(clear_state_program(), Mode.Application)
f.write(compiled)

此智能合约执行多选投票。每种选项都是任意字节字符串,且任何账户都可注册和投票选择任一选项。

该程序具有可配置的注册期,由全局状态键 RegBeginRegEnd 来定义,用于限制账户可注册投票的时间。此外,还有单独可配置的投票期,由全局状态键 VotingBeginVotingEnd 来定义,用于限制可进行投票的时间。

账户必须注册才能投票。每个账户只能投票一次,并且如果账户在投票期结束前选择退出应用,其投票将作废。结果可在应用的全局状态中查看,得票最多的选项胜出。

让我们来看看构成此智能合约的各个部分。

主要条件

program = Cond(
[Txn.application_id() == Int(0), on_creation],
[Txn.on_completion() == OnComplete.DeleteApplication, Return(is_creator)],
[Txn.on_completion() == OnComplete.UpdateApplication, Return(is_creator)],
[Txn.on_completion() == OnComplete.CloseOut, on_closeout],
[Txn.on_completion() == OnComplete.OptIn, on_register],
[Txn.application_args[0] == Bytes("vote"), on_vote]
)

此语句是智能合约的核心。基于合约的调用方式,来选择要运行的操作。例如,如果 Txn.application_id() 为 0,则运行来自on_creation的代码。如果Txn.on_completion()OnComplete.OptIn*,则运行on_register*。如果Txn.application_args[0]"vote"*,则运行on_vote`。如果上述条件均非真,则该程序将退出并报错。让我们来逐一看看以下情况。

创建时

on_creation = Seq([
App.globalPut(Bytes("Creator"), Txn.sender()),
Assert(Txn.application_args.length() == Int(4)),
App.globalPut(Bytes("RegBegin"), Btoi(Txn.application_args[0])),
App.globalPut(Bytes("RegEnd"), Btoi(Txn.application_args[1])),
App.globalPut(Bytes("VoteBegin"), Btoi(Txn.application_args[2])),
App.globalPut(Bytes("VoteEnd"), Btoi(Txn.application_args[3])),
Return(Int(1))
])

该程序的此部分负责设置智能合约的初始状态。它将以下键写入全局状态:CreatorRegBeginRegEndVoteBeginVoteEnd。这些键的键值取决于Txn.application_args列表中的应用调用参数。

注册时

on_register = Return(And(
Global.round() >= App.globalGet(Bytes("RegBegin")),
Global.round() <= App.globalGet(Bytes("RegEnd"))
))

只要账户选择加入智能合约,此代码即运行。它将在当前一轮处于RegBeginRegEnd之间时返回真,也就是说仅可在此期间注册。

投票时

choice = Txn.application_args[1]
choice_tally = App.globalGet(choice)
on_vote = Seq([
Assert(And(
Global.round() >= App.globalGet(Bytes("VoteBegin")),
Global.round() <= App.globalGet(Bytes("VoteEnd"))
)),
get_vote_of_sender,
If(get_vote_of_sender.hasValue(),
Return(Int(0))
),
App.globalPut(choice, choice_tally + Int(1)),
App.localPut(Int(0), Bytes("voted"), choice),
Return(Int(1))
])

此部分负责账户投票。首先,on_vote使用Assert语句来确保当前一轮处于VoteBeginVoteEnd之间。然后,get_vote_of_sender用于检查发送者的账户本地状态中是否包含"voted"键。变量get_vote_of_sender之前在程序中定义为App.localGetEx(Int(0), App.id(), Bytes("voted")),此为扩展 get 操作。与常规 get 操作不同,扩展操作用于检查是否存在键,而不是为缺失的键返回默认值 0。如果账户的本地状态中包含"voted”键,则表示已投票且程序失败而返回 0。

如果账户尚未投票,程序通过Txn.application_args[1] 收到发送者想要投票的选项。而且还会收到choice_tally,即选项当前所获的投票数。该代码使计数加 1,并将新值写回全局状态。然后,向账户本地状态中的"voted”键写入所投票的选项,以记录账户已成功投票。

on_closeout 代码具有相似作用,但会在账户选择退出智能合约时丢弃账户的投票。

总结

有状态智能合约是非常有效的工具,而最新版 PyTeal 可用于更轻松地编写此类合约。PyTeal 中的更多有状态和无状态合约实例可参见此文档。此外,使用 PyTeal 操纵状态的完整指南是非常有用的资源,可帮助理解状态的作用原理和如何利用状态。

立即安装 PyTeal,开始创建 Algorand 智能合约!

订阅开发者周报,获取关于 PyTeal 和其他 Algorand* 开发工具的最新资讯。