5.Javascript合约介绍

5.1 Javascript合约语言

星火链的JavaScript智能合约符合ECMAScript as specified in ECMA-262标准,运行在V8虚拟机引擎,同时我们针对区块链的性能对V8虚拟机做了优化。

合约的固定结构分为三段。合约上链部署完成后,合约文本会直接存储到合约账户结构中。

  • 初始化接口。合约的初始化函数是 init, 合约部署时自动由虚拟机引擎直接调用init进行合约账户数据的初始化。

  • 执行接口。合约执行的入口函数是 main函数,main中可实现不同的功能接口,并通过参数字符串input选择不同接口。main内部功能接口可实现合约数据存储相关操作(写功能)。

  • 查询接口。合约查询接口是 query函数,query中可实现不同的查询功能接口,并通过参数字符串input选择不同接口。query函数内部功能接口可用于合约账户中数据的读取,禁止进行合约数据存储相关操作。调用过程不需消耗星火令(只读功能)。

下面是一个简单的例子:

"use strict";
function init(input)
{
    /*init whatever you want*/
    return;
}

function main(input)
{
    let para = JSON.parse(input);
    if (para.do_foo)
    {
        let x = {
        'hello' : 'world'
        };
    }
}

function query(input)
{ 
    return input;
}

5.1.1 内置API

JavaScript智能合约的高效执行,星火链实现了部分预编译JavaScript指令,可通过智能合约直接进行调用。

智能合约内提供了全局对象 ChainUtils, 这两个对象提供了多样的方法和变量,可以获取区块链的一些信息,也可驱动账号发起交易。

详见星火链JavaScript合约内置API

5.1.2 合约事件tlog

星火链JavaScript设计了类似Solidity合约event的tlog来支持合约事件,详见tlog

5.1.3 语法限制

区块链上的智能合约需要运行在隔离的沙箱环境,保证其隔离性、安全性和确定性,因此我们对原有的JavaScript语法做了以下裁剪:

  • 源码开头必须添加 "use strict;"

  • 判断使用===!==, 禁用 == !=

  • 使用 +=, -=, 禁用 ++ --

  • 语句块内使用 let 声明变量

  • 语句必须以 ; 结束

  • 语句块必须用 {} 包括起来,且禁止空语句块

  • for 的循环变量初始变量需在条件语句块之前声明,每次使用重新赋值

  • 禁用 ++--

  • 禁止使用 eval, void 关键字

  • 禁止使用 new 创建 Number, String, Boolean对象,可以使用其构造调用来获取对象

  • 禁止使用的数组关键字创建数组

    示例:

    let color = new Array(100); //编译报错
    
    //可以使用替代 new Array(100) 语句;
    let color = ["red","black"]; 
    let arr = [1,2,3,4];
    
  • 禁止使用 try, catch 关键字,可以使用 throw 手动抛出异常。

  • 禁止使用函数: Data, Random 。

  • 禁用可能产生随机结果的关键字:

    "DataView", "decodeURI", "decodeURIComponent", "encodeURI",
    "encodeURIComponent", "Generator","GeneratorFunction", "Intl", "Promise",
    "Proxy", "Reflect", "System", "URIError", "WeakMap", "WeakSet", "Math",
    "Date", "eval", "void", "this", "try", "catch"
    
  • 堆大小限制: 30Mb。

  • 栈大小限制: 512Kb。

  • 执行计步限制: 10240。

  • 合约字节限制: 256Kb。

  • 合约函数调用递归深度最大为4层,即合约A–>合约B–>合约C–>合约D时交易正常执行,超过调用步长是则执行失败。

5.1.4 异常处理

5.1.4.1 主动抛出异常

星火链Javascript合约禁用了try catch关键字, 但是可以调用throw来抛出异常, 当执行遇到throw异常时, 该交易判定为失败, 入链扣费但是交易不生效。

5.1.4.2 语法异常

当合约运行中出现未捕获的JavaScript异常时,处理规定:

  • 本次合约执行失败,合约中做的所有交易都不会生效。

  • 触发本次合约的这笔交易为失败。错误代码为151

  • 执行交易失败:

    合约中可以执行多个交易,只要有一个交易失败,就会抛出异常,导致整个交易失败。

5.1.5 注意事项

5.1.5.1 数组处理

Chain对象方法中Chain.store(key,value)的功能是向DB中保存一条数据。其中key的最大长度是1024Byte,value的最大长度是256KB,超过最大长度会写失败、报错。

超过长度限制的情况常出现在合约中数组,场景如下:

//合约中定义了如下结构 
Json::value record{count:0,index:[]};
func addRecord(){
	record[index][index.size] = "new record";
	Chain.store("record",record.toFastString());
}

函数addRecord会增加index的记录并保存,当index的长度超过256KB时,Chain.store语句会报错。

建议的处理方式是用多个大小固定的数组来代替一个持续增长的大数组。

5.1.5.2 循环处理

虚拟机在执行合约的函数时,有步长限制,最大1024。每一次Chain或者utils的对象方法的调用步长加1,因此当一个函数中调用了Chain的对象方法超过1024次时,函数会因为超过步长限制而执行失败。

通常出现在循环调用中,场景如下:

func getRecord(){
	for(i=0; i < 5000; i++){
		Chain.load(i,value);
	}
}

函数getRecord()中通过一个循环来调用Chain.load,当循环次数大于1024时函数报错。

建议的处理是,如果循环的长度会超过步长限制,需要修改函数的逻辑。

5.2 Javascript合约工具

5.2.1 检测工具

为了方便开发者更规范的、安全的开发合约,在做合约语法检测时候, 星火链提供了针对JavaScript智能合约的定制化的校验工具JSLint 做检查。编辑合约时候,首先需要在 JSLint 里检测通过,才可以被星火链系统检测为一个合法的合约。

合约校验工具:jslint.zip

5.2.2 合约文本压缩

星火链上部署智能合约消耗的交易费和合约的大小有关,因此可以通过压缩合约来减少部署合约的交易消耗。我们提供了定制化的合约压缩工具JSMin 。注意压缩之前保存原合约文档,压缩是不可逆的操作。

合约压缩工具:jsmin.zip

  • 文件解压后可看到 jsmin.bat文件。

  • 文本编解jsmin.bat,设置待压缩文件名及压缩后文件名,示例中为private.js。

jsmin.exe <.\private.js >.\private.min.js
  • 点击jsmin.bat执行bat文件,即可在配置目录下看到生成好的private.min.js。