我们先来编写一份符合以下规范的经典HelloWorld.scilla
合约:
- 有一个由合约的创建者初始化的不变变量
owner
。该变量初始化后,其值就不能改变。owner
的类型是Address
。 - 有一个初始化为
""
的可变变量welcome_msg
,该变量为String
类型。这里的可变性是指即使在部署合约之后也可以修改变量的值。 - 只有
owner
才能通过接口setHello
修改welcome_msg
。该接口需要msg
(String
类型)作为输入方,并允许owner
设置发送给msg
的welcome_msg
的值。 - 有一个
getHello
接口可获取到welcome_msg
。getHello
不接收任何内容输入。
使用contract
声明合约,让合约正式生效。contract
后面跟着合约名称,如例子中的HelloWorld
。因此,以下代码声明了一个HelloWorld
合约。
contract HelloWorld
注意
目前,Scilla合约只能包含单个合约声明,因此
contract
后面的任何字符都是合约声明的一部分。换句话说,没有明确的关键字来声明合约定义的结束点。
合约声明后,接着要声明不可变变量,由()
来定义。
每个不可变变量以下列方式声明:vname: vtype
,其中vname
是变量名称,vtype
是变量类型。不可变变量用,
分隔。根据规范,合约将只有一个不可变的类型变量owner
,类型为Address
。参阅如下代码。
(owner: Address)
合约中的可变变量通过field
声明。
每个可变变量都按以下方式声明:
field vname : vtype = init_val
vname
是变量名称,vtype
是它的类型,init_val
是变量的初始值。
HelloWorld
合约带有一个可变参数welcome_msg
(String
类型),初始化为""
。
代码如下:
field welcome_msg : String = ""
至此,HelloWorld.scilla
合约形式如下,其中包含合约名称及其可变/不可变变量:
contract HelloWorld(owner: Address)
field welcome_msg : String = ""
注意
除了这些字段之外,Scilla中的合约都有一个隐式声明的可变字段
_balance
(在合约创建时初始化),这代表了合约范围内的资金量。该字段可在合约过程中免费读取,但只能在显式转账时才能修改。
定义接口(即Transitions)
类似setHello
的接口在Scilla中称为transitions。Transitions类似于其他语言中的函数或方法。
transition使用transition
来声明。transition用end
表示结束。transition
后面为transition名称,如例子中的setHello
。名称后面是用()
括起来的参数。每个参数用,
分隔,声明格式如下:vname : vtype
。按照规范,setHello
只需要一个string
类型的msg
参数。代码如下:
transition setHello (msg : String)
然后是transition的主体。例如,在transitionsetHello (msg : String)
中设定welcome_msg
,代码如下:
transition setHello (msg : String)
is_owner = builtin eq owner _sender;
match is_owner with | False => msg = {_tag : "Main"; _recipient : _sender; _amount : Uint128 0; code : not_owner_code};
msgs = one_msg msg;
send msgs
| True => welcome_msg := msg;
msg = {_tag : "Main"; _recipient : _sender; _amount : Uint128 0; code : set_hello_code};
msgs = one_msg msg;
send msgs
end
end
首先看代码第二行,用builtin eq owner _sender
来判断owner
是否正确。builtin为
内置命令。
为了比较两个账户地址,我们使用了builtin
方法的eq
。这返回布尔值True
或False
。
_
为前缀。例如,
_sender
指的是当前合约调用的帐户地址。
根据比较结果,transition根据匹配结果进入不同的分支路径,语法如下:
match expr with| x => expr_1
| y => expr_2
end
上面代码检查expr
是符合x
还是y
。如果expr
符合x
,那么下一步将是expr_1
,如果expr
符合y
,那么下一步将是expr_2
。简单地说,上面的代码实现了一条 if-then-else
指令。
如果调用者不是owner
,则transition会走 False
分支,此时合约会输出一条消息。Scilla为输出消息定义了一种特殊类型 Message
。输出消息包含需要调用的其他合约信息(作为当前调用的一部分)或需要返回的值。
在这种情况下,输出消息是msg
中的一个错误代码not_owner_code
。具体来说,这种情况下的输出消息是:
msg = {_tag : "Main"; _recipient : _sender; _amount : Uint128 0; code : not_owner_code};
输出消息参数格式为vname : value
组成,用;
分隔,其范围在{}
之内。每个输出消息必须有三个必填字段:_tag
、 _recipient
、_amount
,字段顺序无要求。_recipient
是消息发送地址。_tag
是_recipient
里要调用的transition的名称,_amount
是要转移给_recipient
的ZIL数量。
除了这些必填字段,消息可能还有其他字段。在这个示例中,消息使用code
字段来报告错误消息。
send
指令能承载一系列 Message
类型的内容,因此会用它来发送消息。在当前示例中,项内容仅有一个消息。总而言之,以下代码将创建并发送一条消息。
msgs = one_msg msg;
send msgs
one_msg
是一个实用函数,它允许创建消息列表并插入至msg
列表中。
如果调用者是owner
,则合约允许调用者设定可变变量welcome_msg
的值,并作为msg
的输入参数。代码如下:
welcome_msg := msg;
注意
写入可变参数是通过:=
完成的。
在上述例子中,合约使用set_hello_code
向调用者发送了一条消息。
Scilla合约可能带有一些辅助库,包含一些合约的纯功能(没有状态管理)组件。库会在合约的最前面使用library
加上库名称来声明。库声明如下例所示:
library HelloWorld
在这个示例中,库将使用标准用法let x = y in expr
来定义错误代码。
let not_owner_code = Int32 1let set_hello_code = Int32 2
该库还可以包括实用函数。例如,one_msg
函数创建了一个包含Message
类型项的列表 ,如下所示:
let one_msg =
fun (msg : Message) =>
let nil_msg = Nil {Message} in Cons {Message} msg nil_msg
在此阶段,合约代码如下:
library HelloWorld
let one_msg =
fun (msg : Message) =>
let nil_msg = Nil {Message} in Cons {Message} msg nil_msg
let not_owner_code = Int32 1 let set_hello_code = Int32 2
contract HelloWorld
(owner: Address)
field welcome_msg : String = ""
transition setHello (msg : String)
is_owner = builtin eq owner _sender;
match is_owner with | False => msg = {_tag : "Main"; _recipient : _sender; _amount : Uint128 0; code : not_owner_code};
msgs = one_msg msg;
send msgs
| True => welcome_msg := msg;
msg = {_tag : "Main"; _recipient : _sender; _amount : Uint128 0; code : set_hello_code};
msgs = one_msg msg;
send msgs
end
end
最后
现在可以添加第二个transitiongetHello()
,让所有调用者都能收到welcome_msg
。声明方法类似setHello (msg : String)
,而getHello()
不用带任何参数。
transition getHello ()
r welcome_msg := msg;
msg = {_tag : "Main"; _recipient : _sender; _amount : 0; code : set_hello_code};
msgs = one_msg msg;
send msgs
end
end
transition getHello ()
r
fun (c : Bool) =>
match b with | False => False
| True => match c with | False => False
| True => True
end
end
let orb =
fun (b : Bool) => fun (c : Bool) =>
match b with | True => True
| False => match c with | False => False
| True => True
end
end
let negb = fun (b : Bool) =>
match b with | True => False
| False => True
end
let one_msg =
fun (msg : Message) =>
let nil_msg = Nil {Message} in Cons {Message} msg nil_msg
let check_update =
fun (bs : Map Address Int) =>
fun (_sender : Address) =>
fun (_amount : Int) =>
let c = builtin contains bs _sender in match c with | False => let bs1 = builtin put bs _sender _amount in Some {Map Address Int} bs1
| True => None {Map Address Int}
end
let blk_leq =
fun (blk1 : BNum) =>
fun (blk2 : BNum) =>
let bc1 = builtin blt blk1 blk2 in let bc2 = builtin eq blk1 blk2 in orb bc1 bc2
let accepted_code = 1let missed_deadline_code = 2let already_backed_code = 3let not_owner_code = 4let too_early_code = 5let got_funds_code = 6let cannot_get_funds = 7let cannot_reclaim_code = 8let reclaimed_code = 9
(***************************************************)
(* The contract definition *)
(***************************************************)
contract Crowdfunding
(* Parameters *)
(owner : Address,
max_block : BNum,
goal : Int)
(* Mutable fields *)
field backers : Map Address Int = Emp Address Int
field funded : Bool = False
transition Donate ()
blk bs msg = {_tag : Main; _recipient : _sender; _amount : 0;
code : already_backed_code};
msgs = one_msg msg;
send msgs
| Some bs1 =>
backers := bs1;
accept;
msg = {_tag : Main; _recipient : _sender; _amount : 0;
code : accepted_code};
msgs = one_msg msg;
send msgs
end
| False => msg = {_tag : Main; _recipient : _sender; _amount : 0;
code : missed_dealine_code};
msgs = one_msg msg;
send msgs
end
end
transition GetFunds ()
is_owner = builtin eq owner _sender;
match is_owner with | False => msg = {_tag : Main; _recipient : _sender; _amount : 0;
code : not_owner_code};
msgs = one_msg msg;
send msgs
| True => blk tt = True;
funded := tt;
msg = {_tag : Main; _recipient : owner; _amount : bal;
code : got_funds_code};
msgs = one_msg msg;
send msgs
end
end
end
(* transition ClaimBack *)
transition ClaimBack ()
blk msg = {_tag : Main; _recipient : _sender; _amount : 0;
code : too_early_code};
msgs = one_msg msg;
send msgs
| True => bs msg = {_tag : Main; _recipient : _sender; _amount : 0;
code : cannot_reclaim_code};
msgs = one_msg msg;
send msgs
| Some v =>
bs1 = builtin remove bs _sender;
backers := bs1;
msg = {_tag : Main; _recipient : _sender; _amount : v;
code : reclaimed_code};
msgs = one_msg msg;
send msgs
end
end
end
end
https://zhuanlan.zhihu.com/p/40044768