Skip to content

Latest commit

 

History

History
216 lines (164 loc) · 11.5 KB

2.3 语法规范(下)——TypeScript篇.md

File metadata and controls

216 lines (164 loc) · 11.5 KB

2.3.1 TypeScript 基本规范

智能合约语法使用的是 Typescript 语言的子集:

  • 所有变量都有具体的类型,不使用nullanyneverobjectunknown 等类型
  • 不支持Symbol
  • 不支持交叉类型(如string & number)和联合类型(如string | null)
  • 不支持生成器和异步语法(不使用Promiseasync/await)
  • 不使用强制类型转换(不使用<string>namename as string)
  • 只能使用引擎内置的类型、对象、函数
  • 变量、函数等必须有明确的类型或可推断出明确的类型,不可以是any
2.3.1.1 常用类型说明
  • 简单类型:指numberstringbooleanbigint之一
  • 状态容器:指Mapping<T>Vecotr<T>
2.3.1.2 一个合约文件可包含的内容
  • 常量声明
  • 数据接口类型声明
  • 自定义状态类型声明
  • 合约类定义

2.3.2 常量声明

一个合约文件中可以有多个常量声明,使用const关键字声明,需要注意的是:常量只能使用四种简单类型(stringnumberbigintboolean),其他类型包括object,any等都不支持,例:

const DEFAULT_NAME = 'name'
const DEFAULT_INTEVEL = 3 * 1000

2.3.3 数据接口类型声明

一个合约文件中可以有多个数据接口类型声明,数据接口类型用于公开方法的参数及返回值,使用interface关键字声明,限制如下:

  • 成员只能使用简单类型、Array、数据接口类型,成员可以是可选的(使用?语法声明)
  • 不支持联合类型(如string & numberstring | number)
  • 如果成员是Array,必须指定泛型参数。泛型类型可以是简单类型、Array、数据接口类型,泛型类型参数如果本身不是泛型推荐使用简写形式(如:names: string[])
  • 数据接口类型的嵌套深度不能超过 3
  • 数据接口类型不可以使用泛型定义(不支持inteface Data<T> {...})
  • 不支持只读成员(不支持readonly)
  • 不支持索引访问器

示例:

interface AddressInfo {
  province: string
  city: string
  street: string
}

interface PersonInfo {
  name: string
  age?: number
  sex: boolean
  address: Address
}

interface PeopleResultInfo {
  count: number
  pepole: PersonInfo[]
}

2.3.4 自定义状态类型声明

一个合约文件中可以有多个状态类型声明,状态类型用于合约状态,类似于传统的POJO使用class关键字声明,限制如下:

  • 成员只能属性定义或构造函数,属性可以使用简单类型、状态容器、状态类型
    • 状态容器指Mapping<T>(类似于Map<stirng,T>)或Vecotr<T>(类似于Array<T>),状态容器中的数据会自动持久化
  • 成员可以是可选的(使用?语法定义),可以初始化默认值。除可选成员外的所有成员属性必须通过默认值或构造器初始化
  • 只支持实例成员(不支持static)且可见性为公开(public可省略),不支持private, protected
  • 如果成员是状态容器,必须指定泛型参数。泛型类型可以是简单类型、状态容器和状态类型
  • 状态类型的嵌套深度不能超过 3
  • 状态类不可以使用泛型定义(不支持class StateData<T> {...})
  • 不能是抽象类(不支持abstract)
  • 不支持实现接口(不支持implements语法)和继承(不支持extends语法)
  • 不支持索引访问器
  • 不支持只读成员(不支持readonly)
  • 不支持gettersetter
  • 所有非可选成员必须初始化,可以在声明时初始化或在构造函数中初始化
  • 可以声明一个公开的构造函数,状态类构造函数不能产生异常 由于状态数据需要从数据库中加载,需要通过无参构造函数初始化(所有的参数都为undefined,随后再初始化各个成员属性)。引擎在调用构造函数时不能产生异常,否则会导致合约加载失败

示例:

class PayState {
  payTimes: number
  amount: bigint

  constructor() {
    this.payTimes =  0
    this.amount = BigInt(0)
  }
}

class PayStateDefault {
  payTimes = 0
  amount = BigInt(0)
}

class PayStateOptional {
  payTimes = 0
  amount?: bigint
}

2.3.5 合约类定义

一个合约文件中必须有且仅有一个合约类定义,使用class关键字定义,合约类必须是AschContract的子类。合约类只允许合约状态和方法两类成员,基本要求如下:

  • 使用export关键字修饰
  • 必须从AschContract直接继承,不支持多重继承
  • 不能是泛型类(不能有泛型参数)
  • 不能是抽象类(不支持abstract)
  • 不支持实现接口(不支持implements语法)
  • 不支持索引访问器
  • 不支持gettersetter
  • 只支持实例成员,不支持静态成员(不支持static)

下面来逐个介绍合约中两类成员的具体规范。

2.3.5.1. 合约状态

合约状态是可以自动进行持久化的合约成员属性。开发者只需要给合约的成员属性赋值,引擎会自动把这些状态持久化到区块链中,对于合约状态来说:

  • 类型必须是简单类型、状态类型、状态容器之一,
  • 如果成员是状态容器,必须指定泛型参数。泛型类型可以是简单类型、状态容器和状态类型
  • 状态类型的嵌套深度不能超过 3
  • 所有合约状态成员必须初始化,可以在声明时初始化或在构造函数中初始化
  • 状态不可以是可选的(不可以是undefined)
  • 可见性为公开的状态可以通过HTTP接口查询其状态值(见本文后续介绍),非公开状态不可直接查询(可通过查询方法实现查询)
