diff --git a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml index 8493b72cd82bc866047a40640db9d8ef149b1359..bdd2a6921126dd07d3a53a11f04515a29b2953a3 100644 --- a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml @@ -2862,7 +2862,9 @@ let commands_rw () = return_unit); command ~group - ~desc:"Transfer tickets from an implicit account to a contract." + ~desc: + "Transfer tickets from an implicit account to a contract or another \ + implicit account." (args7 fee_arg dry_run_switch @@ -2884,7 +2886,9 @@ let commands_rw () = @@ prefixes ["with"; "entrypoint"] @@ Tezos_clic.param ~name:"entrypoint" - ~desc:"Entrypoint to use on the receiving contract." + ~desc: + "Entrypoint to use on the receiving contract or implicit account. \ + Needs to be \"default\" for implicit account destinations." entrypoint_parameter @@ prefixes ["and"; "contents"] @@ Tezos_clic.param diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index 6db5b22c0ad34f852623f4e67a630e1463d981b2..bb377c8e59614936fe6cedf623119ecc7a440b68 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -235,7 +235,7 @@ "Ticket_token_map", "Ticket_operations_diff", "Ticket_accounting", - "Tx_rollup_ticket", + "Ticket_transfer", "Script_interpreter_defs", "Script_interpreter", diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index 1478bb97d1fe5cf7b4d82c6559aa6332b242d1b7..4de0c6c9099b5a18b6117f71fb033546dfe69fcb 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -39,7 +39,6 @@ type error += | Empty_transaction of Contract.t | Tx_rollup_feature_disabled | Tx_rollup_invalid_transaction_ticket_amount - | Cannot_transfer_ticket_to_implicit | Sc_rollup_feature_disabled | Internal_operation_replay of Apply_internal_results.packed_internal_operation @@ -189,15 +188,6 @@ let () = | Tx_rollup_invalid_transaction_ticket_amount -> Some () | _ -> None) (fun () -> Tx_rollup_invalid_transaction_ticket_amount) ; - register_error_kind - `Permanent - ~id:"operation.cannot_transfer_ticket_to_implicit" - ~title:"Cannot transfer ticket to implicit account" - ~description:"Cannot transfer ticket to implicit account" - Data_encoding.unit - (function Cannot_transfer_ticket_to_implicit -> Some () | _ -> None) - (fun () -> Cannot_transfer_ticket_to_implicit) ; - let description = "Smart contract rollups will be enabled in a future proposal." in @@ -818,20 +808,19 @@ let apply_manager_operation : Tx_rollup_l2_qty.(amount <= zero) Script_tc_errors.Forbidden_zero_ticket_quantity >>?= fun () -> - Tx_rollup_ticket.parse_ticket + Ticket_transfer.parse_ticket ~consume_deserialization_gas ~ticketer ~contents ~ty ctxt >>=? fun (ctxt, ticket_token) -> - Tx_rollup_ticket.make_withdraw_order + Ticket_balance_key.of_ex_token ctxt - tx_rollup + ~owner:(Tx_rollup tx_rollup) ticket_token - claimer - amount - >>=? fun (ctxt, withdrawal) -> + >>=? fun (ticket_hash, ctxt) -> + let withdrawal = Tx_rollup_withdraw.{claimer; ticket_hash; amount} in return (withdrawal :: acc_withdraw, (withdrawal, ticket_token) :: acc, ctxt)) ([], [], ctxt) @@ -868,7 +857,7 @@ let apply_manager_operation : ~owner:(Contract (Contract.Implicit claimer)) ticket_token >>=? fun (claimer_ticket_hash, ctxt) -> - Tx_rollup_ticket.transfer_ticket_with_hashes + Ticket_transfer.transfer_ticket_with_hashes ctxt ~src_hash:tx_rollup_ticket_hash ~dst_hash:claimer_ticket_hash @@ -892,9 +881,36 @@ let apply_manager_operation : | Transfer_ticket {contents; ty; ticketer; amount; destination; entrypoint} -> ( match destination with - | Implicit _ -> fail Cannot_transfer_ticket_to_implicit + | Implicit _ -> + error_unless + Entrypoint.(entrypoint = default) + (Script_tc_errors.No_such_entrypoint entrypoint) + >>?= fun () -> + Ticket_transfer.parse_ticket + ~consume_deserialization_gas + ~ticketer + ~contents + ~ty + ctxt + >>=? fun (ctxt, ticket) -> + Ticket_transfer.transfer_ticket + ctxt + ~src:(Contract source_contract) + ~dst:(Contract destination) + ticket + amount + >>=? fun (ctxt, paid_storage_size_diff) -> + return + ( ctxt, + Transfer_ticket_result + { + balance_updates = []; + consumed_gas = Gas.consumed ~since:ctxt_before_op ~until:ctxt; + paid_storage_size_diff; + }, + [] ) | Originated destination_hash -> - Tx_rollup_ticket.parse_ticket_and_operation + Ticket_transfer.parse_ticket_and_operation ~consume_deserialization_gas ~ticketer ~contents @@ -904,23 +920,23 @@ let apply_manager_operation : ~entrypoint ~amount ctxt - >>=? fun (ctxt, ticket_token, op) -> - Tx_rollup_ticket.transfer_ticket + >>=? fun (ctxt, token, op) -> + Ticket_transfer.transfer_ticket ctxt ~src:(Contract source_contract) ~dst:(Contract destination) - ticket_token + token amount >>=? fun (ctxt, paid_storage_size_diff) -> - let result = - Transfer_ticket_result - { - balance_updates = []; - consumed_gas = Gas.consumed ~since:ctxt_before_op ~until:ctxt; - paid_storage_size_diff; - } - in - return (ctxt, result, [op])) + return + ( ctxt, + Transfer_ticket_result + { + balance_updates = []; + consumed_gas = Gas.consumed ~since:ctxt_before_op ~until:ctxt; + paid_storage_size_diff; + }, + [op] )) | Origination {delegate; script; credit} -> (* Internal originations have their address generated in the interpreter so that the script can use it immediately. diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index 83ad6c74bbbabb2ed4a3826b5b97d1ad7723785a..3841752d300ee94ca1c8118aa83b2a079c1daf35 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -243,7 +243,7 @@ Ticket_token_map Ticket_operations_diff Ticket_accounting - Tx_rollup_ticket + Ticket_transfer Script_interpreter_defs Script_interpreter Sc_rollup_management_protocol @@ -517,7 +517,7 @@ ticket_token_map.ml ticket_token_map.mli ticket_operations_diff.ml ticket_operations_diff.mli ticket_accounting.ml ticket_accounting.mli - tx_rollup_ticket.ml tx_rollup_ticket.mli + ticket_transfer.ml ticket_transfer.mli script_interpreter_defs.ml script_interpreter.ml script_interpreter.mli sc_rollup_management_protocol.ml sc_rollup_management_protocol.mli @@ -771,7 +771,7 @@ ticket_token_map.ml ticket_token_map.mli ticket_operations_diff.ml ticket_operations_diff.mli ticket_accounting.ml ticket_accounting.mli - tx_rollup_ticket.ml tx_rollup_ticket.mli + ticket_transfer.ml ticket_transfer.mli script_interpreter_defs.ml script_interpreter.ml script_interpreter.mli sc_rollup_management_protocol.ml sc_rollup_management_protocol.mli @@ -1030,7 +1030,7 @@ ticket_token_map.ml ticket_token_map.mli ticket_operations_diff.ml ticket_operations_diff.mli ticket_accounting.ml ticket_accounting.mli - tx_rollup_ticket.ml tx_rollup_ticket.mli + ticket_transfer.ml ticket_transfer.mli script_interpreter_defs.ml script_interpreter.ml script_interpreter.mli sc_rollup_management_protocol.ml sc_rollup_management_protocol.mli diff --git a/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml b/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml index aa59f2a42e715a82fd31557df251cebcf2d7d644..4fc80a70e39270e1f650cf03b6e65c1fa52567e8 100644 --- a/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml +++ b/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml @@ -5440,161 +5440,344 @@ module Withdraw = struct ignore i ; return_unit - (** [test_forge_deposit_withdraw_deposit ()] check the following scenario : - 1. forges new tickets and stores it in contract's storage. - 2. deposits theses tickets into a tx_rollup - 3. dispatches them to an account - 4. forges new tickets - 3. transfers the tickets from the account to the contract - 4. deposits the just received tickets into the tx_rollup *) - let test_forge_deposit_withdraw_deposit () = - context_init1 () >>=? fun (block, account) -> - originate block account >>=? fun (block, tx_rollup) -> - originate_forge_withdraw_deposit_contract account block - >>=? fun (forge_withdraw_deposit_contract, block) -> + module Forge_deposit_withdraw (Ctxt : sig + val forge_withdraw_deposit_contract : Contract.t + + val account : Contract.t + + val tx_rollup : Tx_rollup.t + end) = + struct + open Lwt_result_syntax + let forge_ticket block = - Op.transaction - (B block) - ~entrypoint:Entrypoint.default - ~parameters: - (Expr_common.( - pair_n - [ - int (Z.of_int Nat_ticket.contents_nat); - int (Tx_rollup_l2_qty.to_z Nat_ticket.amount); - ]) - |> Tezos_micheline.Micheline.strip_locations |> Script.lazy_expr) - ~fee:Tez.one - account - (Originated forge_withdraw_deposit_contract) - (Tez.of_mutez_exn 0L) - >>=? fun operation -> Block.bake ~operation block - in + let* operation = + Op.transaction + (B block) + ~entrypoint:Entrypoint.default + ~parameters: + (Expr_common.( + pair_n + [ + int (Z.of_int Nat_ticket.contents_nat); + int (Tx_rollup_l2_qty.to_z Nat_ticket.amount); + ]) + |> Tezos_micheline.Micheline.strip_locations |> Script.lazy_expr) + ~fee:Tez.one + Ctxt.account + Ctxt.forge_withdraw_deposit_contract + (Tez.of_mutez_exn 0L) + in + Block.bake ~operation block + let deposit_ticket block = - Op.transaction - (B block) - ~entrypoint:(Entrypoint.of_string_strict_exn "deposit") - ~parameters: - (Expr_common.( - pair_n - [ - string (Tx_rollup.to_b58check tx_rollup); - string "tz4MSfZsn6kMDczShy8PMeB628TNukn9hi2K"; - ]) - |> Tezos_micheline.Micheline.strip_locations |> Script.lazy_expr) - ~fee:Tez.one - account - (Originated forge_withdraw_deposit_contract) - (Tez.of_mutez_exn 0L) - >>=? fun operation -> Block.bake ~operation block - in + let* operation = + Op.transaction + (B block) + ~entrypoint:(Entrypoint.of_string_strict_exn "deposit") + ~parameters: + (Expr_common.( + pair_n + [ + string (Tx_rollup.to_b58check Ctxt.tx_rollup); + string "tz4MSfZsn6kMDczShy8PMeB628TNukn9hi2K"; + ]) + |> Tezos_micheline.Micheline.strip_locations |> Script.lazy_expr) + ~fee:Tez.one + Ctxt.account + Ctxt.forge_withdraw_deposit_contract + (Tez.of_mutez_exn 0L) + in + Block.bake ~operation block + let dispatch_ticket block = - Nat_ticket.withdrawal - (B block) - ~ticketer:(Originated forge_withdraw_deposit_contract) - ~claimer:account - tx_rollup - >>=? fun (withdraw, ticket_info) -> + let* withdraw, ticket_info = + Nat_ticket.withdrawal + (B block) + ~ticketer:Ctxt.forge_withdraw_deposit_contract + ~claimer:Ctxt.account + Ctxt.tx_rollup + in let message_index = 0 in - finalize_all_commitment_with_withdrawals - ~batches:["batch"] - ~account - ~tx_rollup - ~withdrawals:[(message_index, [withdraw])] - block - >>=? fun (commitment, context_hash_list, committed_level, block) -> + let* commitment, context_hash_list, committed_level, block = + finalize_all_commitment_with_withdrawals + ~batches:["batch"] + ~account:Ctxt.account + ~tx_rollup:Ctxt.tx_rollup + ~withdrawals:[(message_index, [withdraw])] + block + in let context_hash = - WithExceptions.Option.get - ~loc:__LOC__ - (List.nth context_hash_list message_index) + WithExceptions.Option.get ~loc:__LOC__ + @@ List.nth context_hash_list message_index in let message_result_path = compute_message_result_path commitment ~message_position:message_index in - Op.tx_rollup_dispatch_tickets - (B block) - ~source:account - ~message_index - ~message_result_path - tx_rollup - committed_level - context_hash - [ticket_info] - >>=? fun operation -> Block.bake ~operation block - in + let* operation = + Op.tx_rollup_dispatch_tickets + (B block) + ~source:Ctxt.account + ~message_index + ~message_result_path + Ctxt.tx_rollup + committed_level + context_hash + [ticket_info] + in + Block.bake ~operation block + let transfer_ticket block = - Op.transfer_ticket - (B block) - ~source:account - ~contents:(Script.lazy_expr Nat_ticket.contents) - ~ty:(Script.lazy_expr Nat_ticket.ty) - ~ticketer:(Originated forge_withdraw_deposit_contract) - ~amount: - (WithExceptions.Option.get ~loc:__LOC__ - @@ Ticket_amount.of_zint - @@ Tx_rollup_l2_qty.to_z Nat_ticket.amount) - ~destination:(Originated forge_withdraw_deposit_contract) - ~entrypoint:(Entrypoint.of_string_strict_exn "withdraw") - >>=? fun operation -> Block.bake ~operation block - in + let* operation = + Op.transfer_ticket + (B block) + ~source:Ctxt.account + ~contents:(Script.lazy_expr Nat_ticket.contents) + ~ty:(Script.lazy_expr Nat_ticket.ty) + ~ticketer:Ctxt.forge_withdraw_deposit_contract + ~amount: + (WithExceptions.Option.get ~loc:__LOC__ + @@ Ticket_amount.of_zint + @@ Tx_rollup_l2_qty.to_z Nat_ticket.amount) + ~destination:Ctxt.forge_withdraw_deposit_contract + ~entrypoint:(Entrypoint.of_string_strict_exn "withdraw") + in + Block.bake ~operation block + let token_one = - Nat_ticket.ex_token ~ticketer:(Originated forge_withdraw_deposit_contract) - in + Nat_ticket.ex_token ~ticketer:Ctxt.forge_withdraw_deposit_contract + let assert_contract_ticket_balance ~__LOC__ block balance = assert_ticket_balance ~loc:__LOC__ block token_one - (Contract (Originated forge_withdraw_deposit_contract)) + (Contract Ctxt.forge_withdraw_deposit_contract) balance - in + let assert_account_ticket_balance ~__LOC__ block balance = assert_ticket_balance ~loc:__LOC__ block token_one - (Contract account) + (Contract Ctxt.account) balance - in + let assert_tx_rollup_ticket_balance ~__LOC__ block balance = assert_ticket_balance ~loc:__LOC__ block token_one - (Tx_rollup tx_rollup) + (Tx_rollup Ctxt.tx_rollup) balance + end + + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4083 + Remove and replace the following test with transfers between + originated and implicit accounts directly. *) + + (** [test_forge_deposit_withdraw_deposit ()] checks the following scenario: + 1. forges new tickets and stores it in contract's storage. + 2. deposits these tickets into a tx_rollup + 3. dispatches them to an account + 4. forges new tickets + 3. transfers the tickets from the account to the contract + 4. deposits the just received tickets into the tx_rollup *) + let test_forge_deposit_withdraw_deposit () = + let open Lwt_result_syntax in + let* block, account = context_init1 () in + let* block, tx_rollup = originate block account in + let* forge_withdraw_deposit_contract, block = + originate_forge_withdraw_deposit_contract account block in + let open Forge_deposit_withdraw (struct + let forge_withdraw_deposit_contract = + Contract.Originated forge_withdraw_deposit_contract + + let account = account + + let tx_rollup = tx_rollup + end) in (* forge tickets and store them in the contract storage. *) - forge_ticket block >>=? fun block -> - assert_contract_ticket_balance ~__LOC__ block (Some 10) >>=? fun () -> - assert_tx_rollup_ticket_balance ~__LOC__ block None >>=? fun () -> - assert_account_ticket_balance ~__LOC__ block None >>=? fun () -> + let* block = forge_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block (Some 10) in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block None in + let* () = assert_account_ticket_balance ~__LOC__ block None in (* deposit tickets from the contract storage into the tx_rollup. *) - deposit_ticket block >>=? fun block -> - assert_contract_ticket_balance ~__LOC__ block None >>=? fun () -> - assert_tx_rollup_ticket_balance ~__LOC__ block (Some 10) >>=? fun () -> - assert_account_ticket_balance ~__LOC__ block None >>=? fun () -> + let* block = deposit_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block None in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block (Some 10) in + let* () = assert_account_ticket_balance ~__LOC__ block None in (* add withdrawals, then transfer the tickets from tx_rollup to account. *) - dispatch_ticket block >>=? fun block -> - assert_contract_ticket_balance ~__LOC__ block None >>=? fun () -> - assert_tx_rollup_ticket_balance ~__LOC__ block None >>=? fun () -> - assert_account_ticket_balance ~__LOC__ block (Some 10) >>=? fun () -> + let* block = dispatch_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block None in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block None in + let* () = assert_account_ticket_balance ~__LOC__ block (Some 10) in (* forge new tickets and store them in the contract storage. *) - forge_ticket block >>=? fun block -> - assert_contract_ticket_balance ~__LOC__ block (Some 10) >>=? fun () -> - assert_tx_rollup_ticket_balance ~__LOC__ block None >>=? fun () -> - assert_account_ticket_balance ~__LOC__ block (Some 10) >>=? fun () -> + let* block = forge_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block (Some 10) in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block None in + let* () = assert_account_ticket_balance ~__LOC__ block (Some 10) in (* transfer tickets from account to the contract. *) - transfer_ticket block >>=? fun block -> - assert_contract_ticket_balance ~__LOC__ block (Some 20) >>=? fun () -> - assert_tx_rollup_ticket_balance ~__LOC__ block None >>=? fun () -> - assert_account_ticket_balance ~__LOC__ block None >>=? fun () -> - deposit_ticket block >>=? fun block -> + let* block = transfer_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block (Some 20) in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block None in + let* () = assert_account_ticket_balance ~__LOC__ block None in (* deposit back the tickets that was just transfered. *) - assert_contract_ticket_balance ~__LOC__ block (Some 10) >>=? fun () -> - assert_tx_rollup_ticket_balance ~__LOC__ block (Some 10) >>=? fun () -> + let* block = deposit_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block (Some 10) in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block (Some 10) in assert_account_ticket_balance ~__LOC__ block None + (** [test_forge_deposit_withdraw_implicit_transfer ()] checks the following scenario: + 1. forges new tickets and stores them in contract's storage + 2. deposits these tickets into a tx_rollup + 3. dispatches them to an account + 4. transfers the dispatched tickets to another implicit account *) + let test_forge_deposit_withdraw_implicit_transfer () = + let open Lwt_result_syntax in + let* block, (account, another_account) = context_init2 () in + let* block, tx_rollup = originate block account in + let* forge_withdraw_deposit_contract, block = + originate_forge_withdraw_deposit_contract account block + in + let open Forge_deposit_withdraw (struct + let forge_withdraw_deposit_contract = + Contract.Originated forge_withdraw_deposit_contract + + let account = account + + let tx_rollup = tx_rollup + end) in + let transfer_ticket_to_implicit_with_wrong_type block = + let* operation = + Op.transfer_ticket + (B block) + ~source:account + ~contents:(Script.lazy_expr Nat_ticket.contents) + ~ty:(Script.lazy_expr @@ Expr.from_string "string") + ~ticketer:(Originated forge_withdraw_deposit_contract) + ~amount: + (WithExceptions.Option.get ~loc:__LOC__ + @@ Ticket_amount.of_zint + @@ Tx_rollup_l2_qty.to_z Nat_ticket.amount) + ~destination:another_account + ~entrypoint:Entrypoint.default + in + Block.bake ~operation block + in + let transfer_ticket_to_implicit_with_too_large_amount block = + let* operation = + Op.transfer_ticket + (B block) + ~source:account + ~contents:(Script.lazy_expr Nat_ticket.contents) + ~ty:(Script.lazy_expr @@ Expr.from_string "string") + ~ticketer:(Originated forge_withdraw_deposit_contract) + ~amount: + (WithExceptions.Option.get ~loc:__LOC__ + @@ Ticket_amount.of_zint @@ Z.of_int64 + @@ Int64.add Nat_ticket.int64_amount 1L) + ~destination:another_account + ~entrypoint:Entrypoint.default + in + Block.bake ~operation block + in + let transfer_ticket_to_self_implicit block = + let* operation = + Op.transfer_ticket + (B block) + ~source:account + ~contents:(Script.lazy_expr Nat_ticket.contents) + ~ty:(Script.lazy_expr @@ Expr.from_string "string") + ~ticketer:(Originated forge_withdraw_deposit_contract) + ~amount: + (WithExceptions.Option.get ~loc:__LOC__ + @@ Ticket_amount.of_zint + @@ Tx_rollup_l2_qty.to_z Nat_ticket.amount) + ~destination:account + ~entrypoint:Entrypoint.default + in + Block.bake ~operation block + in + let transfer_ticket_to_implicit block = + let* operation = + Op.transfer_ticket + (B block) + ~source:account + ~contents:(Script.lazy_expr Nat_ticket.contents) + ~ty:(Script.lazy_expr Nat_ticket.ty) + ~ticketer:(Originated forge_withdraw_deposit_contract) + ~amount: + (WithExceptions.Option.get ~loc:__LOC__ + @@ Ticket_amount.of_zint + @@ Tx_rollup_l2_qty.to_z Nat_ticket.amount) + ~destination:another_account + ~entrypoint:Entrypoint.default + in + Block.bake ~operation block + in + let assert_another_account_ticket_balance ~__LOC__ block balance = + assert_ticket_balance + ~loc:__LOC__ + block + token_one + (Contract another_account) + balance + in + (* forge tickets and store them in the contract storage. *) + let* block = forge_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block (Some 10) in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block None in + let* () = assert_account_ticket_balance ~__LOC__ block None in + (* deposit tickets from the contract storage into the tx_rollup. *) + let* block = deposit_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block None in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block (Some 10) in + let* () = assert_account_ticket_balance ~__LOC__ block None in + (* add withdrawals, then transfer the tickets from tx_rollup to account. *) + let* block = dispatch_ticket block in + let* () = assert_contract_ticket_balance ~__LOC__ block None in + let* () = assert_tx_rollup_ticket_balance ~__LOC__ block None in + let* () = assert_account_ticket_balance ~__LOC__ block (Some 10) in + (* transfer the tickets dispatched from tx_rollup to another implicit account (error cases). *) + let*! result = transfer_ticket_to_implicit_with_wrong_type block in + let* () = + Assert.is_error + ~pp:(fun pp _ -> + Format.pp_print_string + pp + "tickets with wrong type should not be accepted") + ~loc:__LOC__ + result + in + let*! result = transfer_ticket_to_implicit_with_too_large_amount block in + let* () = + Assert.is_error + ~pp:(fun pp _ -> + Format.pp_print_string + pp + "over-drafting tickets should not be acceptable") + ~loc:__LOC__ + result + in + let*! result = transfer_ticket_to_self_implicit block in + let* () = + Assert.is_error + ~pp:(fun pp _ -> + Format.pp_print_string + pp + "transferring tickets to oneself should not be acceptable") + ~loc:__LOC__ + result + in + let* () = assert_account_ticket_balance ~__LOC__ block (Some 10) in + let* () = assert_another_account_ticket_balance ~__LOC__ block None in + (* transfer the tickets dispatched from tx_rollup to another implicit account (success case). *) + let* block = transfer_ticket_to_implicit block in + let* () = assert_account_ticket_balance ~__LOC__ block None in + assert_another_account_ticket_balance ~__LOC__ block (Some 10) + let tests = [ Tztest.tztest "Test withdraw" `Quick test_valid_withdraw; @@ -5637,10 +5820,14 @@ module Withdraw = struct `Quick test_multiple_withdrawals_multiple_batches; Tztest.tztest - "Test multiple withdrawals from the same batch and from different \ - batches" + "Test deposit, followed by withdrawal, followed by deposit" `Quick test_forge_deposit_withdraw_deposit; + Tztest.tztest + "Test deposit, followed by withdrawal, followed by transfer to an \ + implicit account" + `Quick + test_forge_deposit_withdraw_implicit_transfer; ] end diff --git a/src/proto_alpha/lib_protocol/ticket_scanner.ml b/src/proto_alpha/lib_protocol/ticket_scanner.ml index 3b2106187fc47594ab658709e34cb3d3d4c740b8..6c48ef0bc026fe29a571e85e847e130c7832f826 100644 --- a/src/proto_alpha/lib_protocol/ticket_scanner.ml +++ b/src/proto_alpha/lib_protocol/ticket_scanner.ml @@ -257,8 +257,7 @@ module Ticket_collection = struct type accumulator = ex_ticket list - type 'a continuation = - Alpha_context.context -> accumulator -> 'a tzresult Lwt.t + type 'a continuation = context -> accumulator -> 'a tzresult Lwt.t (* Currently this always returns the original list. @@ -297,7 +296,7 @@ module Ticket_collection = struct let tickets_of_set : type a ret. - Alpha_context.context -> + context -> a Script_typed_ir.comparable_ty -> a Script_typed_ir.set -> accumulator -> diff --git a/src/proto_alpha/lib_protocol/ticket_scanner.mli b/src/proto_alpha/lib_protocol/ticket_scanner.mli index 90521e3f3e1de89b4bc48e13e240cda7fbc10eb2..49d3a11b7eeb216a8f86c9f6954f4e5687604ebc 100644 --- a/src/proto_alpha/lib_protocol/ticket_scanner.mli +++ b/src/proto_alpha/lib_protocol/ticket_scanner.mli @@ -26,6 +26,8 @@ (** This module provides an API for extracting tickets of arbitrary types from an OCaml values, given a type-witness. *) +open Alpha_context + (** A type for representing existentially quantified tickets (tickets with different types of payloads). An [ex_ticket] value consists of: - A type-witness representing the type of the content of the ticket. @@ -45,9 +47,7 @@ type 'a has_tickets shape [ty]. *) val type_has_tickets : - Alpha_context.context -> - ('a, _) Script_typed_ir.ty -> - ('a has_tickets * Alpha_context.context) tzresult + context -> ('a, _) Script_typed_ir.ty -> ('a has_tickets * context) tzresult (** [tickets_of_value ctxt ~include_lazy ht value] extracts all tickets from the given [value], using the type-witness [ht]. The [include_lazy] flag @@ -61,11 +61,11 @@ val type_has_tickets : between overlapping keys between the context and the overlay. *) val tickets_of_value : - Alpha_context.context -> + context -> include_lazy:bool -> 'a has_tickets -> 'a -> - (ex_ticket list * Alpha_context.context) tzresult Lwt.t + (ex_ticket list * context) tzresult Lwt.t (** [tickets_of_node ctxt ~include_lazy ht node] extracts all tickets from the given [node], using the type-witness [ht].If [ht] indicates that @@ -81,11 +81,11 @@ val tickets_of_value : the overlay. *) val tickets_of_node : - Alpha_context.context -> + context -> include_lazy:bool -> 'a has_tickets -> - Alpha_context.Script.node -> - (ex_ticket list * Alpha_context.context) tzresult Lwt.t + Script.node -> + (ex_ticket list * context) tzresult Lwt.t (** [has_tickets ht] returns whether or not the type of the given [has_tickets] witness [ht] has tickets. *) @@ -94,11 +94,9 @@ val has_tickets : 'a has_tickets -> bool (** [ex_ticket_size ctxt ex_ticket] returns the size of the in-memory representation of [ex_ticket] in bytes. *) val ex_ticket_size : - Alpha_context.context -> + context -> ex_ticket -> - (Saturation_repr.may_saturate Saturation_repr.t * Alpha_context.context) - tzresult - Lwt.t + (Saturation_repr.may_saturate Saturation_repr.t * context) tzresult Lwt.t (** [ex_token_and_amount_of_ex_ticket ex_ticket] returns the token and amount of the given ticket [ex_ticket]. *) diff --git a/src/proto_alpha/lib_protocol/tx_rollup_ticket.ml b/src/proto_alpha/lib_protocol/ticket_transfer.ml similarity index 79% rename from src/proto_alpha/lib_protocol/tx_rollup_ticket.ml rename to src/proto_alpha/lib_protocol/ticket_transfer.ml index d2d6e06c0afffd0c259024396ef5bac038df2516..57b295a6730e3a5238402e47381cdf4c49239b43 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_ticket.ml +++ b/src/proto_alpha/lib_protocol/ticket_transfer.ml @@ -2,6 +2,7 @@ (* *) (* Open Source License *) (* Copyright (c) 2022 Nomadic Labs *) +(* Copyright (c) 2022 Margiold *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) @@ -37,24 +38,15 @@ let parse_ticket ~consume_deserialization_gas ~ticketer ~contents ~ty ctxt = contents_type (Micheline.root contents) >>=? fun (contents, ctxt) -> - return @@ (ctxt, Ticket_token.Ex_token {ticketer; contents_type; contents}) + let token = Ticket_token.Ex_token {ticketer; contents_type; contents} in + return (ctxt, token) let parse_ticket_and_operation ~consume_deserialization_gas ~ticketer ~contents ~ty ~source ~destination ~entrypoint ~amount ctxt = - Script.force_decode_in_context ~consume_deserialization_gas ctxt ty - >>?= fun (ty, ctxt) -> - Script.force_decode_in_context ~consume_deserialization_gas ctxt contents - >>?= fun (contents, ctxt) -> - Script_ir_translator.parse_comparable_ty ctxt (Micheline.root ty) - >>?= fun (Ex_comparable_ty contents_type, ctxt) -> - Script_ir_translator.parse_comparable_data - ctxt - contents_type - (Micheline.root contents) - >>=? fun (contents, ctxt) -> - let ticket_token = - Ticket_token.Ex_token {ticketer; contents_type; contents} - in + parse_ticket ~consume_deserialization_gas ~ticketer ~contents ~ty ctxt + >>=? fun ( ctxt, + (Ticket_token.Ex_token {contents; contents_type; ticketer} as + token) ) -> Script_typed_ir.ticket_t Micheline.dummy_location contents_type >>?= fun ticket_ty -> let ticket = Script_typed_ir.{ticketer; contents; amount} in @@ -79,18 +71,10 @@ let parse_ticket_and_operation ~consume_deserialization_gas ~ticketer ~contents }; } in - return (ctxt, ticket_token, op) - -let make_withdraw_order ctxt tx_rollup ex_ticket claimer amount = - Ticket_balance_key.of_ex_token ctxt ~owner:(Tx_rollup tx_rollup) ex_ticket - >>=? fun (tx_rollup_ticket_hash, ctxt) -> - let withdrawal = - Tx_rollup_withdraw.{claimer; ticket_hash = tx_rollup_ticket_hash; amount} - in - return (ctxt, withdrawal) + return (ctxt, token, op) -let transfer_ticket_with_hashes ctxt ~src_hash ~dst_hash - (qty : Script_typed_ir.ticket_amount) = +let transfer_ticket_with_hashes ctxt ~src_hash ~dst_hash (qty : Ticket_amount.t) + = let qty = Script_int.(to_zint (qty :> n num)) in Ticket_balance.adjust_balance ctxt src_hash ~delta:(Z.neg qty) >>=? fun (src_storage_diff, ctxt) -> diff --git a/src/proto_alpha/lib_protocol/tx_rollup_ticket.mli b/src/proto_alpha/lib_protocol/ticket_transfer.mli similarity index 85% rename from src/proto_alpha/lib_protocol/tx_rollup_ticket.mli rename to src/proto_alpha/lib_protocol/ticket_transfer.mli index 83a5f38b5fdb620f835cac815526bbc87526d2be..b7c31684d6fadeaa987d72a03f5713414c7d0dcd 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_ticket.mli +++ b/src/proto_alpha/lib_protocol/ticket_transfer.mli @@ -2,6 +2,7 @@ (* *) (* Open Source License *) (* Copyright (c) 2022 Nomadic Labs *) +(* Copyright (c) 2022 Margiold *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) @@ -25,10 +26,7 @@ open Alpha_context -(** This module provides various helpers to manipulate tickets, that - are used by the Transaction Rollups. *) - -(** [parse_ticket ~consume_deserialization_gas ~ticketer ~contents ~ty +(** [parse_ticket ~ticketer ~contents ~ty ctxt] reconstructs a ticket from individual parts submitted as part of a layer-1 operation. *) val parse_ticket : @@ -56,18 +54,6 @@ val parse_ticket_and_operation : tzresult Lwt.t -(** [make_withdraw_order ctxt tx_rollup ex_token claimer amount] - computes a withdraw order that specify that [claimer] is entitled - to get the ownership of [amount] units of [ex_token] which were - deposited to [tx_rollup]. *) -val make_withdraw_order : - context -> - Tx_rollup.t -> - Ticket_token.ex_token -> - public_key_hash -> - Tx_rollup_l2_qty.t -> - (context * Tx_rollup_withdraw.order) tzresult Lwt.t - (** [transfer_ticket_with_hashes ctxt ~src_hash ~dst_hash qty] updates the table of tickets moves [qty] units of a given ticket from a source to a destination, as encoded by [src_hash] and [dst_hash]. @@ -85,7 +71,7 @@ val transfer_ticket_with_hashes : context -> src_hash:Ticket_hash.t -> dst_hash:Ticket_hash.t -> - Script_typed_ir.ticket_amount -> + Ticket_amount.t -> (context * Z.t) tzresult Lwt.t (** [transfer_ticket ctxt ~src ~dst ex_token qty] updates the table of @@ -100,5 +86,5 @@ val transfer_ticket : src:Destination.t -> dst:Destination.t -> Ticket_token.ex_token -> - Script_typed_ir.ticket_amount -> + Ticket_amount.t -> (context * Z.t, error trace) result Lwt.t diff --git a/src/proto_alpha/lib_protocol/zk_rollup_apply.ml b/src/proto_alpha/lib_protocol/zk_rollup_apply.ml index 4ab36d81b59c1ddc4bb616b4d8f05be728bbc51a..1e14ee3c14aeeba59bed077e7b615ee9f1342a0b 100644 --- a/src/proto_alpha/lib_protocol/zk_rollup_apply.ml +++ b/src/proto_alpha/lib_protocol/zk_rollup_apply.ml @@ -411,7 +411,7 @@ let perform_exits ctxt exits = (Ticket_amount.of_zint (Z.abs @@ op.price.amount)) in let* ctxt, diff = - Tx_rollup_ticket.transfer_ticket_with_hashes + Ticket_transfer.transfer_ticket_with_hashes ctxt ~src_hash:op.price.id ~dst_hash:receiver_ticket_hash diff --git a/tezt/tests/manager_operations.ml b/tezt/tests/manager_operations.ml index 653f37be899eaf3cb58dd553bd0ef8c47d7b4fcb..959ff4dfdb3b63ccebad5e73d198e0ee71251e02 100644 --- a/tezt/tests/manager_operations.ml +++ b/tezt/tests/manager_operations.ml @@ -1821,7 +1821,7 @@ module Tx_rollup = struct ~__FILE__ ~title:"Deserialization of transfer ticket" (* TX rollups activated with (Proto 13) Jakarta *) - ~supports:(Protocol.From_protocol 13) + ~supports:(Protocol.Between_protocols (13, 15)) ~tags: [ "precheck"; @@ -1861,6 +1861,72 @@ module Tx_rollup = struct in unit + let transfer_ticket_no_overdraft = + Protocol.register_test + ~__FILE__ + ~title:"Deserialization of transfer ticket to implicit account" + (* Ticket transfer to implicit accounts was introduced in (Proto 16) M *) + ~supports:(Protocol.From_protocol 16) + ~tags: + [ + "precheck"; + "deserialization"; + "gas"; + "transfer_ticket"; + "tx_rollup"; + "canary"; + ] + @@ fun protocol -> + let* nodes = Helpers.init ~protocol () in + let size_kB = 20 in + let min_deserialization_gas = + (* contents *) deserialization_gas ~size_kB + (* ty *) 1 + in + let* _oph = + Memchecks.with_applied_checks + ~__LOC__ + nodes + ~expected_statuses:["failed"] + ~expected_errors:[["gas_exhausted.operation"]] + (* Does not fail in precheck, so is applied (as failed) *) + @@ fun () -> + Operation.inject_transfer_ticket + ~protocol + ~source:Constant.bootstrap1 + ~gas_limit:(min_deserialization_gas + 1000) + (* we add 1000 (the gas for manager operation) to avoid failing with + gas_exhausted right after precheck *) + ~contents:(`Json (`O [("bytes", `String (make_zero_hex ~size_kB))])) + ~ty:(`Json (`O [("prim", `String "bytes")])) + ~ticketer:Constant.bootstrap2.public_key_hash + ~amount:1 + ~destination:Constant.bootstrap1.public_key_hash + ~entrypoint:"default" + nodes.main.client + in + let* _oph = + Memchecks.with_applied_checks + ~__LOC__ + nodes + ~expected_statuses:["failed"] + ~expected_errors:[["Negative_ticket_balance"]] + (* Does not fail in precheck, is applied with expected failure *) + @@ fun () -> + Operation.inject_transfer_ticket + ~protocol + ~source:Constant.bootstrap1 + ~gas_limit:(min_deserialization_gas + 10000) + (* we add 10000 (the gas for manager operation) to make sure it goes all the way to trial application *) + ~contents:(`Json (`O [("bytes", `String (make_zero_hex ~size_kB))])) + ~ty:(`Json (`O [("prim", `String "bytes")])) + ~ticketer:Constant.bootstrap2.public_key_hash + ~amount:1 + ~destination:Constant.bootstrap1.public_key_hash + ~entrypoint:"default" + nodes.main.client + in + unit + (* This test makes sure that the deserialization is performed in the precheck. The operation should be refused because there isn't enough gas to deserialize the micheline parameters. *) @@ -1896,7 +1962,8 @@ module Tx_rollup = struct let register ~protocols = transfer_ticket_deserialization_canary protocols ; - transfer_ticket_deserialization_too_large protocols + transfer_ticket_deserialization_too_large protocols ; + transfer_ticket_no_overdraft protocols end let register ~protocols =