diff --git a/CHANGES.rst b/CHANGES.rst index 49877f3c87a5f84ba27777b255a4b0ed1925a281..a8faed0c1f3bb492b090ac71f2b99e129338172d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -31,6 +31,8 @@ Client - Simulation returns correct errors on batches of operations where some are backtracked, failed and/or skipped. +- Disabled origination of contracts with timelock instructions. + Accuser ------- diff --git a/src/proto_013_PtJakart/lib_client/client_proto_context.ml b/src/proto_013_PtJakart/lib_client/client_proto_context.ml index 4d393a86741790db0cd9b0e7bf18589d82a04349..c070dfe1c73945f5cd1ea07ca207aae567f6884d 100644 --- a/src/proto_013_PtJakart/lib_client/client_proto_context.ml +++ b/src/proto_013_PtJakart/lib_client/client_proto_context.ml @@ -352,8 +352,24 @@ let save_contract ~force cctxt alias_name contract = RawContractAlias.add ~force cctxt alias_name contract >>=? fun () -> message_added_contract cctxt alias_name >>= fun () -> return_unit -let build_origination_operation ?fee ?gas_limit ?storage_limit ~initial_storage - ~code ~delegate ~balance () = +let check_for_timelock code = + let open Tezos_micheline.Micheline in + let rec has_timelock_opcodes = function + | Prim (_, (Script.T_chest | T_chest_key | I_OPEN_CHEST), _, _) -> true + | Seq (_, exprs) | Prim (_, _, exprs, _) -> + List.exists has_timelock_opcodes exprs + | Int _ | String _ | Bytes _ -> false + in + has_timelock_opcodes (root code) + +let build_origination_operation ?(allow_timelock = false) ?fee ?gas_limit + ?storage_limit ~initial_storage ~code ~delegate ~balance () = + (if (not allow_timelock) && check_for_timelock code then + failwith + "Origination of contracts containing time lock related instructions is \ + disabled in the client because of a vulnerability." + else return_unit) + >>=? fun () -> (* With the change of making implicit accounts delegatable, the following 3 arguments are being defaulted before they can be safely removed. *) Lwt.return (Michelson_v1_parser.parse_expression initial_storage) @@ -375,6 +391,7 @@ let originate_contract (cctxt : #full) ~chain ~block ?confirmations ?dry_run ?verbose_signing ?branch ?fee ?gas_limit ?storage_limit ~delegate ~initial_storage ~balance ~source ~src_pk ~src_sk ~code ~fee_parameter () = build_origination_operation + ~allow_timelock:false ?fee ?gas_limit ?storage_limit diff --git a/src/proto_014_PtKathma/lib_client/client_proto_context.ml b/src/proto_014_PtKathma/lib_client/client_proto_context.ml index 554d7d86ad2bad4ad515f6a3b860a6d2401f352a..13aae0d278edbf36b7340b323f170f352c5c344c 100644 --- a/src/proto_014_PtKathma/lib_client/client_proto_context.ml +++ b/src/proto_014_PtKathma/lib_client/client_proto_context.ml @@ -386,8 +386,24 @@ let save_contract ~force cctxt alias_name contract = RawContractAlias.add ~force cctxt alias_name contract >>=? fun () -> message_added_contract cctxt alias_name >>= fun () -> return_unit -let build_origination_operation ?fee ?gas_limit ?storage_limit ~initial_storage - ~code ~delegate ~balance () = +let check_for_timelock code = + let open Tezos_micheline.Micheline in + let rec has_timelock_opcodes = function + | Prim (_, (Script.T_chest | T_chest_key | I_OPEN_CHEST), _, _) -> true + | Seq (_, exprs) | Prim (_, _, exprs, _) -> + List.exists has_timelock_opcodes exprs + | Int _ | String _ | Bytes _ -> false + in + has_timelock_opcodes (root code) + +let build_origination_operation ?(allow_timelock = false) ?fee ?gas_limit + ?storage_limit ~initial_storage ~code ~delegate ~balance () = + (if (not allow_timelock) && check_for_timelock code then + failwith + "Origination of contracts containing time lock related instructions is \ + disabled in the client because of a vulnerability." + else return_unit) + >>=? fun () -> (* With the change of making implicit accounts delegatable, the following 3 arguments are being defaulted before they can be safely removed. *) Lwt.return (Michelson_v1_parser.parse_expression initial_storage) @@ -409,6 +425,7 @@ let originate_contract (cctxt : #full) ~chain ~block ?confirmations ?dry_run ?verbose_signing ?branch ?fee ?gas_limit ?storage_limit ~delegate ~initial_storage ~balance ~source ~src_pk ~src_sk ~code ~fee_parameter () = build_origination_operation + ~allow_timelock:false ?fee ?gas_limit ?storage_limit diff --git a/src/proto_alpha/lib_client/client_proto_context.ml b/src/proto_alpha/lib_client/client_proto_context.ml index 3f684db13a66b7c758cf92066080238524ad92ff..5c3d6e0a74d3a9d76a98cdd6242b4d57d963a64f 100644 --- a/src/proto_alpha/lib_client/client_proto_context.ml +++ b/src/proto_alpha/lib_client/client_proto_context.ml @@ -386,8 +386,24 @@ let save_contract ~force cctxt alias_name contract = RawContractAlias.add ~force cctxt alias_name contract >>=? fun () -> message_added_contract cctxt alias_name >>= fun () -> return_unit -let build_origination_operation ?fee ?gas_limit ?storage_limit ~initial_storage - ~code ~delegate ~balance () = +let check_for_timelock code = + let open Tezos_micheline.Micheline in + let rec has_timelock_opcodes = function + | Prim (_, (Script.T_chest | T_chest_key | I_OPEN_CHEST), _, _) -> true + | Seq (_, exprs) | Prim (_, _, exprs, _) -> + List.exists has_timelock_opcodes exprs + | Int _ | String _ | Bytes _ -> false + in + has_timelock_opcodes (root code) + +let build_origination_operation ?(allow_timelock = false) ?fee ?gas_limit + ?storage_limit ~initial_storage ~code ~delegate ~balance () = + (if (not allow_timelock) && check_for_timelock code then + failwith + "Origination of contracts containing time lock related instructions is \ + disabled in the client because of a vulnerability." + else return_unit) + >>=? fun () -> (* With the change of making implicit accounts delegatable, the following 3 arguments are being defaulted before they can be safely removed. *) Lwt.return (Michelson_v1_parser.parse_expression initial_storage) @@ -409,6 +425,7 @@ let originate_contract (cctxt : #full) ~chain ~block ?confirmations ?dry_run ?verbose_signing ?branch ?fee ?gas_limit ?storage_limit ~delegate ~initial_storage ~balance ~source ~src_pk ~src_sk ~code ~fee_parameter () = build_origination_operation + ~allow_timelock:false ?fee ?gas_limit ?storage_limit diff --git a/tezt/tests/main.ml b/tezt/tests/main.ml index 4c98fa2bfba5754340677ea81e0a2161cd21d44e..158c8bb8215a3e72d82b3df74965c8b6da86c55d 100644 --- a/tezt/tests/main.ml +++ b/tezt/tests/main.ml @@ -126,6 +126,7 @@ let register_protocol_agnostic_tests () = Stresstest_command.register ~protocols:[Alpha] ; Synchronisation_heuristic.register ~protocols:[Alpha] ; Tenderbake.register ~protocols:[Alpha] ; + Timelock.register ~protocols ; Tx_rollup.register ~protocols ; Tx_rollup_l2_node.register ~protocols ; Views.register [Alpha] diff --git a/tezt/tests/timelock.ml b/tezt/tests/timelock.ml new file mode 100644 index 0000000000000000000000000000000000000000..98ba13f78bfffea387b2039d09b2e25184774d67 --- /dev/null +++ b/tezt/tests/timelock.ml @@ -0,0 +1,98 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(* Testing + ------- + Component: Client commands + Invocation: dune exec tezt/tests/main.exe -- --file timelock.ml + Subject: Tests checking contracts with timelock cannot be originated +*) + +(* Script extracted from + `src/proto_alpha/lib_protocol/test/integration/michelson/contracts/timelock.tz` +*) +let script = + {| +storage (bytes); +parameter (pair (chest_key) (chest)); +code { + UNPAIR; + DIP {DROP}; + UNPAIR; + DIIP {PUSH nat 1000}; + OPEN_CHEST; + IF_LEFT + { # successful case + NIL operation; + PAIR ; + } + { + IF + { # first type of failure + PUSH bytes 0x01; + NIL operation; + PAIR; + } + { # second type of failure + PUSH bytes 0x00; + NIL operation; + PAIR; + } + } + } + |} + +let test_contract_not_originable ~protocol () = + let* client = Client.init_mockup ~protocol () in + let result = + Client.spawn_originate_contract + ~alias:Constant.bootstrap1.Account.alias + ~amount:Tez.zero + ~src:Constant.bootstrap1.alias + ~prg:script + ~init:"0xaa" + ~burn_cap:(Tez.of_int 1) + client + in + let msg = + rex + "Origination of contracts containing time lock related instructions is \ + disabled in the client because of a vulnerability." + in + Process.check_error ~exit_code:1 ~msg result + +let register ~protocols = + List.iter + (fun (title, test_function) -> + Protocol.register_test + ~__FILE__ + ~title + ~tags:["client"; "michelson"; "timelock"] + (fun protocol -> test_function ~protocol ()) + protocols) + [ + ( "Check a contract containing timelock operations is forbidden", + test_contract_not_originable ); + ]