用 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.globalPut
、App.globalGet
、App.globalDel
对应用程序全局状态执行读写。 - 使用
App.localPut
、App.localGet
、App.localDel
对账户本地状态执行读写。 - 使用
App.localGetEx
和App.globalGetEx
执行扩展读取。 - 使用
ScratchLoad
和ScratchStore
对暂存时隙执行读写。
其他改进
我们还新增了一些功能,以支持更轻松地编写更强大的 TEAL v2 应用:
- 位算术表达式:
&
、|
、^
、~
。 - 能够使用
Bytes
从 UTF-8 字符串创建字节字符串。 - 使用
Mode.Signature
和Mode.Application
定义要编写的智能合约类型。 - 编译 PyTeal 程序的新方法:
compileTeal(program, mode)
。
PyTeal 文档中包含关于现有和新功能的更多信息。
有状态实例
新版 PyTeal 的最大新功能是能够创建有状态智能合约,此类合约可在 Algorand 区块链上读取和写入键值对。这可支持智能合约执行复杂任务,例如举行拍卖、管理众筹活动、组织调查或投票。让我们来看看一个使用 PyTeal 实现基本投票应用的智能合约实例。
本例包含两个主要部分:approval_program
和 clear_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)
此智能合约执行多选投票。每种选项都是任意字节字符串,且任何账户都可注册和投票选择任一选项。
该程序具有可配置的注册期,由全局状态键 RegBegin
和 RegEnd
来定义,用于限制账户可注册投票的时间。此外,还有单独可配置的投票期,由全局状态键 VotingBegin
和 VotingEnd
来定义,用于限制可进行投票的时间。
账户必须注册才能投票。每个账户只能投票一次,并且如果账户在投票期结束前选择退出应用,其投票将作废。结果可在应用的全局状态中查看,得票最多的选项胜出。
让我们来看看构成此智能合约的各个部分。
主要条件
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))
])
该程序的此部分负责设置智能合约的初始状态。它将以下键写入全局状态:Creator
、RegBegin
、RegEnd
、VoteBegin
、VoteEnd
。这些键的键值取决于Txn.application_args
列表中的应用调用参数。
注册时
on_register = Return(And(
Global.round() >= App.globalGet(Bytes("RegBegin")),
Global.round() <= App.globalGet(Bytes("RegEnd"))
))
只要账户选择加入智能合约,此代码即运行。它将在当前一轮处于RegBegin
与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))
])
此部分负责账户投票。首先,on_vote
使用Assert
语句来确保当前一轮处于VoteBegin
与VoteEnd
之间。然后,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* 开发工具的最新资讯。