Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
311 lines (193 sloc) 15.3 KB

Equity 语言入门

目录

Equity 简介

Equity 是一种高级语言,专门用来编写运行在 Bytom 上的合约程序。通过编写、部署 Equity 智能合约,可以对 Bytom 上的各类资产进行操作。

为方便下文的理解,在正式介绍 Equity 语言之前,首先对 Bytom 上的资产做简要介绍:

  • Bytom 采用 BUTXO 结构,即区块链上记录由多种不同类型的 UTXO 构成的账本。

  • 每一笔 UTXO 都有两个重要属性:asset_idamount,即 资产编号资产数量。一般将这里的 资产数量 称作 value,有时也用 value 抽象地指代一笔 UTXO。

  • 所有 value(UTXO) 都被其对应的合约程序 program 锁定,只有满足 program 中定义的条件的输入,才能解锁 program 锁定的 value

因此,用 Equity 编写智能合约,其目的就是 "描述用智能合约锁定哪些资产,以及定义在哪些条件下可以解锁指定的资产"。

合约(contract)

Equity 合约程序由一个用 contract 关键字定义的合约(contract)构成。合约的构成形式为:

  • contract ContractName ( parameters ) locks value { clauses }

具体来讲:

  • ContractName 是标识符,代表合约的名称,在编写合约时自定义。

  • parameters 是合约的参数列表,这些参数的类型应当在 Equity 数据类型 的范围内。

  • value 是标识符,表示锁定在合约中的 value,编写合约时自定义。

  • clauses 是一个或多个条款函数(clause)组成的列表。

条款函数(clause)

条款函数(clause)描述一种解锁合约中的 value 的方法,以及所需的任何数据(有时还需要支付新的 value)。clause 的结构是:

  • clause ClauseName ( parameters ) { statements }

或者这样:

  • clause ClauseName ( parameters ) requires payments { statements }

具体来讲:

  • ClauseName 是标识符,表示 clause 的名称,编写合约时自定义。

  • parameters 是 clause 的参数列表,这些参数的类型应当在 Equity 数据类型 的范围内。

  • payments支付需求组成的列表,在 条款的支付需求 中详细讲解。

  • statements 由一个或多个语句组成。每个语句都应是 verifylockunlock 中的一种,在 语句 中详细讲解。

合约与条款函数的参数(Parameters)

合约和条款的参数需指明 名称(name)数据类型(type)。参数的写法是:

  • name : TypeName

有多个参数时的写法为:

  • name1 : TypeName1 , name2 : TypeName2 , ...

为了简洁,可以像这样合并相同类型的相邻参数:

  • name1 , name2 , ... : TypeName

所以这两种合约的声明是等价的:

  • contract LockWithMultiSig(key1: PublicKey, key2: PublicKey, key3: PublicKey)
  • contract LockWithMultiSig(key1, key2, key3: PublicKey)

可用的数据类型有:

  • Integer Amount Boolean String Hash Asset PublicKey Signature Program

将在 Equity 数据类型 中介绍这些数据类型。

条款的支付需求(Required payments)

有时候,条款函数(clause)会要求支付一些其他的 value 才能解锁合约中已有的 value。例如用美元交易换取欧元的时候,就需要支付美元以解锁合约中的欧元。

在这种情况下,必须在 clause 中使用 requires 语法来为所需的 value 指定名称并指定其数量和资产类型,形式为:

  • clause ClauseName ( parameters ) requires name : amount of asset

具体来讲:

  • name 是标识符,表示支付的 value 的名称。

  • amountAmount 数据类型的表达式 (expressions)

  • assetAsset 数据类型的表达式。

还有的 clause 需要支付两种或更多种的 value 才能解锁合约。这时,可以在 requires 后按这种格式来写:

  • ... requires name1 : amount1 of asset1 , name2 : amount2 of asset2 , ...

语句 (Statements)

条款函数(clause)的主体部分包含一个或多个语句:

  • verify 语句用来验证表达式的结果是否为真。

  • unlock 语句用来解锁合约中锁定的 value。

  • lock 语句可以将原合约中的 value 以及支付给条款函数的 value 锁定至新的合约中。

Verify 语句 (Verify statements)

verify 语句的格式如下:

  • verify expression

expression(表达式)的值必须是布尔型(Boolean)。只有当每个 verify 语句的检测结果都为 true 时,cluase 才能被成功执行。

一些例子如下:

  • verify above(blockNumber) 检测当前块的区块高度是否高于 blockNumber
  • verify checkTxSig(key, sig) 检测给定的签名,是否与预先设定的公钥以及解锁合约的交易匹配。
  • verify newBid > currentBid 检测某个数值,是否(严格)大于另一个数值。

