合同
Tact 中的合约类似于流行的面向对象语言中的类,只是它们的实例部署在区块链上,不能像 Structs and Messages 那样被传递。
Self-references
契约和[特质][trait]有一个内置的标识符 self
,用于引用它们的字段(持久状态变量和常量)和方法(内部函数):
Structure
每份合同可包括:
- Inherited traits
- Supported interfaces
- Persistent state variables
- Constructor function
init()
- Contract constants
- Getter functions
- Receiver functions
- Internal functions
Inherited traits, with
契约可以继承[traits][trait]的所有声明和定义,并覆盖它们的某些默认行为。除此之外,每个契约和特质都隐式继承了特殊的BaseTrait
trait。
要继承[trait][trait],请在合约声明中的关键字with
后指定其名称。要同时继承多个特质,请在逗号分隔的列表中指定它们的名称,并在后面加上逗号。
由于[traits][trait]不允许有init()
函数,因此继承了声明了任何持久状态变量的trait的合约必须通过提供自己的init()
函数来初始化这些变量。
如果在特质中声明或定义了内部函数和常量,则可将其标记为虚拟或抽象,并在从特质继承的合约中重写。
Supported interfaces, @interface(…)
如果不查看源代码,就很难弄清一个合约是做什么的,有哪些接收器和获取器。有时,无法获得或无法访问源代码,剩下的办法就是尝试拆解合同,并以这种方式对其进行反省,这是一种非常混乱且容易出错的方法,其收益会递减,而且没有真正的可重复性。
为了解决这个问题,创建了OTP-001:支持的接口。据此,Tact 合约可以报告支持的接口列表,作为特殊的supported_interfaces
getter的返回值。使用任何 TON 区块链浏览器都可以在链外访问该获取器—只需指定”supported_interfaces”作为要执行的方法,即可获得十六进制值列表。
这些十六进制值被截断为受支持接口的原始String
值的SHA-256哈希值的前 128 位。该列表中的第一个值必须等于(十六进制符号),即"org.ton.introspection.v0"
的 SHA-256 哈希值的前半部分。如果第一个值是错误的,就必须停止反省合同,因为它不符合支持的接口建议。
要声明支持某个接口,可在 contract 和[trait][trait]声明之前添加一个或多个@interface("…")
属性:
Tact 有一小套在特定条件下提供的接口:
"org.ton.abi.ipfs.v0"
,根据 OTP-003: Self-ABI Reporting - 通过ipfsAbiGetter
配置属性选择加入"org.ton.deploy.lazy.v0"
,符合OTP-005:参数可寻址合约"org.ton.debug.v0"
,但只有在启用了调试模式时才会这样做"org.ton.chain.any.v0"
如果启用了 masterchain 支持,否则为"org.ton.chain.workchain.v0"
标准库中的一些[traits][trait]也定义了它们的接口:
Ownable
trait 指定"org.ton.ownable"
OwnableTransferable
trait 指定"org.ton.ownable.transferable.v2"
Stoppable
trait 指定"org.ton.stoppable"
Resumable
trait 指定"org.ton.resumable"
要启用 supported_interfaces
getter 生成并在 Tact 合约中使用 @interface()
属性,请修改项目根目录下的 tact.config.json
文件(如果该文件不存在,则创建该文件),并 将 interfacesGetter
属性设置为 true
。
如果您的项目基于 Blueprint,您可以在合约的编译配置中启用supported_interfaces
,这些配置位于名为wrappers/
的目录中:
除此之外,蓝图 项目中仍可使用 tact.config.json
。 在这种情况下,除非在 wrappers/
中进行修改,否则 tact.config.json
中指定的值将作为默认值。
Persistent state variables
合约可以定义在合约调用之间持续存在的状态变量。 TON 中的合约支付租金 与它们消耗的持久空间成正比,因此鼓励通过序列化进行紧凑表示。
状态变量必须有默认值或在 init()
函数中初始化,该函数在部署合约时运行。唯一的例外是 map<K, V>
类型的持久状态变量,因为它们默认初始化为空。
Contract constants
与 变量 不同,常量不能更改。 它们的值是在编译时计算的,在执行过程中不会改变。
在合约外定义的常量(全局常量)和在合约内定义的常量(合约常量)没有太大区别。 项目中的其他合同可以使用外部定义的常量。
常量初始化必须相对简单,并且只依赖于编译时已知的值。 例如,如果您将两个数字相加,编译器会在编译过程中计算出结果,并将结果放入已编译的代码中。
与合约变量不同,合约常量不会占用持久状态的空间。 它们的值直接存储在合约的代码 单元格
中。
有关常量的更多信息,请访问其专门页面:常量。
Constructor function init()
在部署合约时,会运行构造函数 init()
。
如果合约有任何未指定默认值的持久状态变量,则必须在此函数中对其进行初始化。
如果合约没有任何持久状态变量,或所有持久状态变量都指定了默认值,则可以完全省略 init()
函数声明。 这是因为除非明确声明,否则所有合约中都默认存在空的 init()
函数。
下面是一个有效空合同的示例:
为方便起见,init()
的参数列表可以使用逗号:
Getter functions
获取函数 不能从其他合约访问,只能导出到链外世界。
此外,获取器不能修改合约的状态变量,只能读取它们的值并在表达式中使用。
请在其专门章节中阅读更多相关信息:获取函数
Receiver functions
Tact 中的接收器函数可以是以下三种之一:
receive()
,用于接收内部消息(来自其他合约)。bounced()
,当该合约发出的消息被退回时会调用。external()
,没有发送者,世界上任何人都可以发送。
用下划线_
命名接收函数的参数时,其值将被视为未使用的值并被丢弃。当您不需要检查接收到的信息,而只想让它传达特定的操作码时,这就很有用了:
Internal functions
这些函数的行为类似于流行的面向对象语言中的私有方法——它们是合约的内部函数,可以通过在其前缀添加特殊的标识符 self
来调用。因此,内部函数有时也被称为”契约方法”。