Michelson: SELF instruction implemented

This commit is contained in:
Milo Davis 2018-01-11 11:15:35 -05:00 committed by Benjamin Canou
parent d69717dd75
commit 0c9592bca7
12 changed files with 69 additions and 9 deletions

View File

@ -68,6 +68,7 @@ let collect_error_locations errs =
| Bad_stack (loc, _, _, _) | Bad_stack (loc, _, _, _)
| Unmatched_branches (loc, _, _) | Unmatched_branches (loc, _, _)
| Transfer_in_lambda loc | Transfer_in_lambda loc
| Self_in_lambda loc
| Transfer_in_dip loc | Transfer_in_dip loc
| Invalid_constant (loc, _, _) | Invalid_constant (loc, _, _)
| Invalid_contract (loc, _) | Invalid_contract (loc, _)
@ -312,6 +313,10 @@ let report_errors ~details ~show_source ?parsed ppf errs =
Format.fprintf ppf Format.fprintf ppf
"%aThe TRANSFER_TOKENS instruction cannot appear within a DIP." "%aThe TRANSFER_TOKENS instruction cannot appear within a DIP."
print_loc loc print_loc loc
| Self_in_lambda loc ->
Format.fprintf ppf
"%aThe SELF instruction cannot appear in a lambda."
print_loc loc
| Bad_stack_length -> | Bad_stack_length ->
Format.fprintf ppf Format.fprintf ppf
"Bad stack length." "Bad stack length."

View File

@ -263,6 +263,7 @@ module Cost_of = struct
let steps_to_quota = step_cost 1 let steps_to_quota = step_cost 1
let get_steps_to_quota gas = Script_int.abs @@ Script_int.of_int gas.remaining let get_steps_to_quota gas = Script_int.abs @@ Script_int.of_int gas.remaining
let source = step_cost 3 let source = step_cost 3
let self = step_cost 3
let amount = step_cost 1 let amount = step_cost 1
let compare_bool _ _ = step_cost 1 let compare_bool _ _ = step_cost 1
let compare_string s1 s2 = let compare_string s1 s2 =

View File

@ -90,6 +90,7 @@ module Cost_of : sig
val get_steps_to_quota : t -> Script_int.n Script_int.num val get_steps_to_quota : t -> Script_int.n Script_int.num
val steps_to_quota : cost val steps_to_quota : cost
val source : cost val source : cost
val self : cost
val amount : cost val amount : cost
val wrap : cost val wrap : cost
val compare_bool : 'a -> 'b -> cost val compare_bool : 'a -> 'b -> cost

View File

@ -85,6 +85,7 @@ type prim =
| I_SIZE | I_SIZE
| I_SOME | I_SOME
| I_SOURCE | I_SOURCE
| I_SELF
| I_STEPS_TO_QUOTA | I_STEPS_TO_QUOTA
| I_SUB | I_SUB
| I_SWAP | I_SWAP
@ -204,6 +205,7 @@ let string_of_prim = function
| I_SIZE -> "SIZE" | I_SIZE -> "SIZE"
| I_SOME -> "SOME" | I_SOME -> "SOME"
| I_SOURCE -> "SOURCE" | I_SOURCE -> "SOURCE"
| I_SELF -> "SELF"
| I_STEPS_TO_QUOTA -> "STEPS_TO_QUOTA" | I_STEPS_TO_QUOTA -> "STEPS_TO_QUOTA"
| I_SUB -> "SUB" | I_SUB -> "SUB"
| I_SWAP -> "SWAP" | I_SWAP -> "SWAP"
@ -304,6 +306,7 @@ let prim_of_string = function
| "SIZE" -> ok I_SIZE | "SIZE" -> ok I_SIZE
| "SOME" -> ok I_SOME | "SOME" -> ok I_SOME
| "SOURCE" -> ok I_SOURCE | "SOURCE" -> ok I_SOURCE
| "SELF" -> ok I_SELF
| "STEPS_TO_QUOTA" -> ok I_STEPS_TO_QUOTA | "STEPS_TO_QUOTA" -> ok I_STEPS_TO_QUOTA
| "SUB" -> ok I_SUB | "SUB" -> ok I_SUB
| "SWAP" -> ok I_SWAP | "SWAP" -> ok I_SWAP
@ -448,6 +451,7 @@ let prim_encoding =
("SIZE", I_SIZE) ; ("SIZE", I_SIZE) ;
("SOME", I_SOME) ; ("SOME", I_SOME) ;
("SOURCE", I_SOURCE) ; ("SOURCE", I_SOURCE) ;
("SELF", I_SELF) ;
("STEPS_TO_QUOTA", I_STEPS_TO_QUOTA) ; ("STEPS_TO_QUOTA", I_STEPS_TO_QUOTA) ;
("SUB", I_SUB) ; ("SUB", I_SUB) ;
("SWAP", I_SWAP) ; ("SWAP", I_SWAP) ;

