作者:Joe Bender(Hiro PBC)
2021年7月27日
自 1 月份 Stacks 2.0上线以来,开发者们已经拥抱并探索了Clarity 的世界。
他们喜欢用熟悉的、基于 LISP 的语言构建智能合约,该语言利用强大的静态类型系统并重视安全性。 Clarity 还消除了像以太坊的 Solidity 这样的图灵完备的智能合约语言带来的诸多问题。现在 Stacks 开发者社区正在大步向前并迅速成熟,我们开始看到一些模式的出现,例如最佳实践、最佳智能合约结构、常见设计选择以及最常用的功能和关键字。 Clarity 中的关键字和函数是语言的基本构建块,并执行操作区块链上的数据和价值所需的机制。
为了帮助开发者更轻松地开始使用 Clarity 构建智能合约,我们编制了一份最有用、最受欢迎的 Clarity 关键字和函数列表。 为此,我们使用了来自 Clarity搜索 (Clarity Search) 的 API,Clarity搜索是一个用于显示所有智能合约源代码的搜索引擎,已成功部署在 Stacks 主网上。 对于想要浏览社区中其他开发者构建的 Clarity 范例的开发者来说,这是一个很有用的工具。
我们的探索结果包括这五个要素:tx-sender、block-height、define-trait & impl-trait、define-map 和define-constant。
tx-sender
tx-sender 是Clarity语言中最常用的关键字。 这是一个不需要输入的全局关键字,但在Clarity 智能合约中使用时,tx-sender 将输出一个主体 (Principal)。 主体是一种 Clarity 原生类型,表示可以拥有代币余额的实体。 Stacks 上有两种类型的主体:区块链上可以发送交易的哈希钱包地址(由公钥/私钥保护),以及存在于网络上的 Clarity 智能合约(没有私钥,无法发送交易)。 因此,每当在智能合约中使用 tx-sender 时,它都会使用与最初调用合约的地址相关联的标识符值来实例化该变量。 在 Stacks 上,钱包地址和主体由 40 个字母数字字符组成的字符串表示。 (例如:STJRM2AMVF90ER6G3RW1QTF85E3HZH37006D5ER1)
了解下更多背景, Stacks 地址基于三个组成部分:前缀 (S)、描述地址类型的版本字节(T =testnet、P = mainnet、M = multisig mainnet、N = multisig testnet),其余的是hash160公钥,表示为 Crockford Base32 字符串。
在这个基本示例中,调用此合约的人试图将一定数量的 STX 从他们自己的地址转移到朋友的钱包中。 调用 stx-transfer 函数,需要三个逻辑参数:数额、发送 STX 的主体以及应接收代币的地址。 手动将地址输入到合约中将是低效和有潜在危险的。 这是一个极长的字符串,很容易拼写错误,而且它还会通过消除抽象来破坏合约的公共可重用性。 通过使用 tx-sender 关键字,Clarity 合约可以使用调用合约的主体的地址自动地“自动填充” tx-sender 变量。 这样,区块链就知道从哪里提取资金以完成所需的交易机制。
tx-sender 似乎是在智能合约中实现归属的一种基本方式,但它是任何去中心化操作的组成部分。 几乎可以将其视为电子邮件中的“发件人”字段。 tx-sender 是一种将主体纳入 Clarity 智能合约的可靠易用的方式。
block-height
每个 Clarity 开发者工具箱中的另一个关键字是block-height。 有时将有关区块链操作的统计信息注入智能合约很有用。 区块链的核心是状态机,拥有一个跟踪矿工验证的最新状态的变量可能很有价值。 为了解决这个问题,在 Clarity 中使用 block-height 将返回一个代表 Stacks 区块链当前块高度的无符号整数值。
它也可以用作时间估算器。 由于区块链是一个如此安全的计算机科学系统,因此很难将某些真实世界的信息输入网络。 处理特定的时间和持续时间可能会有问题,因为区块链没有原生的内部时钟。 因此,可预测的块数和它们之间一致的待处理时间可以用作时间流逝的有用相对跟踪器。
上面的例子是一个实用的 Clarity 合约,用于创建在 Stacks 网络上成功开采区块的矿工们的注册表。 为此,该开发者创建了一个简单的地图,用于跟踪每个新区块号以及获得区块奖励的矿工主体。 这是在 Stacks 上维护实时存档和挖掘历史记录的超级有用的功能。 从逻辑上讲,这个地图只需要一个获胜矿工主体的元组,以及一个相关区块号的无符号整数。 使用区块高度获取有关最近挖出区块的数据是一种简单轻便的方法,可以快速获取重要指标,并为合约中的特定操作添加基于时间的先决条件。
define-trait 和 impl-trait
如果今天要为区块链行业重现史蒂夫鲍尔默(Steve Ballmer)的经典视频,我想他会喊 “互操作性!互操作性!互操作性!”
区块链仍然是处于实验阶段的一项极为新兴的技术。 有数千个不同的团队致力于推动技术的极限,但重要的是要制定标准,让断开连接的智能合约和代币在一起“玩得开心”。您看到早期互联网中出现了相同类型的互操作性。 HTTP、蓝牙、RSS、XML,甚至像 .PDF 这样的特定文件类型都在努力让不同的硬件和软件共享一个共同的词汇。
Clarity 中的 trait 系统是一种允许简单定义和配置通用接口的机制。 define-trait 函数概述了将合约视为 trait 的必要名称、函数、参数类型和返回类型。然后,这允许独立的智能合约相信它们之间有一组共同的功能。一旦 trait 被定义并公开部署到区块链,一个简单的 impl-trait 调用就会向网络发出信号,表明智能合约确实在实现预先存在的 trait 的特性。
最近部署到 Stacks 网络上的非同质化代币 (NFT) trait是一个完美的例子。 在测试网时代,由于开发者想要测试 Clarity 的表达功能,因此在资源管理器和 Github 上出现了各种 NFT 实验。 然而,社区接受的、正式的 NFT 标准尚未建立。 这导致所有 NFT 都实现了自己专门的、细致入微的方法和功能,以实现 NFT 功能。 当社区开发 NFT trait并部署到网络中时,一切都发生了变化! 现在,如上所示,智能合约必须包含四种不同的方法才能被视为Stacks上符合标准的 NFT。 这些方法允许合约设置原始所有者、连接元数据、检查最后注册的 NFT 或转移所有权。
一旦定义了trait,并且开发者希望将其执行到他们的新智能合约中,只需合约顶部的一行代码,表明其遵守NFT trait。 上述合约是在 Stacks 区块链上重创 Beeple NFT。 这个合约的创建者所要做的就是使用函数impl-trait,后跟一个trait标识符,其中包含定义trait的合约的主体。 一旦实现了trait,该合约就需要包含我们之前提到的四种必需方法,这些方法允许 NFT 正常运行。 它还允许 NFT 元数据显示在钱包或资源管理器等产品中。 来看看资源管理器中的 beeple.clar 合约,自己调查源代码和执行trait !
define-map
就编程语言而言,数据结构是处理和操作信息集不可或缺的一部分。 您可能熟悉用于存储信息的数组、列表、树、图形、表格、集合和其他数据结构。 在 Clarity 中,地图 (Maps) 用于跟踪特定数据在内存中的位置。 它们是关联两组信息的一种简单但有效的方法。
在上面的示例中,开发者将合约部署到 Stacks 区块链,以维护用户提交的quote。 要将quote添加到库中,个人只需要调用合约的 add-quote 方法并传入他们选择的字符串。 这些quote及其相关标识符是如何存储的? 地图被定义为一种数据结构,用于对quote的字符串和任意递增的 ID 进行编目。 每当添加新quote时,都会使用 map-insert 函数将其附加到地图中。 由于地图状态存在于智能合约中的区块链上,因此任何用户都可以调用 get-quote-by-id 方法,传入报价标识符,并通过地图接收与该 ID 关联的map-get函数。
虽然地图可能看起来像一个简单的元组列表,但它实际上解锁了大量对开发者有价值的独特功能。 能够以有组织的方式方便地存储和调用数据对于为众多用户提供服务的更强大的合约至关重要。
define-constant
define-constant 是 Clarity 中最常用的函数之一,这是因为它的多功能性。 有时,作为开发者,您需要一些持久的全局变量来在整个合约中执行必要的功能。 实现这一点的最安全、最有组织的方法是使用define-constant 函数在您的 Clarity 合约中实例化常量变量。 传递到定义中的表达式在合约启动时按照它在合约中提供的顺序进行评估。 与其他类型的定义语句类似,define-constant 只能在其他函数之外使用。 你不能在函数本体的中间放置一个定义常量的语句,因为那样它就不能被全局访问。
Boom.Money 的新 Stacking NFT:Boomboxes 是一个实现常量特别好的 Clarity 智能合约。 这是一个极其复杂的软件,它处理加密货币,与Stacks 的核心共识机制交互,并跟踪各种stacking (栈押) 周期的长度。 实现 Boombox 机制需要一些全局常量。
它定义了一个常量 dplyr,并将 tx-sender 分配给它。 之前,我们知道 tx-sender 只是部署合约的用户的地址。 该开发人员可能需要在合约后期提供他们的个人地址(例如,允许调用合约)。 使用define-constant 将主体实例化为全局变量是在各种方法中使用它的一种快速简便的方法。
它还使用定义常量设置某人需要参与的最小 STX 数量。 通过将 minimum-amount 实例化为 100000000 uSTX,该开发人员可以将该变量插入合约内的任何位置,以进行快速输入验证。
不过,这里最有趣的定义常量用法是它如何帮助在智能合约中建立时间感。 Stacks 区块链上的stacking是按周期完成的,一个stacking周期大约持续约 2,100 个比特币区块或 15 天。 因此,对于此智能合约而言,能够辨别特定周期何时开始、进行中或结束非常重要。 首先,该开发者为时间限制常数分配了一个值 u690950。 这实际上与秒或分钟无关,而是stacking周期将开始且stacking参与者必须贡献其 STX 的精确比特币区块号。 然后,当该合约被调用开始支付 NFT 时,它会检查当前区块高度是否小于 time-limit 中定义的区块数量。 这是一种迂回但天才的方法,可以确定现实世界中特定时间何时过去。
正如这个合约所证明的那样,在合约开始时定义常量可能是一种超能力。 该开发者实例化了五个巧妙的常量,然后在整个 Clarity 合约中高效地使用它们。 在为 Stacks 开发软件时,将它们视为友好的全局变量。