iscl-doc

isCL新型智能合约语言用户手册

概述

背景

智能合约随着区块链技术的应用在实际生活中起到了越来越重要的作用,然而由智能合约的漏洞所带来的损失数以亿计。以太坊Solidity语言作为第一个大规模用于区块链应用开发的专用智能合约语言,存在重入、哈希依赖、异常处理错序、危险库调用、溢出等多种问题。其中,美链BEC代币由于整数溢出导致64亿人民币蒸发,DAO攻击利用重入导致函数执行顺序改变盗走1亿5千万美元,Gas(费用)机制的引入导致攻击者可以滥用交易所Gas。根据安全公司Hosho报告,25%的以太坊智能合约存在漏洞,其导致半年损失27亿美元;60%的以太坊智能合约出现设计缺陷;全球有超过30万人或组织在攻击区块链。

过于灵活的语法、合约实现不规范、合约性质不易验证、参与者无法理解合约逻辑、智能合约编译器和虚拟机存在漏洞等都直接或间接地影响了智能合约的落地应用,安全性带来的劣势掩盖了灵活性带来的优势,限制了它的发展。因此,中科院软件所RepChain区块链项目组面向区块链常见应用场景,主要满足RepChain的实际应用需求,其次尽可能满足以太坊常见智能合约的开发需求,研究一套领域专用的智能合约编程语言isCL(Institute of Software Contract Language)。通过移除不必要的语法从根源上避免部分智能合约的漏洞,同时封装开发智能合约时所需要的常用操作,使得智能合约更加安全、简洁和可读。

语言特点

isCL智能合约语言定位为一种安全、简洁的非图灵完备的领域专用语言,面向智能合约相关业务的开发,该智能合约语言的特点如下:

严格语义

对智能合约语言核心翻译步骤中的AST(Abstract Syntax Tree,抽象语法树)都赋予其明确的语义,既可以明确语法的执行效果,又可以采用Coq证明翻译过程的语义保持性,防止“误编译”的产生。

静态检查

isCL智能合约语言依赖严格的类型系统和多种检查项的前端,在编译期就能检查出绝大部分错误。除常见的前端检查外,i2c编译器前端具备以下特点:

环境搭建

Ubuntu 22.04

apt update  //更新apt软件包清单信息,并以此更新本地软件仓库
apt install -y vim git make opam //安装vim、opam

opam -y init //opam初始化
test -r /root/.opam/opam-init/init.sh && . /root/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true //测试opam 命令
opam switch create 4.11.2 //选择 4.11.2版本
opam install -y coq=8.12.0 ocamlbuild //安装8.12.0版本的coq

opam repo add coq-released https://coq.inria.fr/opam/released //添加opam源
opam update //更新opam
opam install -y coq-compcert //安装coq-compcert

cd ~ //返回用户主目录
echo "eval `opam env`" >> .bashrc //写入启动设置文件
source .bashrc  //环境配置生效

git clone https://gitee.com/BTAJL/i2c //获取项目
cd i2c/ //进入项目目录
make //编译以得到i2c编译器
./i2c examples/hello.iscl  //使用i2c编译器编译合约文件,生成contract.c文件

注 : test.iscl 是用户自定义合约文件,执行 ./i2c test.iscl 语句可生成.c文件contract.c。

运行界面:

img_2.png

MacOS Ventura 13.3

brew update //更新brew软件包清单信息,并以此更新本地软件仓库
brew install vim git make opam //安装vim、opam

opam init //opam初始化
test -r /root/.opam/opam-init/init.sh && . /root/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true //测试opam 命令
opam switch create 4.11.2 //选择 4.11.2版本
opam install coq=8.12.0 ocamlbuild //安装8.12.0版本的coq

opam repo add coq-released https://coq.inria.fr/opam/released //添加opam源
opam update  //更新opam
opam install coq-compcert //安装coq-compcert

cd ~ //返回用户主目录
echo "eval `opam env`" >> .bashrc  //写入启动设置文件
source .bashrc  //环境配置生效

git clone https://gitee.com/BTAJL/i2c //获取项目
cd i2c/ //进入项目目录
make //编译以得到i2c编译器
./i2c examples/hello.iscl  //使用i2c编译器编译合约文件,生成contract.c文件

