语法规范

形式规范

\[\begin{split}\begin{align*} program &::= [importDirective]^*\ [contract]^+\\ importDirective &::= \mathrm{import}\ "\mathrm{ID}";\\ contract &::= \mathrm{contract}\ \mathrm{ID}\ \{\ [var]^*\ [constructor]\ [function]^+\ \}\\ var &::= formal;\\ formal &::= \mathrm{TYPE}\ \mathrm{ID}\\ constructor &::= \mathrm{constructor}([formal[,\ formal]^*])\ \{\ [stmt]^*\ \}\\ function &::= \mathrm{[public|static]}\ \mathrm{function}\ \mathrm{ID}([formal[,\ formal]^*])\ \mathrm{[returns}\ (\mathrm{TYPE]})\ \{\ [stmt]^*\ \mathrm{[return}\ expr;]\ \}\\ stmt &::= \mathrm{TYPE}\ \mathrm{ID} = expr;\\ &\ \ \ |\ \ \mathrm{ID}\ \mathrm{ID} = \mathrm{new}\ \mathrm{ID}(expr^*);\\ &\ \ \ |\ \ \mathrm{ID} = expr;\\ &\ \ \ |\ \ \mathrm{require}(expr);\\ &\ \ \ |\ \ \mathrm{exit}(expr);\\ &\ \ \ |\ \ \mathrm{if}\ (expr)\ stmt\ [\mathrm{else}\ stmt]\\ &\ \ \ |\ \ \mathrm{loop}\ (intConst)\ stmt\\ &\ \ \ |\ \ \{\ [stmt]^*\ \}\\ &\ \ \ |\ \ \mathrm{CODESEPARATOR}\\ expr &::= \mathsf{UnaryOp}\ expr\\ &\ \ \ |\ \ expr\ \mathsf{BinaryOp}\ expr\\ &\ \ \ |\ \ \mathrm{ID}(expr[,\ expr]^*)\\ &\ \ \ |\ \ \mathrm{ID}.\mathrm{ID}\\ &\ \ \ |\ \ \mathrm{ID}.\mathrm{ID}(expr[,\ expr]^*)\\ &\ \ \ |\ \ \mathrm{ID}\mathbf{[}expr:expr\mathbf{]}\\ &\ \ \ |\ \ (expr)\\ &\ \ \ |\ \ \mathrm{ID}\\ &\ \ \ |\ \ boolConst \\ &\ \ \ |\ \ intConst \\ &\ \ \ |\ \ bytesConst \\ \end{align*}\end{split}\]

大部分语法含义都是显而易见的。sCrypt特有的语法会在后面介绍。

行注释以 // 开头,块注释位于 /**/ 之间。

类型

基本类型

  • bool - 布尔值 truefalse

  • int - 任意长度的有符号整数,字面量(literals)有十进制和十六进制两种格式。

    int a1 = 42;
    int a2 = -4242424242424242;
    int a3 = 55066263022277343669578718895168534326250603453777594175500187360389116729240;
    int a4 = 0xFF8C;
    
  • bytes - 一个可变长度的字节数组,其字面量是带引号的十六进制格式,前缀为 b,或双引号 UTF8 字符串。

    bytes b1 = b'ffee1234';
    bytes b2 = b'414136d08c5ed2bf3ba048afe6dcaebafeffffffffffffffffffffffffffffff00';
    bytes b3 = b'1122' + b'eeff'; // b3 is b'1122eeff'
    bytes str = "hello world"; // utf8 string
    

数组类型