Unlock 语句 (Unlock statements)

Unlock 语句只有一种格式:

  • unlock value

其中,value 是在 contract 声明时、 locks 关键字后指定的、合约中锁定的 value 的名称。该语句释放了合约中的 value,使得 value 可被用于任何用途。即没有指定一个新的合约来锁定释放的 value。

Lock 语句 (Lock statements)

Lock 语句的格式如下:

  • lock value with program

该语句用 program 锁定了 value (此处的 value 可以是合约中锁定的 value 的名称,也可以是向 clause 支付的其他 value)。program 处的表达式必须是 Program 类型。

Equity 数据类型

Equity 编译器支持以下数据类型:

  • integer - 整数类型

    • Amount: 资产的数量 (更确切地说,是一些以最小单位记的资产数量),范围是 0 ~ 2^63,类型为 Amount 的变量表示锁定在合约中的资产

    • Integer: 介于 2^-63 ~ 2^63 之间的整数

  • boolean - 布尔类型

    • Boolean: 值为 true 或 false
  • string - 字符串类型,以十六进制字节串的形式出现

    • String: 普通的字符串

    • Hash: 哈希得到的字符串

    • Asset: 资产编号

    • PublicKey: 公钥

    • Signature: 私钥签名后的消息

    • Program: 用字节形式表示的程序码

表达式 (Expressions)

Equity 支持很多种表达式,这些表达式可用于 verifylock 语句,也可以用在 clause 声明时的 requires 部分里。

本节的 expr 代表一段表达式(expression)。例如 expr1 + expr2 就可以看作是 20 + 15 或更复杂的语句。

  • - expr : 对数学表达式取负值
  • ~ expr : 对字节串(byte string)做按位翻转(invert the bits)

下面的每种表达式都使用数字型操作数(numeric operands)(即IntegerAmount类型),并且返回一个 Boolean 型的结果:

  • expr1 > expr2 : 检测 expr1 是否大于 expr2
  • expr1 < expr2 : 检测 expr1 是否小于 expr2
  • expr1 >= expr2 : 检测 expr1 是否大于或等于 expr2
  • expr1 <= expr2 : 检测 expr1 是否小于或等于 expr2
  • expr1 == expr2 : 检测 expr1 是否等于 expr2
  • expr1 != expr2 : 检测 expr1 是否不等于 expr2

下面的表达式用于 byte strings,且返回值也是 byte strings:

  • expr1 ^ expr2 : 得到两操作数按位异或(XOR)的结果
  • expr1 | expr2 : 得到两操作数按位或(OR)的结果
  • expr1 & expr2 : 得到两操作数按位与(AND)的结果

下面的表达式用于数值型(numeric)操作数(IntegerAmount),返回数值型的结果:

  • expr1 + expr2 : 将两操作数相加
  • expr1 - expr2 : 从 expr1 中减去 expr2
  • expr1 * expr2 : 将两操作数相乘
  • expr1 / expr2 : 用 expr1 除以 expr2
  • expr1 % expr2 : 即 expr1expr2 取余
  • expr1 << expr2 : 将 expr1 按位左移 expr2
  • expr1 >> expr2 : 将 expr1 按位右移 expr2

还有其他的表达式类型:

  • ( expr ) : 依然表示 expr 本身

  • expr ( arguments ) : 表示函数调用,传入的参数 arguments 是一些用逗号分隔的表达式,具体可查阅下面的 函数 (functions)

  • 单独出现的标识符表示变量本身

  • [ exprs ] : 表示一个列表(list),其中 exprs 是一些用逗号分隔的表达式(列表参数目前仅用于checkTxMultiSig)

  • - 开头的一串数字序列属于 Integer 类型

  • 单引号 '...' 之间的字节序列(bytes)表示一个字符串

  • 前缀 0x 后跟 2n 个十六进制数字,则表示 n 字节长的字符串 (注:每个十六进制数为4位,每个字符为8位)

函数 (Functions)