2.3.5.2 合约方法

合约类中的方法都必须是成员方法(不支持static),不支持异步语法(Promiseasync/await)和生成器语法(generator)。可分为以下几类

  • 构造器
  • 可调用方法(可见性为公开的普通方法)
  • 资产接收方法(使用payable注解)
  • 查询方法(使用constant注解)
  • 内部方法(可见性为非公开的普通方法privateprotected)
2.3.5.3 构造器

一个合约只能有一个构造器,是合约类的初始化方法,名称必须为constructor,仅在合约注册时执行一次,具体要求如下:

  • 可见性必须是公开
  • 签名必须是constructor() {...},没有参数也没有返回值
  • 可以访问this.context
  • 调用构造器不应产生异常,否则合约无法注册成功
  • 不可以访问this.transfer,否则会产生异常导致合约无法注册(因为合约注册时,合约账户没有任何资产)
2.3.5.4 可调用方法

一个合约可以有多个可调用方法,是合约类中可见性为公开的,且没有注解修饰的成员方法,具体要求如下:

  • 可见性必须是公开,否则外部不可访问
  • 每个参数必须声明明确的类型,参数类型必须是简单类型、Array、数据接口类型之一
  • 如果成员是Array,必须指定泛型参数。泛型类型可以是简单类型、Array、数据接口类型,泛型类型参数如果本身不是泛型推荐使用简写形式(如:names: string[])
  • 不支持可选参数、不支持参数默认值,也不支持展开参数(...args: string[])
  • 返回值类型同参数类型要求相同,必须明确声明返回值类型,否则返回值无法从外部获取
  • 可以访问this.contextthis.transfer(如合约账户余额不足,则会失败)
2.3.5.5 资产接收方法

一个合约可以多个资产接收方法,资产接收方法是使用payable注解的公开方法,用于接收调用转入智能合约的资产,要求如下:

  • 可见性必须是公开
  • 必须有两个参数分别为金额与资产名称,一般采用 amount 和 currency 命名
  • amount 类型为bigint
  • currency 类型必须为string
  • 可以不声明返回类型,如果声明了返回类型必须void
  • 可以访问this.contextthis.transfer(如合约账户余额不足,则会失败)
  • payable有一个可选参数,类型为{ isDefault?: boolean },用于表示是否是默认的资产接受方法(使用@payable({ isDefault: true })注解)。一个合约中最多只能有一个默认资产接受方法
2.3.5.6 查询方法

一个合约可以有多个查询方法,资产接收方法是使用constant注解的公开方法,用于实现状态查询等只读状态的计算逻辑,具体要求:

  • 可见性必须是公开
  • 必须有返回类型,且必须是简单类型、Array、数据接口类型之一
  • 不可访问this.contextthis.transfer,否则会失败
  • 只能只读访问状态成员,不能修改状态。否则会失败
2.3.5.7 内部方法

一个合约可以有多个内部方法,可见性为保护(protected)或私有(private推荐),具体要求:

  • 可见性必须是保护或私有
  • 不可使用constantpayable注解

2.3.6 智能合约其他语法约定

智能合约语言是Typescript语言的子集,除上节描述的结构约定外,其他主要限制如下:

  • 不可以使用引入第三方库
  • 不支持Symbol
  • 不使用nullanyneverobjectunknown 等类型,undefined可以使用
  • 不使用交叉类型(如string & number)和联合类型(如string | null)
  • 不支持生成器和异步语法(不使用Promiseasync/await)
  • 不使用强制类型转换(不使用<string>namename as string)
  • 一个合约文件只能有一个合约类,这个类必须从AschContract继承而来
  • 不可以定义全局函数、静态函数
  • 智能合约中只能使用合约引擎提供的内置类型、方法和对象,未提供的原Node.js内置的对象、函数或类型是不可用的(如FunctionDate都是不可用的)
  • 私有或保护方法的参数和返回值的定义比较灵活,但请谨慎使用。尽可能避免不确定性
  • 合约中不允许使用try...catch语法,也不允许使用throw语句。任何时候抛出异常(如使用assert语句)即导致中止合约
  • 可调用方法和查询方法参数和返回值的额外要求
    • 由于合约调用时所有参数会被序列化为JSON传递,故只支持可序化的类型(可参考数据接口类的定义)基于效率考虑,全部参数或返回值序列化后的JSON字符串长度应控制在32K以内(length <= 32,767)
    • 查询方法必须声明返回类型,对于可调用方法,如果未声明返回值类型,返回值将被丢弃(不作为调用结果返回)
  • 状态类型和数据接口类嵌套深度不超过3 由于状态容器类型的值可以是状态容器类型或合约状态类型,而状态类型中也可以有状态类型或状态容器(数据接口类似)。基于代码可读性以及状态管理的性能考虑。嵌套的深度不应超过3,如Mapping<bigint>深度称为 1,Vector<Mapping<number>>深度为2;简单自定义类型本身深度为1,包含一个深度为1的容器类型或自定义状态类型深度为2;以此类推
  • 注意,与以太坊的solidity不同的是,在solidity中,给存储状态赋值会导致自动的复制。而在ASCH智能合约中,状态容器或自定义状态中使用的是对象的引用。这样的好处是性能更好、编程更灵活、更符合主流语言的习惯,但也会带来一个问题:当两个状态容器中保存相同的对象引用时,可能会导致误操作。合约引擎会自动检查这种情况的存在,当尝试把一个已经属于合约状态一部分的对象赋值给合约状态时,会抛出异常。