运行界面: img_3.png 注意:以上界面截图与实际情况可能有所出入

配置环境视频: https://iscas1-my.sharepoint.cn/:v:/g/personal/zhengls_iscas1partner_onmschina cn/ETm6h0ThpQRIhIvF1NmZ_oIBnKyTzH2DXFNCnKq9XUEwVw?e=MoGoYh

注意:以上演示视频与实际情况可能有所出入。

部署

目前支持将isCL语言编写的智能合约编译部署到RepChain区块链中,具体方法请参考开源工具i2w(https://gitee.com/BTAJL/i2w.git)。

语法

标识符

标识符指用户定义的常量名、变量名、函数名等,用户定义的标识符应符合以下要求:

类型

isCL语言支持布尔、整数、浮点数、字符串、选项、列表、映射、结构体共8种类型。

布尔类型

说明 Bool    
取值范围 true, false    
默认值 false    
一元操作符 !    
二元操作符 &&, ==, !=, | |     
取值操作符    
内置函数    
备注    

实例:

1  b1: Bool = true;
2  b2: Bool;
3	
4  b2 = !b1;
5  b2 = b1 && b2;
6  b2 = b1 || b2;
7  b2 = b1 == b2;
8  b2 = b1 != b2;

整数类型

本合约语言不支持null,因此在定义变量没有但没有初始化时,编译器会赋予该变量默认值。

说明 Int
取值范围 -2^31 ~ 2^31 - 1
默认值 0
一元操作符 -
二元操作符 +, -, *, /, %, ==, !=, <, >, <=, >=
取值操作符
内置函数
备注 除0,模0将触发异常

实例:

1  i1: Int = 2;
2  i2: Int = 1;
3  i3: Int;
4  b1: Bool;
5	
6  i3 = -i1;
7  i3 = i1 + i2;
8  i3 = i1  i2;
9  i3 = i1 * i2;
10 i3 = i1 / i2;
11 i3 = i1 % i2;
12 b1 = i1 == i2;
13 b1 = i1 != i2;
14 b1 = i1 < i2;
15 b1 = i1 > i2;
16 b1 = i1 <= i2;
17 b1 = i1 >= i2;

浮点数类型

说明 Double
取值范围 -1.79E308 ~ 1.79E308
默认值 0.0
一元操作符 -
二元操作符 +, -, *, /, ==, !=, <, >, <=, >=
取值操作符
内置函数
备注 除0.0将触发异常

实例:

1  d1: Double = 4.0;
2  d2: Double = 2.0;
3  d3: Double;
4  b1: Bool;
5	
6  d3 = -d1;
7  d3 = d1 + d2;
8  d3 = d1 - d2;
9  d3 = d1 * d2;
10 d3 = d1 / d2;
11 b1 = d1 == d2;
12 b1 = d1 != d2;
13 b1 = d1 < d2;
14 b1 = d1 > d2;
15 b1 = d1 <= d2;
16 b1 = d1 >= d2;

字符串类型

说明 String
取值范围
默认值 空字符串
一元操作符
二元操作符 + // 字符串拼接
==, != // 字符串比较
取值操作符 [ : ] // 字符串截取
内置函数 len() // 字符串长度
备注 字符串截取越界将触发异常

实例:

1  str1: String = abc;
2  str2: String = def;
3  str3: String;
4  i1: Int;
5  b1: Bool;
6	
7  b1 = str1 == str2;
8  b1 = str1 != str2;
9  str3 = str1 + str2;
10 str3 = str1[0: 1];
11 i1 = str1.len();

选项类型

说明 Option
取值范围
默认值 none
一元操作符
二元操作符
取值操作符
内置函数 get(), isEmpty()
备注 值为none时调用get()将触发异常

实例:

1  o1: Option[Int] = some(1);
2  o2: Option[Int] = none;
3  i1: Int;
4  b1: Bool;
5	
6  i1 = o1.get();
7  b1 = o2.isEmpty();

列表类型

说明 List
取值范围
默认值 空列表
一元操作符
二元操作符 + // 列表拼接
取值操作符 [ ] // 列表赋值、取值
内置函数 len() // 列表长度
备注 索引值类型为Int;
列表赋值、取值越界将触发异常

实例 :

1  l1: List[Int] = List[Int](1, 2, 3);
2  l2: List[Int] = List[Int](4, 5, 6);
3  l3: List[Int];
4  i1: Int;
5	
6  l3 = l1 + l2;
7  i3 = l1.len();
8  i1 = l1[0];
9  l1[2] = 4;

映射类型

说明 Map
取值范围
默认值 空映射
一元操作符
二元操作符 + // 映射拼接
取值操作符 [ ] // 映射赋值、取值
内置函数 len() // 映射长度
备注 索引值类型为Int或String;
映射赋值时类型为Value的类型;
映射取值时类型为Value类型对应的Option类型

实例:

1  m1: Map[Int, String] = Map[Int, String](1: a, 2: b);
2  m2: Map[Int, String] = Map[Int, String](3: c, 4: d);
3  m3: Map[Int, String];
4  s1: String;
5  o1: Option[String];
6	
7  m3 = m1 + m2;
8  i1 = m1.len();
9  o1 = m1[1];
10 m2[5] = e;

结构体类型

说明 Struct
取值范围
默认值 成员变量按各自类型所对应的默认值初始化
一元操作符
二元操作符
取值操作符 . // 结构体赋值、取值
内置函数
备注
1  // struct StructType1(i: Int, str: String);
2  s1: StructType1 = StructType1(1, ok);
3  i1: Int;
4  str1: String = fail;
5
6  i1 = s1.i;
7  s1.str = str1;

程序

isCL语言程序由结构体块、上下文变量块、状态变量块、全局常量块以及函数块五个模块构成。此外,模块应按上述顺序依次定义,不可改变顺序、不可穿插使用。

结构体块

实例:

1  struct StructType1(i: Int, str: String);
2  struct StructType2(j: Int, s: StructType1)

上下文变量块

实例:

1  const B: Int = true;
2  const L: List[Int] = List[Int](1, 2, 3);

本地变量定义语句

实例:

1  return;
2  return false;

require语句

实例:

1  // comment 1
2  /* comment 2 */

异常

合约实例

存证

1  storage proof: Map[String, String];
2  func putProof(key: String, value: String) = {
3  proofOption: Option[String] = proof[key];
4  require(proofOption.isEmpty(), "Already existed value for key: " + key);
5  proof[key] = value;
6  }

转帐

1  context sender: String;
2
3  storage ledger: Map[String, Int];
4  storage owner: String;
5	
6  func init(balance: Int) = {
7  require(owner == "", "Error: the init function already be called");
8  ledger[sender] = balance;
9  owner = sender;
10 }
11	
12  func mint(amount: Int) = {
13    newBalance: Int;
14    balanceOption: Option[Int];
15    require(owner == sender, "Error: the mint function must be called by the owner");
16    balanceOption = ledger[sender];
17    newBalance = balanceOption.get() + amount;
18    ledger[sender] = newBalance;
19  }
20	
21  func transfer(recipient: String, amount: Int) = {
22    balanceOfSpender: Int;
23    newBalanceOfSpender: Int;
24    balanceOfSpenderOption: Option[Int] = ledger[sender];
25	
26    balanceOfRecipient: Int;
27    newBalanceOfRecipient: Int;
28    balanceOfRecipientOption: Option[Int];
29	
30    require(!balanceOfSpenderOption.isEmpty(), "Error: the account " + sender + " has no balance.");
31    balanceOfSpender = balanceOfSpenderOption.get();
32    require(balanceOfSpender >= amount, "Error: the account " + sender + " has no enough balance.");
33	
34    balanceOfRecipientOption = ledger[recipient];
35    if (!balanceOfRecipientOption.isEmpty()) {
36    balanceOfRecipient = balanceOfRecipientOption.get();
37    }
38	
39    newBalanceOfSpender = balanceOfSpender - amount;
40    newBalanceOfRecipient = balanceOfRecipient + amount;
41	
42    ledger[sender] = newBalanceOfSpender;
43    ledger[recipient] = newBalanceOfRecipient;
44  }