Equity 内置了一些函数,可用在 verify 语句和其他地方:

  • abs(n) 返回数字 n 的绝对值。
  • min(x, y) 返回 x 和 y 两数中较小的那个数的值。
  • max(x, y) 返回 x 和 y 两数中较大的那个数的值。
  • size(s) 传入一个任意类型的表达式,返回其以字节为单位时的长度,返回值类型是 Integer
  • concat(s1, s2) 对两个字符串做拼接,从而生成一个新字符串。
  • concatpush(s1, s2) 传入两个字符串 s1 和 s2,返回的结果是 s1 与一段 Bytom 虚拟机操作码(BVM opcodes) 的拼接,这段操作码的作用是将 s2 送入 Bytom 虚拟机的栈。此函数通常用于嵌套合约中。具体可参考 BVM 操作码
  • below(height) 传入 Integer 类型的值,返回 Boolean 型的值,判断当前区块高度是否低于参数 height,如果是则返回 true,否则返回 false。
  • above(height) 传入 Integer 类型的值,返回 Boolean 型的值,判断当前区块高度是否高于参数 height。
  • sha3(s) 传入 string 类的值,返回其 SHA3-256 的哈希值(返回值类型为Hash)。
  • sha256(s) 传入 string 类的值,返回其 SHA-256 的哈希值(返回值类型为Hash)。
  • checkTxSig(key, sig) 传入 PublicKeySignature,返回 Boolean 型的值,以检测签名 sig 是否与公钥 key 和解锁交易匹配。
  • checkTxMultiSig([key1, key2, ...], [sig1, sig2, ...]) 分别传入列表形式的 PublicKeysSignatures,返回 Boolean 型的值,以检测是否每个 sig 都与 key 以及解锁交易匹配。关于列表参数顺序的问题:并非所有公钥都需要有签名与其匹配,但所有签名都应有匹配的公钥,并且这些公钥/签名必须在各自的列表中按相同的顺序排列。

Equity 合约的规则 (Rules for contracts)

只有遵循以下规则的 Equity 合约才是有效的:

  • 标识符不能互相冲突。例如,clause 参数的名称不能与 contract 参数的名称相同。(但是,两个不同的 clause 可以使用相同的参数名,这不构成冲突)
  • 每个 contract 参数必须至少在一个 clause 中被使用。
  • 每个 clause 参数必须在 clause 内被使用。
  • 每个 clause 必须使用 lockunlock 语句处理合约中锁定的 value。
  • 每个 clause 还必须用 lock 语句处理所有的支付给 clause 的 value(如果有的话)。

示例 (Examples)

单公钥锁定的合约

下面这个合约叫做 LockWithPublicKey,是最简单的合约之一。通过阅读本文档,可以详细了解它是如何工作的。

contract LockWithPublicKey(pubKey: PublicKey) locks value {
  clause spend(sig: Signature) {
    verify checkTxSig(pubKey, sig)
    unlock value
  }
}

这个合约的名称是 LockWithPublicKey。它锁定了一笔资产,被称作 value。在锁定 value 的交易(即创建合约的交易)中必须为 LockWithPublicKey 指定合约参数,即pubKey

LockWithPublicKey 包含一个 clause,这意味着有一种(且只有一种)途径解锁 value:把 Signature 作为参数,调用 spend 条款函数。

spend 条款中的 verify 检查传入的 Signature(sig)是否与 pubKey 和尝试解锁 value 的交易匹配。如果检查成功,则 value 被解锁。

借贷合约

接下来是一个更具挑战性的例子:叫做 LoanCollateral

contract LoanCollateral(assetLoaned: Asset,
                        amountLoaned: Amount,
                        repaymentHeight: Integer,
                        lender: Program,
                        borrower: Program) locks collateral {
  clause repay() requires payment: amountLoaned of assetLoaned {
    lock payment with lender
    lock collateral with borrower
  }
  clause default() {
    verify above(repaymentHeight)
    lock collateral with lender
  }
}

合约的名称是 LoanCollateral。它锁定了一些被称为 collateral 的 value。在创建此合约的交易中,必须指定五个合约参数:assetLoanedamountLoanedrepaymentHeightlenderborrower

此合约包含两个 clause,也就是说,有两种途径来解锁 collateral

  • repay() 不需要传入数据,但需要支付与 amountLoaned 等量的资产 assetLoaned
  • default() 既不需要传入数据,也不需要支付资产

合约的含义是:贷方 lender 与借方 borrower 以原子交易的形式进行抵押贷款,即用抵押物换取某种资产。在贷方收到所需的数量的资产 assetLoaned 的同时,将抵押物 collateral 的所有权交给借方。但是,如果交易的截止时间已过,则贷方有权将抵押物收回。

repay() 条款函数通过两个简单的 lock 语句将 collateral 发送给贷方,且将 assetLoaned 发送给借方。回想一下,向区块链参与者发送 value,其实就是将 value 锁定至允许接收者解锁的 program。

default() 条款函数的 verify 语句确保在截止区块高度已过时,可以将抵押物 collateral 锁定给贷方 lender。需要注意的是,这一过程并不是在截止时间到达的时刻自动发生的。贷方(或某个人)必须构造一个调用 LoanCollateral 合约的 default() 条款的交易,才能解锁抵押物 collateral 并将其重新锁定给 lender

You can’t perform that action at this time.