数组是长度固定的,具有相同基本类型的值列表。

  • 数组常量 - 以逗号分隔的表达式列表,括在方括号中。数组大小必须是大于零的整数常量。

    bool[3] b = [false, false && true || false, true || (1 > 2)];
    int[3] c = [72, -4 - 1 - 40, 833 * (99 + 9901) + 8888];
    bytes[3] a = [b'ffee', b'11', b'22'];
    int[2][3] d = [[11, 12, 13], [21, 22, 23]];
    // array demension can be omitted when declared
    int[] e = [1, 4, 2];  // e is of type int[3]
    int[][] f = [[11, 12, 13], [21, 22, 23]]; // f is of type int[2][3]
    
  • 将数组初始化/设置为相同值 - 函数 T[size] repeat(T e, static const int size) 返回一个数组,其中所有 size 元素都设置为 e ,其中 T 可以是任何类型。注意 size 必须是 编译时常量

    // a == [0, 0, 0]
    int[3] a = repeat(0, 3);
    // arr2D == [[0, 0, 0], [0, 0, 0]]
    int[2][3] arr2D = repeat(a, 2);
    int[4] flags = [false, true, false, true]
    // set all flags to be false
    flags = repeat(false, 4);
    
  • 索引运算符 - 索引从 0 开始。越界访问立即使合约执行失败。

    int[3] a = [1, 4, 2];
    int[2][3] arr2D = [[11, 12, 13], [21, 22, 23]];
    int d = a[2];
    a[1] = -4;
    int idx = 2;
    // read
    d = a[idx];
    d = arr2D[idx][1];
    // write
    a[idx] = 2;
    // assign to an array variable
    a = arr2D[1];
    // b is a new copy and the same as a
    int[3] b = a;
    // two arrays are equal if and only if they are of the same size and all elements are equal
    require(a == b);
    

结构体

结构体是单个名称下的变量的集合。变量可以是不同的基本类型、数组或结构体

  • 定义结构体

    struct Point {
      int x;
      int y;
    }
    
    struct Line {
      // nested struct
      Point start;
      Point end;
    }
    
  • 使用结构体
    Point p = {10, -10};
    int x = p.x;
    p.y = 20;
    // Define a variable q of type Point, and set members to the same values as those of p
    Point q = p;
    require(p == q); // true
    // nested
    Line l = {p, q};
    l.start.x = l.end.y + 1;
    

类型推断

auto 关键字表示变量的类型由变量的初始值自动推导出来。

auto a1 = b'36';      // bytes a1 = b'36';
auto a2 = 1 + 5 * 3;  // int a2 = 1 + 5 * 3;

类型别名

类型别名为类型创建一个新名称。它实际上并没有创建一个新类型,它只是创建一个新名称来引用该类型。请注意,声明的右侧不能是未指定的泛型结构类型。

type Age = int;
type Coordinate = int[2];
type A = ST<int>; // this is fine.
type B = ST; // this is not allowd.

泛型/泛型类型

泛型类型是参数化类型。它允许库/结构体处理多种类型而不是单一类型。用户可以创建这些库/结构体并使用他们自己的具体类型。

  • 声明泛型类型

泛型类型可以在库级别声明并在库的范围内使用,也可以在结构级别声明并在内部使用。

// declare a library with two generic type variables: K & V
library HashedMap<K, V> {

  // use them as function parameters' type
  function set(K k, V v, int idx) {
    ...
  }

}

// declare a struct with two generic type variables: T & P
struct ST<T, P> {
  T x;
  P y;
}
  • 实例化泛型类型

    // initialize a library with generics
    HashedMap<bytes, int> map = new HashedMap();
    
    // initialize a struct with generics
    ST<int, bytes> st = {1, b'02'};
    

子类型

有几个特定于比特币上下文的子类型,用于进一步提高类型安全性。

bytes 的子类型

要把 bytes 类型强制转换成某个子类型,必须显式调用与该子类型同名的函数。

  • PubKey - 一种公钥类型。

    PubKey pubKey = PubKey(b'0200112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100');
    
  • Sig - DER 格式的签名类型。包含 签名哈希类型,如下例子中的签名哈希类型是 SIGHASH_ALL | SIGHASH_FORKID (0x41) 。

    Sig sig = Sig(b'3045022100b71be3f1dc001e0a1ad65ed84e7a5a0bfe48325f2146ca1d677cf15e96e8b80302206d74605e8234eae3d4980fcd7b2fdc1c5b9374f0ce71dea38707fccdbd28cf7e41');
    
  • Ripemd160 - RIPEMD-160 哈希类型。

    Ripemd160 r = Ripemd160(b'0011223344556677889999887766554433221100');
    
  • PubKeyHash - Ripemd160 类型的别名。通常代表一个比特币地址。

    PubKeyHash aliceAddress = PubKeyHash(b'0011223344556677889999887766554433221100');
    
  • Sha1 - SHA-1 哈希类型。

    Sha1 s = Sha1(b'0011223344556677889999887766554433221100');
    
  • Sha256 - SHA-256 哈希类型。

    Sha256 s = Sha256(b'00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100');
    
  • SigHashType - 签名哈希类型。

    SigHashType s = SigHashType(b'01');
    SigHashType s = SigHash.ALL | SigHash.ANYONECANPAY;
    
  • SigHashPreimage - sighash 原像类型。

    SigHashPreimage s = SigHashPreimage(b'0100000028bcef7e73248aa273db19d73');
    
  • OpCodeType - 操作码类型。

    OpCodeType s = OpCode.OP_DUP + OpCode.OP_ADD;
    

