创建文章

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* 开发工具的最新资讯。