View File

@ -83,6 +83,7 @@ type prim =
| I_SIZE | I_SIZE
| I_SOME | I_SOME
| I_SOURCE | I_SOURCE
| I_SELF
| I_STEPS_TO_QUOTA | I_STEPS_TO_QUOTA
| I_SUB | I_SUB
| I_SWAP | I_SWAP

View File

@ -752,6 +752,10 @@ let rec interp
let gas = Gas.consume gas Gas.Cost_of.source in let gas = Gas.consume gas Gas.Cost_of.source in
Gas.check gas >>=? fun () -> Gas.check gas >>=? fun () ->
logged_return (Item ((ta, tb, orig), rest), gas, ctxt) logged_return (Item ((ta, tb, orig), rest), gas, ctxt)
| Self (ta, tb), rest ->
let gas = Gas.consume gas Gas.Cost_of.self in
Gas.check gas >>=? fun () ->
logged_return (Item ((ta, tb, source), rest), gas, ctxt)
| Amount, rest -> | Amount, rest ->
let gas = Gas.consume gas Gas.Cost_of.amount in let gas = Gas.consume gas Gas.Cost_of.amount in
Gas.check gas >>=? fun () -> Gas.check gas >>=? fun () ->

View File

@ -36,6 +36,7 @@ type error += Bad_stack : Script.location * prim * int * _ stack_ty -> error
type error += Unmatched_branches : Script.location * _ stack_ty * _ stack_ty -> error type error += Unmatched_branches : Script.location * _ stack_ty * _ stack_ty -> error
type error += Transfer_in_lambda of Script.location type error += Transfer_in_lambda of Script.location
type error += Transfer_in_dip of Script.location type error += Transfer_in_dip of Script.location
type error += Self_in_lambda of Script.location
type error += Bad_stack_length type error += Bad_stack_length
type error += Bad_stack_item of int type error += Bad_stack_item of int
type error += Inconsistent_annotations of string * string type error += Inconsistent_annotations of string * string
@ -67,12 +68,15 @@ type ex_stack_ty = Ex_stack_ty : 'a stack_ty -> ex_stack_ty
type tc_context = type tc_context =
| Lambda : tc_context | Lambda : tc_context
| Dip : 'a stack_ty -> tc_context | Dip : 'a stack_ty * tc_context -> tc_context
| Toplevel : { storage_type : 'a ty } -> tc_context | Toplevel : { storage_type : 'sto ty ;
param_type : 'param ty ;
ret_type : 'ret ty } -> tc_context
let add_dip ty annot = function let add_dip ty annot prev =
| Lambda | Toplevel _ -> Dip (Item_t (ty, Empty_t, annot)) match prev with
| Dip stack -> Dip (Item_t (ty, stack, annot)) | Lambda | Toplevel _ -> Dip (Item_t (ty, Empty_t, annot), prev)
| Dip (stack, _) -> Dip (Item_t (ty, stack, annot), prev)
let default_param_annot = Some "@parameter" let default_param_annot = Some "@parameter"
let default_storage_annot = Some "@storage" let default_storage_annot = Some "@storage"
@ -244,6 +248,7 @@ let number_of_generated_growing_types : type b a. (b, a) instr -> int = function
| H _ -> 0 | H _ -> 0
| Steps_to_quota -> 0 | Steps_to_quota -> 0
| Source _ -> 1 | Source _ -> 1
| Self _ -> 1
| Amount -> 0 | Amount -> 0
(* ---- Error helpers -------------------------------------------------------*) (* ---- Error helpers -------------------------------------------------------*)
@ -332,6 +337,7 @@ let namespace = function
| I_SIZE | I_SIZE
| I_SOME | I_SOME
| I_SOURCE | I_SOURCE
| I_SELF
| I_STEPS_TO_QUOTA | I_STEPS_TO_QUOTA
| I_SUB | I_SUB
| I_SWAP | I_SWAP
@ -1842,7 +1848,8 @@ and parse_instr
let ret_type_full = Pair_t ((ret_type, None), (storage_type, None)) in let ret_type_full = Pair_t ((ret_type, None), (storage_type, None)) in
trace trace
(Ill_typed_contract (cannonical_code, [])) (Ill_typed_contract (cannonical_code, []))
(parse_returning (Toplevel { storage_type }) ctxt ?type_logger (arg_type_full, None) ret_type_full code_field) >>=? (parse_returning (Toplevel { storage_type ; param_type = arg_type ; ret_type })
ctxt ?type_logger (arg_type_full, None) ret_type_full code_field) >>=?
fun (Lam ({ bef = Item_t (arg, Empty_t, _) ; fun (Lam ({ bef = Item_t (arg, Empty_t, _) ;
aft = Item_t (ret, Empty_t, _) ; _ }, _) as lambda) -> aft = Item_t (ret, Empty_t, _) ; _ }, _) as lambda) ->
Lwt.return @@ ty_eq arg arg_type_full >>=? fun (Eq _) -> Lwt.return @@ ty_eq arg arg_type_full >>=? fun (Eq _) ->
@ -1876,6 +1883,15 @@ and parse_instr
(Lwt.return (parse_ty ta)) >>=? fun (Ex_ty ta, _) -> (Lwt.return (parse_ty ta)) >>=? fun (Ex_ty ta, _) ->
(Lwt.return (parse_ty tb)) >>=? fun (Ex_ty tb, _) -> (Lwt.return (parse_ty tb)) >>=? fun (Ex_ty tb, _) ->
return (typed loc (Source (ta, tb), Item_t (Contract_t (ta, tb), stack, instr_annot))) return (typed loc (Source (ta, tb), Item_t (Contract_t (ta, tb), stack, instr_annot)))
| Prim (loc, I_SELF, [], instr_annot),
stack ->
let rec get_toplevel_type : tc_context -> bef judgement tzresult Lwt.t = function
| Lambda -> fail (Self_in_lambda loc)
| Dip (_, prev) -> get_toplevel_type prev
| Toplevel { param_type ; ret_type ; _ } ->
return (typed loc (Self (param_type, ret_type),
Item_t (Contract_t (param_type, ret_type), stack, instr_annot))) in
get_toplevel_type tc_context
(* Primitive parsing errors *) (* Primitive parsing errors *)
| Prim (loc, (I_DROP | I_DUP | I_SWAP | I_SOME | I_UNIT | Prim (loc, (I_DROP | I_DUP | I_SWAP | I_SOME | I_UNIT
| I_PAIR | I_CAR | I_CDR | I_CONS | I_PAIR | I_CAR | I_CDR | I_CONS
@ -1964,7 +1980,7 @@ and parse_instr
I_PUSH ; I_NONE ; I_LEFT ; I_RIGHT ; I_NIL ; I_PUSH ; I_NONE ; I_LEFT ; I_RIGHT ; I_NIL ;
I_EMPTY_SET ; I_DIP ; I_LOOP ; I_EMPTY_SET ; I_DIP ; I_LOOP ;
I_IF_NONE ; I_IF_LEFT ; I_IF_CONS ; I_IF_NONE ; I_IF_LEFT ; I_IF_CONS ;
I_EMPTY_MAP ; I_IF ; I_SOURCE ; I_LAMBDA ] I_EMPTY_MAP ; I_IF ; I_SOURCE ; I_SELF ; I_LAMBDA ]
and parse_contract and parse_contract
: type arg ret. context -> arg ty -> ret ty -> Script.location -> Contract.t -> : type arg ret. context -> arg ty -> ret ty -> Script.location -> Contract.t ->
@ -2062,7 +2078,8 @@ let parse_script
(parse_data ?type_logger ctxt storage_type (root storage)) >>=? fun storage -> (parse_data ?type_logger ctxt storage_type (root storage)) >>=? fun storage ->
trace trace
(Ill_typed_contract (code, [])) (Ill_typed_contract (code, []))
(parse_returning (Toplevel { storage_type }) ctxt ?type_logger (arg_type_full, None) ret_type_full code_field) (parse_returning (Toplevel { storage_type ; param_type = arg_type ; ret_type })
ctxt ?type_logger (arg_type_full, None) ret_type_full code_field)
>>=? fun code -> >>=? fun code ->
return (Ex_script { code; arg_type; ret_type; storage; storage_type }) return (Ex_script { code; arg_type; ret_type; storage; storage_type })
@ -2097,7 +2114,7 @@ let typecheck_code
let ret_type_full = Pair_t ((ret_type, None), (storage_type, None)) in let ret_type_full = Pair_t ((ret_type, None), (storage_type, None)) in
let result = let result =
parse_returning parse_returning
(Toplevel { storage_type }) (Toplevel { storage_type ; param_type = arg_type ; ret_type })
ctxt ctxt
~type_logger: (fun loc bef aft -> type_map := (loc, (bef, aft)) :: !type_map) ~type_logger: (fun loc bef aft -> type_map := (loc, (bef, aft)) :: !type_map)
(arg_type_full, None) ret_type_full code_field in (arg_type_full, None) ret_type_full code_field in
@ -2442,6 +2459,18 @@ let () =
| _ -> None) | _ -> None)
(fun (loc, ()) -> (fun (loc, ()) ->
Transfer_in_lambda loc) ; Transfer_in_lambda loc) ;
register_error_kind
`Permanent
~id:"selfInLambda"
~title: "SELF instruction in lambda (typechecking error)"
~description:
"A SELF instruction was encountered in a lambda expression."
(located empty)
(function
| Self_in_lambda loc -> Some (loc, ())
| _ -> None)
(fun (loc, ()) ->
Self_in_lambda loc) ;
register_error_kind register_error_kind
`Permanent `Permanent
~id:"inconsistentStackLengthsTypeError" ~id:"inconsistentStackLengthsTypeError"

View File

@ -45,6 +45,7 @@ type error += Unexpected_annotation of Script.location
type error += Transfer_in_lambda of Script.location type error += Transfer_in_lambda of Script.location
type error += Transfer_in_dip of Script.location type error += Transfer_in_dip of Script.location
type error += Self_in_lambda of Script.location
type error += Bad_stack_length type error += Bad_stack_length
type error += Bad_stack_item of int type error += Bad_stack_item of int
type error += Invalid_map_body : Script.location * _ Script_typed_ir.stack_ty -> error type error += Invalid_map_body : Script.location * _ Script_typed_ir.stack_ty -> error

View File

@ -335,6 +335,8 @@ and ('bef, 'aft) instr =
('rest, n num * 'rest) instr ('rest, n num * 'rest) instr
| Source : 'p ty * 'r ty -> | Source : 'p ty * 'r ty ->
('rest, ('p, 'r) typed_contract * 'rest) instr ('rest, ('p, 'r) typed_contract * 'rest) instr
| Self : 'p ty * 'r ty ->
('rest, ('p, 'r) typed_contract * 'rest) instr
| Amount : | Amount :
('rest, Tez.t * 'rest) instr ('rest, Tez.t * 'rest) instr

View File

@ -198,6 +198,7 @@ module Script : sig
| I_SIZE | I_SIZE
| I_SOME | I_SOME
| I_SOURCE | I_SOURCE
| I_SELF
| I_STEPS_TO_QUOTA | I_STEPS_TO_QUOTA
| I_SUB | I_SUB
| I_SWAP | I_SWAP

4
test/contracts/self.tz Normal file
View File

@ -0,0 +1,4 @@
parameter unit ;
storage (contract unit unit) ;
return unit ;
code { MAP_CDR { DROP ; SELF } }

View File

@ -405,6 +405,13 @@ account=tz1SuakBpFdG9b4twyfrSMqZzruxhpMeSrE5
$client transfer 0 from bootstrap1 to default_account -arg "\"$account\"" $client transfer 0 from bootstrap1 to default_account -arg "\"$account\""
assert_balance $account "100 ꜩ" assert_balance $account "100 ꜩ"
# Test SELF
init_with_transfer $CONTRACT_PATH/self.tz $key1 \
'"tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx"' 1,000 bootstrap1
$client transfer 0 from bootstrap1 to self
assert_storage_contains self "\"$(get_contract_addr self)\""
assert_fails $client typecheck data '{ Elt 0 1 ; Elt 0 1 }' against type '(map nat nat)' assert_fails $client typecheck data '{ Elt 0 1 ; Elt 0 1 }' against type '(map nat nat)'
assert_fails $client typecheck data '{ Elt 0 1 ; Elt 10 1 ; Elt 5 1 }' against type '(map nat nat)' assert_fails $client typecheck data '{ Elt 0 1 ; Elt 10 1 ; Elt 5 1 }' against type '(map nat nat)'
assert_fails $client typecheck data '{ "A" ; "C" ; "B" }' against type '(set string)' assert_fails $client typecheck data '{ "A" ; "C" ; "B" }' against type '(set string)'