int 的子类型

  • PrivKey - 私钥类型。

    PrivKey privKey = PrivKey(0x00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100);
    

const 变量

声明为 const 的变量一旦初始化就不能更改。

contract Test {
    const int x;

    constructor(int x) {
        this.x = x; // good, since this is initialization
    }

    public function equal(const int y) {
        y = 1; // <-- error

        const int a = 36;
        a = 11; // <-- error

        require(y == this.x);
    }
}

if 语句

if 条件可以是 intbytes 类型,除了 bool 。它们像在 C 和 Javascript 中一样被隐式转换为 boolint 表达式被评估为 false 当且仅当它为 0 (包括负数 0)。 bytes 表达式被评估为 false 当且仅当它的每个字节都是 b'00' (包括空的 bytes b'')。

int cond = 25; // true
int cond = 0;  // false
int cond = unpack(b'80') // false since it is negative 0
int cond = unpack(b'000080') // false since it is negative 0
if (cond) {} // equivalent to if (cond != 0) {}

bytes cond = b'00'; // false
bytes cond = b''; // false
bytes cond = b'80'; // true. Note b'80' is treated as false if converted to int
bytes cond = b'10' & b'73'; // true since it evaluates to b'10'
if (cond) {}

exit() 语句

exit(bool status); 语句终止合约执行。如果 statustrue ,则合约执行成功;否则合约执行失败。

contract TestPositiveEqual {
    int x;

    constructor(int x) {
        this.x = x;
    }

    public function equal(int y) {
        if (y <= 0) {
          exit(true);
        }
        require(y == this.x);
    }
}

Code Separator 代码分隔符

Three or more * in a line inserts an OP_CODESEPARATOR. It is used to exclude what comes before it (including itself), from being part of the signature. Note there is no ; at the end.

contract P2PKH_OCS {
    Ripemd160 pubKeyHash;

    public function unlock(Sig sig, PubKey pubKey) {
        // code separator 1
        ***
        require(hash160(pubKey) == this.pubKeyHash);
        // code separator 2
        *****
        require(checkSig(sig, pubKey));
    }
}

访问修饰符

可以使用三种类型的访问修饰符来帮助限制合约的属性和函数的范围:

  • 默认:不需要关键字
  • 私有的
  • 公共:仅适用于函数

比特币交易只能从外部调用公共函数。

  default private public
合约内 Yes Yes Yes
其他合约 Yes No Yes
外部 No No Yes

运算符

优先级 运算符 关联性 注意
1 () ++ -- .  
2 []  
3 ++ -- - ! ~  
4 * / %  
5 + -  
6 << >> 要移位的位数必须为非负数,否则立即失败
7 < <= > >=  
8 == !=  
9 & 在两个长度不同的整数的情况下,较短的首先使用 num2bin 扩展为与较长的相同的长度
10 ^ & 相同
11 | & 相同
12 &&  
13 ||  
14 ? :  
15 += -= *= /= %= &= |= ^= <<= >>=  

注解

  • 运算符 &&||? : 使用 短路评估.
  • 在对整数执行按位运算后,例如运算符 &|^~,编译器使用 OP_BIN2NUM 压缩运算结果。
  • 不管是正数还是负数,都是以 原码 格式存储的,区别于计算机使用的 补码 格式。 如果参与运算的操作数都是正数,则运算结果与补码的按位运算符一致。 (除了 ~)。 否则,运行结果可能会不一致。

作用域

sCrypt的作用域遵循C99和Solidity的现行作用域规则。外部作用域的变量会被内部作用域的同名变量覆盖。