diff --git a/.dockerignore b/.dockerignore index 51b0b2efdb76903cc2f9d1c08fdb65c138639da4..89ac970188f7ee8d9db08cec0c3c6e426402425c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -51,6 +51,7 @@ _coverage_report **/*.orig .idea +.vscode # .venv directories are created by poetry if the option in-project is set to # true. **/.venv diff --git a/.gitignore b/.gitignore index 0a83d42f621d340734c3e52592e705aebf1807a7..79d441df0a71b812f7521d51e2537bdeb8a4b975 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ __pycache__ *.orig .idea +.vscode # .venv directories are created by poetry if the option in-project is set to true. **/.venv **/.mypy_cache/ diff --git a/docs/alpha/event.rst b/docs/alpha/event.rst new file mode 100644 index 0000000000000000000000000000000000000000..5c9d28405c41caf40393eac0d6e0f02b6a8d4f8b --- /dev/null +++ b/docs/alpha/event.rst @@ -0,0 +1,118 @@ +Contract event logging +====================== + +Contract event logging is a way for contracts to deliver event-like information to external applications. +This mechanism allows off-chain applications to react to Tezos contracts execution. + +Event address +------------- +On the Tezos chain, an event is uniquely identified with a 53-byte long base58 address starting with ``ev1``. +This address is computed from an event schema and an event tag (see below for details). +An event schema is a Micheline expression denoting the Michelson type of the event data attachment. +For instance, this schema could be ``or (nat %int) (string %str)`` in Micheline. +An event tag is an identifier represented by a string, such as ``writeOut`` or ``mintToken``. + +Computing an event address +-------------------------- +An address for a event is computed from a Base58 32-byte hash of the binary serialization of +the original Michelson node, with all annotations, separated by a ``0x05`` byte. + +For instance, the following event schema declaration produces a unique address of +``ev12m5E1yW14mc9rsrcdGAWVfDSdmRGuctykrVU55bHZBGv9kmdhW``, which is a Base58-encoded Blake2b hash of +a ``0x05`` byte and then followed by the binary serialization of ``or (nat %int) (string %str)`` +with all the annotations ``%int`` and ``%str``. + +:: + %tag1 (or (nat %int) (string %str)); + +Alternatively, there is a RPC exposed by the node for computing this address. +Make a ``POST`` request to ``helpers/scripts/event_address`` with a JSON payload of shape +``{"type": ""}``, and it will return a JSON response of shape +``{"address": "ev1..."}``. + +Sending events +-------------- +Contract events can be emitted by invoking the Michelson instruction ``EMIT``. +``EMIT %tag ty`` pops an item of type ``ty`` off the stack and pushes an ``operation`` onto the stack. + +``EMIT`` has the following typing rule. + +:: + EMIT %tag ty :: 'ty : 'S -> operation : 'S + +To actually send out the events, most importantly, the produced ``operation``\s must be included into the list of +operations that the main contract code returns along with other ``operation``\s that this contract wants to effect. + +Event +----- +A contract event in Tezos consists of the following data. + +- An event ``data`` of a certain type ``event``. +- An event address associated with a certain event tag and data type. + +Each successful contract execution attaches into the transaction receipt a list of contract events +arranged in the order of appearance in the resultant list of operations. +There, the events are made ready for consumption by services observing the chain. + +Example +------- +Suppose a contract wants to emit events with the following type for the data attachment. + +:: + or (nat %int) (string %str); + +Then, this contract may generate an event emission operation with the following instructions. +Note that the ``EMIT`` instruction will emit an event with a tag ``notify_client``. + +:: + PUSH string "right"; + RIGHT nat; + EMIT %notify_client (or (nat %int) (string %str)); + + +Retrieving events +----------------- +Events successfully emitted can be read off directly from transaction results. +This is typically achieved by making JSON RPCs to the block service. +It will return a list of operations, each including the event entries with the information above. + +Here is a sample result from a call. + +:: + + { + "protocol": "ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK", + "hash": "opNX59asPwNZGu2kFiHFVJzn7QwtyaExoLtxdZSm3Q3o4jDdSmo", + // ... fields elided for brevity + "contents": [ + { + "kind": "transaction", + // ... fields elided for brevity + "metadata": { + // ... fields elided for brevity + "internal_operation_results": [ + { + "status": "applied", + // ... fields elided for brevity + "source": "KT...", + "destination": "ev1...", // <~ + "parameters": { // <~ + "entrypoint": "event_tag...", // <~ + "value": { // <~ + "prim": "Right", // <~ + "args": [ // <~ + { // <~ + "string": "right" // <~ + } // <~ + ] // <~ + } // <~ + } // <~ + } + ] + } + } + ] + } + +Note that the ``operation`` produced by ``EMIT`` does not constitute a call to any other contract. +Events emitted with ``EMIT`` is optimized to avoid calls to external contracts to reduce gas usage. diff --git a/docs/alpha/michelson.rst b/docs/alpha/michelson.rst index 8e6812ba32d811ba8f641bd344c0f7eb94190229..6b5d875efdf524725e333d6aaad21882a460bb6e 100644 --- a/docs/alpha/michelson.rst +++ b/docs/alpha/michelson.rst @@ -2339,6 +2339,20 @@ Operations on timelock :: chest_key : chest : nat : 'S -> or bytes bool : 'S +Events +~~~~~~ + +- ``EMIT %tag 'ty``: constructs an operation that will write an event into + the transaction receipt after the successful execution of this contract. + It accepts as arguments an annotation as a tag to the emitted event and + the type of data attachment. + + See :doc:`Event ` for more information. + +:: + + :: 'ty : 'S -> operation : 'S + Removed instructions ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/alpha/protocol.rst b/docs/alpha/protocol.rst index 30430754cfe5b56a6d8baae5853676bb902d27f4..05ac61a2d07057b623a6972bd108917176e7654b 100644 --- a/docs/alpha/protocol.rst +++ b/docs/alpha/protocol.rst @@ -75,3 +75,8 @@ Sapling, etc), and some details about its implementation. :maxdepth: 2 plugins + +.. toctree:: + :maxdepth: 2 + + event diff --git a/docs/protocols/alpha.rst b/docs/protocols/alpha.rst index 0e1a1219c16670ded21806a3c221e2799443360d..65a67939f20f34eedcfa550849496f1bb314cf82 100644 --- a/docs/protocols/alpha.rst +++ b/docs/protocols/alpha.rst @@ -17,6 +17,18 @@ Smart Contract Optimistic Rollups Rollups supporting execution of smart contracts. (MRs :gl:`!4933`, :gl:`!4812`) +Contract Event Logging +---------------------- + +Contracts may now emit events thanks to a new ``EMIT`` instruction. + +Event emissions are denoted by internal operations that perform a contract call to a specific class of addresses starting with `ev1`. + +This new class of addresses can be computed with a newly introduced RPC at ``helpers/scripts/event_address``. + +See :doc:`Event <../alpha/event>` for more information. +(MR :gl:`!4656`) + Breaking Changes ---------------- diff --git a/src/proto_alpha/lib_benchmarks_proto/interpreter_model.ml b/src/proto_alpha/lib_benchmarks_proto/interpreter_model.ml index 4754e9766a3cdb166181ed6a38a3a838b413d3f5..10224dc78f35eba7d437ad41e3d6cf2de12c5bdb 100644 --- a/src/proto_alpha/lib_benchmarks_proto/interpreter_model.ml +++ b/src/proto_alpha/lib_benchmarks_proto/interpreter_model.ml @@ -454,7 +454,8 @@ let ir_model ?specialization instr_or_cont = | N_IHalt -> model_0 instr_or_cont (const1_model name) | N_IApply -> model_0 instr_or_cont (const1_model name) | N_ILog -> model_0 instr_or_cont (const1_model name) - | N_IOpen_chest -> model_2 instr_or_cont (open_chest_model name)) + | N_IOpen_chest -> model_2 instr_or_cont (open_chest_model name) + | N_IEmit -> model_0 instr_or_cont (const1_model name)) | Cont_name cont -> ( match cont with | N_KNil -> model_0 instr_or_cont (const1_model name) diff --git a/src/proto_alpha/lib_benchmarks_proto/interpreter_workload.ml b/src/proto_alpha/lib_benchmarks_proto/interpreter_workload.ml index db53a2d2e1aa24987bd76741f469a723e3975e1e..ad3a114d248fa755520f129f6095a00cc253dfeb 100644 --- a/src/proto_alpha/lib_benchmarks_proto/interpreter_workload.ml +++ b/src/proto_alpha/lib_benchmarks_proto/interpreter_workload.ml @@ -223,6 +223,8 @@ type instruction_name = | N_ILog (* Timelock*) | N_IOpen_chest + (* Event *) + | N_IEmit type continuation_name = | N_KNil @@ -408,6 +410,7 @@ let string_of_instruction_name : instruction_name -> string = | N_IHalt -> "N_IHalt" | N_ILog -> "N_ILog" | N_IOpen_chest -> "N_IOpen_chest" + | N_IEmit -> "N_IEmit" let string_of_continuation_name : continuation_name -> string = fun c -> @@ -628,6 +631,7 @@ let all_instructions = N_IHalt; N_ILog; N_IOpen_chest; + N_IEmit; ] let all_continuations = @@ -1093,6 +1097,9 @@ module Instructions = struct let open_chest log_time size = ir_sized_step N_IOpen_chest (binary "log_time" log_time "size" size) + + (** cost model for the EMIT instruction *) + let emit = ir_sized_step N_IEmit nullary end module Control = struct @@ -1425,6 +1432,7 @@ let extract_ir_sized_step : let log_time = Z.log2 Z.(one + Script_int.to_zint time) |> Size.of_int in Instructions.open_chest log_time plaintext_size | IMin_block_time _, _ -> Instructions.min_block_time + | IEmit _, _ -> Instructions.emit let extract_control_trace (type bef_top bef aft_top aft) (cont : (bef_top, bef, aft_top, aft) Script_typed_ir.continuation) = diff --git a/src/proto_alpha/lib_client/client_proto_programs.ml b/src/proto_alpha/lib_client/client_proto_programs.ml index e2e7d31e3803719f9df725ee37a1a49f05879354..73b4710ffb9552da22dbb64e51e609f594626266 100644 --- a/src/proto_alpha/lib_client/client_proto_programs.ml +++ b/src/proto_alpha/lib_client/client_proto_programs.ml @@ -388,3 +388,7 @@ let print_unreachables (cctxt : #Client_context.printer) ~emacs ?script_name ?script_name ~on_errors:(print_errors cctxt ~show_source ~parsed) ty + +let get_event_address cctxt ~chain ~block ~ty = + Plugin.RPC.Scripts.get_event_address cctxt (chain, block) ~ty + >>=? fun address -> return address diff --git a/src/proto_alpha/lib_client/client_proto_programs.mli b/src/proto_alpha/lib_client/client_proto_programs.mli index dd6f3ff3353079e3de0ce70b6995418950c8a0b6..f1a783c79c15772a8c95248a8b15143afef87bae 100644 --- a/src/proto_alpha/lib_client/client_proto_programs.mli +++ b/src/proto_alpha/lib_client/client_proto_programs.mli @@ -224,3 +224,12 @@ val print_unreachables : parsed:Michelson_v1_parser.parsed -> Michelson_v1_primitives.prim list list tzresult -> unit tzresult Lwt.t + +(** A service implementation to compute the event address + for a given event tag and a Michelson event type definition *) +val get_event_address : + #Protocol_client_context.rpc_context -> + chain:Chain_services.chain -> + block:Block_services.block -> + ty:Alpha_context.Script.expr -> + Alpha_context.Contract_event.t tzresult Lwt.t diff --git a/src/proto_alpha/lib_client/injection.ml b/src/proto_alpha/lib_client/injection.ml index d0a1fd86f59f4395d11a27e73df3ba24fc10a214..7942dbfacb5d3e3ad4143ed10408c88a1a97690b 100644 --- a/src/proto_alpha/lib_client/injection.ml +++ b/src/proto_alpha/lib_client/injection.ml @@ -310,7 +310,8 @@ let estimated_gas_single (type kind) | Transaction_result ( Transaction_to_contract_result {consumed_gas; _} | Transaction_to_tx_rollup_result {consumed_gas; _} - | Transaction_to_sc_rollup_result {consumed_gas; _} ) -> + | Transaction_to_sc_rollup_result {consumed_gas; _} + | Transaction_to_event_result {consumed_gas; _} ) -> Ok consumed_gas | Origination_result {consumed_gas; _} -> Ok consumed_gas | Reveal_result {consumed_gas} -> Ok consumed_gas @@ -350,7 +351,8 @@ let estimated_gas_single (type kind) | ITransaction_result ( Transaction_to_contract_result {consumed_gas; _} | Transaction_to_tx_rollup_result {consumed_gas; _} - | Transaction_to_sc_rollup_result {consumed_gas; _} ) -> + | Transaction_to_sc_rollup_result {consumed_gas; _} + | Transaction_to_event_result {consumed_gas; _} ) -> Ok consumed_gas | IOrigination_result {consumed_gas; _} -> Ok consumed_gas | IDelegation_result {consumed_gas} -> Ok consumed_gas) @@ -385,7 +387,10 @@ let estimated_storage_single (type kind) ~tx_rollup_origination_size We need to charge for newly allocated storage (as we do for Michelson’s big map). *) Ok Z.zero - | Transaction_result (Transaction_to_sc_rollup_result _) -> Ok Z.zero + | Transaction_result + (Transaction_to_sc_rollup_result _ | Transaction_to_event_result _) + -> + Ok Z.zero | Origination_result {paid_storage_size_diff; _} -> Ok (Z.add paid_storage_size_diff origination_size) | Reveal_result _ -> Ok Z.zero @@ -441,7 +446,10 @@ let estimated_storage_single (type kind) ~tx_rollup_origination_size We need to charge for newly allocated storage (as we do for Michelson’s big map). *) Ok Z.zero - | ITransaction_result (Transaction_to_sc_rollup_result _) -> Ok Z.zero + | ITransaction_result + (Transaction_to_sc_rollup_result _ | Transaction_to_event_result _) + -> + Ok Z.zero | IOrigination_result {paid_storage_size_diff; _} -> Ok (Z.add paid_storage_size_diff origination_size) | IDelegation_result _ -> Ok Z.zero) @@ -488,7 +496,8 @@ let originated_contracts_single (type kind) Ok originated_contracts | Transaction_result ( Transaction_to_tx_rollup_result _ - | Transaction_to_sc_rollup_result _ ) -> + | Transaction_to_sc_rollup_result _ | Transaction_to_event_result _ + ) -> Ok [] | Origination_result {originated_contracts; _} -> Ok originated_contracts @@ -527,7 +536,8 @@ let originated_contracts_single (type kind) Ok originated_contracts | ITransaction_result ( Transaction_to_tx_rollup_result _ - | Transaction_to_sc_rollup_result _ ) -> + | Transaction_to_sc_rollup_result _ | Transaction_to_event_result _ + ) -> Ok [] | IOrigination_result {originated_contracts; _} -> Ok originated_contracts diff --git a/src/proto_alpha/lib_client/operation_result.ml b/src/proto_alpha/lib_client/operation_result.ml index 30404d93707b1685a5f942a95280298fee8d4c03..d156bd67bc800eb5563654104a4e2d1885d07ace 100644 --- a/src/proto_alpha/lib_client/operation_result.ml +++ b/src/proto_alpha/lib_client/operation_result.ml @@ -533,6 +533,9 @@ let pp_transaction_result ppf = function | Transaction_to_sc_rollup_result {consumed_gas; inbox_after} -> pp_consumed_gas ppf consumed_gas ; pp_inbox_after ppf inbox_after + | Transaction_to_event_result {consumed_gas} -> + pp_consumed_gas ppf consumed_gas ; + Format.fprintf ppf "@,@[Event Applied]" let pp_operation_result ~operation_name pp_operation_result ppf = function | Skipped _ -> Format.fprintf ppf "This operation was skipped." diff --git a/src/proto_alpha/lib_client_commands/client_proto_programs_commands.ml b/src/proto_alpha/lib_client_commands/client_proto_programs_commands.ml index d82c6be1bc6c8e9a372a055057149908d1129358..1f12d3a964e87cf04e465c71c27b0ed2ee75b5c5 100644 --- a/src/proto_alpha/lib_client_commands/client_proto_programs_commands.ml +++ b/src/proto_alpha/lib_client_commands/client_proto_programs_commands.ml @@ -1049,4 +1049,23 @@ let commands () = unlimited_gas; } >>= fun res -> print_view_result cctxt res); + command + ~group + ~desc:"Compute the event address associated with a tag and a data type." + no_options + (prefixes ["get"; "event"; "address"] + @@ param ~name:"type" ~desc:"the type of the event data" data_parameter + @@ stop) + (fun () ty cctxt -> + Client_proto_programs.get_event_address + cctxt + ~chain:cctxt#chain + ~block:cctxt#block + ~ty:ty.expanded + >>=? fun addr -> + cctxt#message + "@[Event address: @,%a@]@." + Contract_event_repr.pp + addr + >|= ok); ] diff --git a/src/proto_alpha/lib_plugin/RPC.ml b/src/proto_alpha/lib_plugin/RPC.ml index 86144e9985b5f9cee56b6ea14c9860b97e7361d8..f45fb752462d11914f03109bbb94eaf5b26aba52 100644 --- a/src/proto_alpha/lib_plugin/RPC.ml +++ b/src/proto_alpha/lib_plugin/RPC.ml @@ -459,6 +459,16 @@ module Scripts = struct []) (req "entrypoints" (assoc Script.expr_encoding))) RPC_path.(path / "entrypoints") + + (** [get_event_address] is a RPC service to compute the contract event address + for the input tag and Michelson event type definition. *) + let get_event_address = + RPC_service.post_service + ~description:"Return the event address for the given tag and data type" + ~input:(obj1 (req "type" Script.expr_encoding)) + ~output:(obj1 (req "address" Contract_event.Hash.encoding)) + ~query:RPC_query.empty + RPC_path.(path / "event_address") end module type UNPARSING_MODE = sig @@ -805,6 +815,7 @@ module Scripts = struct | ISplit_ticket _ -> pp_print_string fmt "SPLIT_TICKET" | IJoin_tickets _ -> pp_print_string fmt "JOIN_TICKETS" | IOpen_chest _ -> pp_print_string fmt "OPEN_CHEST" + | IEmit _ -> pp_print_string fmt "EMIT" | IHalt _ -> pp_print_string fmt "[halt]" | ILog (_, _, _, _, instr) -> Format.fprintf fmt "log/%a" pp_instr_name instr @@ -1492,7 +1503,14 @@ module Scripts = struct Micheline.strip_locations original_type_expr ) :: acc) map - [] ) )) + [] ) )) ; + Registration.register0 + ~chunked:false + S.get_event_address + (fun ctxt () ty_node -> + let ctxt = Gas.set_unlimited ctxt in + Script_ir_translator.hash_event_ty ctxt (Micheline.root ty_node) + >>?= fun (address, _) -> return address) let run_code ?unparsing_mode ?gas ?(entrypoint = Entrypoint.default) ?balance ~script ~storage ~input ~amount ~chain_id ~source ~payer ~self ~now ~level @@ -1633,6 +1651,10 @@ module Scripts = struct let list_entrypoints ctxt block ~script = RPC_context.make_call0 S.list_entrypoints ctxt block () script + + (** [get_event_address] makes a call to the service to compute an event address *) + let get_event_address ~ty ctxt block = + RPC_context.make_call0 S.get_event_address ctxt block () ty end module Contract = struct diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index f7e3ec286ce442057e2e5490e9f3248bf51969b0..76c88eff3a739b62579247397830cfee1bc7567d 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -58,6 +58,7 @@ "Sc_rollup_commitment_repr", "Sc_rollup_proof_repr", "Sc_rollup_game_repr", + "Contract_event_repr", "Tx_rollup_level_repr", "Tx_rollup_l2_proof", "Tx_rollup_l2_address", diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 4b2d226b898c7ad33cb210c7de64be8dc17eb1d4..40aeb1fdd6345dfa731c6fdaa5bdd23bdff44fa5 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -251,6 +251,8 @@ module Script = struct Gas.consume_from available_gas gas_cost end +module Contract_event = Contract_event_repr + module Level = struct include Level_repr include Level_storage diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 76c8c35068bcc57087ef62b8850bf1ef2fb1d5ca..2047d1f6da02e5b63d14a27e06d2da358f389e49 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -634,6 +634,7 @@ module Script : sig | I_SPLIT_TICKET | I_JOIN_TICKETS | I_OPEN_CHEST + | I_EMIT | T_bool | T_contract | T_int @@ -2291,6 +2292,19 @@ module Bond_id : sig end end +(** Contract_event exposes fields for event data access. See [Contract_event_repr]. *) +module Contract_event : sig + module Hash : module type of Contract_event_repr.Hash + + type t = Hash.t + + val in_memory_size : t -> Cache_memory_helpers.sint + + val to_b58check : t -> string + + val pp : Format.formatter -> t -> unit +end + (** This module re-exports definitions from {!Receipt_repr}. *) module Receipt : sig type balance = @@ -3179,6 +3193,7 @@ module Destination : sig | Contract of Contract.t | Tx_rollup of Tx_rollup.t | Sc_rollup of Sc_rollup.t + | Event of Contract_event.t val encoding : t Data_encoding.t diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index 5c8f1820772236ae22c3ac1be124f50c6016e6f6..5478fc5d53b6d5f8dbe3eb4a451213984cbb6f2e 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -1188,6 +1188,13 @@ let apply_internal_manager_operation_content : Transaction_to_sc_rollup_result {consumed_gas; inbox_after} in (ctxt, ITransaction_result result, []) + | Transaction_to_event {addr = _; unparsed_data = _; tag = _} -> + return + ( ctxt, + ITransaction_result + (Transaction_to_event_result + {consumed_gas = Gas.consumed ~since:ctxt_before_op ~until:ctxt}), + [] ) | Origination { delegate; @@ -1954,6 +1961,8 @@ let burn_transaction_storage_fees ctxt trr ~storage_limit ~payer = storage_limit, Transaction_to_tx_rollup_result {payload with balance_updates} ) | Transaction_to_sc_rollup_result _ -> return (ctxt, storage_limit, trr) + | Transaction_to_event_result {consumed_gas} -> + return (ctxt, storage_limit, Transaction_to_event_result {consumed_gas}) let burn_origination_storage_fees ctxt { diff --git a/src/proto_alpha/lib_protocol/apply_internal_results.ml b/src/proto_alpha/lib_protocol/apply_internal_results.ml index cd2e0d6b182d781436700a581bfe8751a0c15bd2..2a4ddcf7d2e876ac192044257b5478f12d670d28 100644 --- a/src/proto_alpha/lib_protocol/apply_internal_results.ml +++ b/src/proto_alpha/lib_protocol/apply_internal_results.ml @@ -91,6 +91,14 @@ let contents_of_internal_operation (type kind) entrypoint; parameters = Script.lazy_expr unparsed_parameters; } + | Transaction_to_event {addr; tag; unparsed_data} -> + Transaction + { + destination = Event addr; + amount = Tez.zero; + entrypoint = tag; + parameters = Script.lazy_expr unparsed_data; + } | Origination {delegate; code; unparsed_storage; credit; _} -> let script = { @@ -131,6 +139,7 @@ type successful_transaction_result = consumed_gas : Gas.Arith.fp; inbox_after : Sc_rollup.Inbox.t; } + | Transaction_to_event_result of {consumed_gas : Gas.Arith.fp} type successful_origination_result = { lazy_storage_diff : Lazy_storage.diffs option; @@ -300,6 +309,15 @@ module Internal_result = struct (function | consumed_gas, inbox_after -> Transaction_to_sc_rollup_result {consumed_gas; inbox_after}); + case + ~title:"To_event" + (Tag 3) + (obj1 + (dft "consumed_milligas" Gas.Arith.n_fp_encoding Gas.Arith.zero)) + (function + | Transaction_to_event_result {consumed_gas} -> Some consumed_gas + | _ -> None) + (fun consumed_gas -> Transaction_to_event_result {consumed_gas}); ] let[@coq_axiom_with_reason "gadt"] transaction_case = diff --git a/src/proto_alpha/lib_protocol/apply_internal_results.mli b/src/proto_alpha/lib_protocol/apply_internal_results.mli index 470a5ce207eaabd3cc17f863bb6df76f0d0554e6..2404113297bffcd3ad92b977b79f2b85880055c2 100644 --- a/src/proto_alpha/lib_protocol/apply_internal_results.mli +++ b/src/proto_alpha/lib_protocol/apply_internal_results.mli @@ -86,6 +86,7 @@ type successful_transaction_result = consumed_gas : Gas.Arith.fp; inbox_after : Sc_rollup.Inbox.t; } + | Transaction_to_event_result of {consumed_gas : Gas.Arith.fp} (** Result of applying an internal origination. *) type successful_origination_result = { diff --git a/src/proto_alpha/lib_protocol/apply_results.ml b/src/proto_alpha/lib_protocol/apply_results.ml index bb7fc7e85ca87e733572cacd90050bb594610b18..42e4072b1e068e50859c3fb1b74f6b4d0ea33670 100644 --- a/src/proto_alpha/lib_protocol/apply_results.ml +++ b/src/proto_alpha/lib_protocol/apply_results.ml @@ -383,6 +383,15 @@ module Manager_result = struct (function | consumed_gas, inbox_after -> Transaction_to_sc_rollup_result {consumed_gas; inbox_after}); + case + ~title:"To_event" + (Tag 3) + (obj1 + (dft "consumed_milligas" Gas.Arith.n_fp_encoding Gas.Arith.zero)) + (function + | Transaction_to_event_result {consumed_gas} -> Some consumed_gas + | _ -> None) + (fun consumed_gas -> Transaction_to_event_result {consumed_gas}); ] let[@coq_axiom_with_reason "gadt"] transaction_case = diff --git a/src/proto_alpha/lib_protocol/contract_event_repr.ml b/src/proto_alpha/lib_protocol/contract_event_repr.ml new file mode 100644 index 0000000000000000000000000000000000000000..639f4c01ede04d5abbb06f9a637c629a737e62e0 --- /dev/null +++ b/src/proto_alpha/lib_protocol/contract_event_repr.ml @@ -0,0 +1,68 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Marigold *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +type error += (* `Permanent *) Invalid_event_notation of string + +module Hash = struct + let prefix = "\058\017\082" (* "ev1" (32) *) + + module H = + Blake2B.Make + (Base58) + (struct + let name = "Event" + + let title = "Event sink" + + let b58check_prefix = prefix + + let size = None + end) + + include H + + let () = Base58.check_encoded_prefix b58check_encoding "ev1" 53 + + include Path_encoding.Make_hex (H) +end + +type t = Hash.t + +let of_b58data = function Hash.Data hash -> Some hash | _ -> None + +let pp = Hash.pp + +let of_b58check_opt s = Option.bind (Base58.decode s) of_b58data + +let of_b58check s = + match of_b58check_opt s with + | Some hash -> ok hash + | None -> error (Invalid_event_notation s) + +let to_b58check hash = Hash.to_b58check hash + +let in_memory_size _ = + let open Cache_memory_helpers in + h1w +! string_size_gen Hash.size diff --git a/src/proto_alpha/lib_protocol/contract_event_repr.mli b/src/proto_alpha/lib_protocol/contract_event_repr.mli new file mode 100644 index 0000000000000000000000000000000000000000..adeeff40a82955255a174499f6966cbabe23f2d0 --- /dev/null +++ b/src/proto_alpha/lib_protocol/contract_event_repr.mli @@ -0,0 +1,54 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Marigold *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +module Hash : sig + val prefix : string + + include S.HASH +end + +type t = Hash.t + +(** [in_memory_size event_addr] returns the number of bytes [event_addr] + uses in RAM. *) +val in_memory_size : t -> Cache_memory_helpers.sint + +(** [to_b58check addr] converts the event address [addr] to the Base58Check string representation *) +val to_b58check : t -> string + +(** Pretty printer for contract events *) +val pp : Format.formatter -> t -> unit + +(** [of_b58data data] tries to decode a contract event from a Base58 [data] and + return [None] if conversion fails *) +val of_b58data : Base58.data -> t option + +(** [of_b58check addr] tries to decode a contract event from a Base58Check string [addr] *) +val of_b58check : string -> t tzresult + +(** [of_b58check_opt addr] tries to + decode a contract event from a Base58Check string [addr] + and return [None] if conversion fails *) +val of_b58check_opt : string -> t option diff --git a/src/proto_alpha/lib_protocol/destination_repr.ml b/src/proto_alpha/lib_protocol/destination_repr.ml index 638fcfe83b3c573ee1ea084341de4a0a4cf835f1..93cb5c141a477033d13f6fce73e1c94ef7d261df 100644 --- a/src/proto_alpha/lib_protocol/destination_repr.ml +++ b/src/proto_alpha/lib_protocol/destination_repr.ml @@ -29,6 +29,7 @@ type t = | Contract of Contract_repr.t | Tx_rollup of Tx_rollup_repr.t | Sc_rollup of Sc_rollup_repr.t + | Event of Contract_event_repr.t (* If you add more cases to this type, please update the [test_compare_destination] test in [test/unit/test_destination_repr.ml] to ensure that the compare @@ -43,6 +44,7 @@ include Compare.Make (struct | Contract k1, Contract k2 -> Contract_repr.compare k1 k2 | Tx_rollup k1, Tx_rollup k2 -> Tx_rollup_repr.compare k1 k2 | Sc_rollup k1, Sc_rollup k2 -> Sc_rollup_repr.Address.compare k1 k2 + | Event k1, Event k2 -> Contract_event_repr.Hash.compare k1 k2 (* This function is used by the Michelson interpreter to compare addresses. It is of significant importance to remember that in Michelson, address comparison is used to distinguish between @@ -52,14 +54,17 @@ include Compare.Make (struct modified when new constructors are added to [t]. *) | Contract _, _ -> -1 | _, Contract _ -> 1 - | Tx_rollup _, Sc_rollup _ -> -1 - | Sc_rollup _, Tx_rollup _ -> 1 + | Tx_rollup _, _ -> -1 + | _, Tx_rollup _ -> 1 + | Sc_rollup _, _ -> -1 + | _, Sc_rollup _ -> 1 end) let to_b58check = function | Contract k -> Contract_repr.to_b58check k | Tx_rollup k -> Tx_rollup_repr.to_b58check k | Sc_rollup k -> Sc_rollup_repr.Address.to_b58check k + | Event k -> Contract_event_repr.to_b58check k type error += Invalid_destination_b58check of string @@ -81,9 +86,12 @@ let of_b58data data = | None -> ( match Tx_rollup_repr.of_b58data data with | Some tx_rollup -> Some (Tx_rollup tx_rollup) - | None -> - Sc_rollup_repr.Address.of_b58data data - |> Option.map (fun sc_rollup -> Sc_rollup sc_rollup)) + | None -> ( + match Sc_rollup_repr.Address.of_b58data data with + | Some sc_rollup -> Some (Sc_rollup sc_rollup) + | None -> + Contract_event_repr.of_b58data data + |> Option.map (fun c -> Event c))) let of_b58check_opt s = Option.bind (Base58.decode s) of_b58data @@ -122,6 +130,12 @@ let encoding = ~title:"Sc_rollup" (function Sc_rollup k -> Some k | _ -> None) (fun k -> Sc_rollup k); + case + (Tag 4) + (Fixed.add_padding Contract_event_repr.Hash.encoding 1) + ~title:"Event sink" + (function Event k -> Some k | _ -> None) + (fun k -> Event k); ])) ~json: (conv @@ -139,6 +153,7 @@ let pp : Format.formatter -> t -> unit = | Contract k -> Contract_repr.pp fmt k | Tx_rollup k -> Tx_rollup_repr.pp fmt k | Sc_rollup k -> Sc_rollup_repr.pp fmt k + | Event k -> Contract_event_repr.pp fmt k let in_memory_size = let open Cache_memory_helpers in @@ -146,3 +161,4 @@ let in_memory_size = | Contract k -> h1w +! Contract_repr.in_memory_size k | Tx_rollup k -> h1w +! Tx_rollup_repr.in_memory_size k | Sc_rollup k -> h1w +! Sc_rollup_repr.in_memory_size k + | Event k -> h1w +! Contract_event_repr.in_memory_size k diff --git a/src/proto_alpha/lib_protocol/destination_repr.mli b/src/proto_alpha/lib_protocol/destination_repr.mli index 2b0c3e57f0c58d1cc48db08b5d0718367c28badc..e884a16f6a0dcdc1dac9735fa61e8dc2a54a1d42 100644 --- a/src/proto_alpha/lib_protocol/destination_repr.mli +++ b/src/proto_alpha/lib_protocol/destination_repr.mli @@ -45,6 +45,7 @@ type t = | Contract of Contract_repr.t | Tx_rollup of Tx_rollup_repr.t | Sc_rollup of Sc_rollup_repr.t + | Event of Contract_event_repr.t include Compare.S with type t := t diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index d59b58f04d031589509965603ff79ff5b236f0ff..4894fb33af17c523972ca7417264aa24c10b5782 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -87,6 +87,7 @@ Sc_rollup_commitment_repr Sc_rollup_proof_repr Sc_rollup_game_repr + Contract_event_repr Tx_rollup_level_repr Tx_rollup_l2_proof Tx_rollup_l2_address @@ -312,6 +313,7 @@ sc_rollup_commitment_repr.ml sc_rollup_commitment_repr.mli sc_rollup_proof_repr.ml sc_rollup_proof_repr.mli sc_rollup_game_repr.ml sc_rollup_game_repr.mli + contract_event_repr.ml contract_event_repr.mli tx_rollup_level_repr.ml tx_rollup_level_repr.mli tx_rollup_l2_proof.ml tx_rollup_l2_proof.mli tx_rollup_l2_address.ml tx_rollup_l2_address.mli @@ -524,6 +526,7 @@ sc_rollup_commitment_repr.ml sc_rollup_commitment_repr.mli sc_rollup_proof_repr.ml sc_rollup_proof_repr.mli sc_rollup_game_repr.ml sc_rollup_game_repr.mli + contract_event_repr.ml contract_event_repr.mli tx_rollup_level_repr.ml tx_rollup_level_repr.mli tx_rollup_l2_proof.ml tx_rollup_l2_proof.mli tx_rollup_l2_address.ml tx_rollup_l2_address.mli @@ -745,6 +748,7 @@ sc_rollup_commitment_repr.ml sc_rollup_commitment_repr.mli sc_rollup_proof_repr.ml sc_rollup_proof_repr.mli sc_rollup_game_repr.ml sc_rollup_game_repr.mli + contract_event_repr.ml contract_event_repr.mli tx_rollup_level_repr.ml tx_rollup_level_repr.mli tx_rollup_l2_proof.ml tx_rollup_l2_proof.mli tx_rollup_l2_address.ml tx_rollup_l2_address.mli @@ -970,6 +974,7 @@ sc_rollup_commitment_repr.ml sc_rollup_commitment_repr.mli sc_rollup_proof_repr.ml sc_rollup_proof_repr.mli sc_rollup_game_repr.ml sc_rollup_game_repr.mli + contract_event_repr.ml contract_event_repr.mli tx_rollup_level_repr.ml tx_rollup_level_repr.mli tx_rollup_l2_proof.ml tx_rollup_l2_proof.mli tx_rollup_l2_address.ml tx_rollup_l2_address.mli diff --git a/src/proto_alpha/lib_protocol/michelson_v1_gas.ml b/src/proto_alpha/lib_protocol/michelson_v1_gas.ml index 39f89d88f157c7f33da62fbd0127fcce6a0465cb..497072da66705b89a5e00a69f30101c6cda62e2a 100644 --- a/src/proto_alpha/lib_protocol/michelson_v1_gas.ml +++ b/src/proto_alpha/lib_protocol/michelson_v1_gas.ml @@ -962,6 +962,9 @@ module Cost_of = struct (* model SAPLING_DIFF_ENCODING *) let cost_SAPLING_DIFF_ENCODING ~nfs ~cms = S.safe_int ((nfs * 22) + (cms * 215)) + + (* model IEmit *) + let cost_N_IEmit = S.safe_int 30 end module Interpreter = struct @@ -1508,6 +1511,8 @@ module Cost_of = struct contents_comparison +@ compare_address +@ add_nat ticket_a.amount ticket_b.amount) + let emit = atomic_step_cost cost_N_IEmit + (* Continuations *) module Control = struct let nil = atomic_step_cost cost_N_KNil diff --git a/src/proto_alpha/lib_protocol/michelson_v1_gas.mli b/src/proto_alpha/lib_protocol/michelson_v1_gas.mli index c87e1a14a64ac4280cec130bb28bec8a28fa0586..b91b3cd478386a857aced7aa37f620b5b143c024 100644 --- a/src/proto_alpha/lib_protocol/michelson_v1_gas.mli +++ b/src/proto_alpha/lib_protocol/michelson_v1_gas.mli @@ -362,6 +362,9 @@ module Cost_of : sig val open_chest : chest:Script_typed_ir.Script_timelock.chest -> time:Z.t -> Gas.cost + (** cost to generate one event emission internal operation *) + val emit : Gas.cost + module Control : sig val nil : Gas.cost diff --git a/src/proto_alpha/lib_protocol/michelson_v1_primitives.ml b/src/proto_alpha/lib_protocol/michelson_v1_primitives.ml index 97464d4f6aee189eb4c926f11445fd62a48348c1..abbad29046c1dae6433d9fa3e6f53aa9006f2fcc 100644 --- a/src/proto_alpha/lib_protocol/michelson_v1_primitives.ml +++ b/src/proto_alpha/lib_protocol/michelson_v1_primitives.ml @@ -151,6 +151,7 @@ type prim = | I_SPLIT_TICKET | I_JOIN_TICKETS | I_OPEN_CHEST + | I_EMIT | T_bool | T_contract | T_int @@ -216,7 +217,8 @@ let namespace = function | I_SENDER | I_SET_DELEGATE | I_SHA256 | I_SHA512 | I_SHA3 | I_SIZE | I_SLICE | I_SOME | I_SOURCE | I_SPLIT_TICKET | I_STEPS_TO_QUOTA | I_SUB | I_SUB_MUTEZ | I_SWAP | I_TICKET | I_TOTAL_VOTING_POWER | I_TRANSFER_TOKENS | I_UNIT - | I_UNPACK | I_UNPAIR | I_UPDATE | I_VOTING_POWER | I_XOR | I_OPEN_CHEST -> + | I_UNPACK | I_UNPAIR | I_UPDATE | I_VOTING_POWER | I_XOR | I_OPEN_CHEST + | I_EMIT -> Instr_namespace | T_address | T_tx_rollup_l2_address | T_big_map | T_bool | T_bytes | T_chain_id | T_contract | T_int | T_key | T_key_hash | T_lambda | T_list @@ -357,6 +359,7 @@ let string_of_prim = function | I_SPLIT_TICKET -> "SPLIT_TICKET" | I_JOIN_TICKETS -> "JOIN_TICKETS" | I_OPEN_CHEST -> "OPEN_CHEST" + | I_EMIT -> "EMIT" | I_VIEW -> "VIEW" | T_bool -> "bool" | T_contract -> "contract" @@ -511,6 +514,7 @@ let prim_of_string = function | "SPLIT_TICKET" -> ok I_SPLIT_TICKET | "JOIN_TICKETS" -> ok I_JOIN_TICKETS | "OPEN_CHEST" -> ok I_OPEN_CHEST + | "EMIT" -> ok I_EMIT | "bool" -> ok T_bool | "contract" -> ok T_contract | "int" -> ok T_int @@ -760,7 +764,10 @@ let prim_encoding = (* Alpha_013 addition *) ("tx_rollup_l2_address", T_tx_rollup_l2_address); ("MIN_BLOCK_TIME", I_MIN_BLOCK_TIME); - ("sapling_transaction", T_sapling_transaction) + ("sapling_transaction", T_sapling_transaction); + (* /!\ NEW INSTRUCTIONS MUST BE ADDED AT THE END OF THE STRING_ENUM, FOR BACKWARD COMPATIBILITY OF THE ENCODING. *) + (* Alpha_014 addition *) + ("EMIT", I_EMIT) (* New instructions must be added here, for backward compatibility of the encoding. *) (* Keep the comment above at the end of the list *); ] diff --git a/src/proto_alpha/lib_protocol/michelson_v1_primitives.mli b/src/proto_alpha/lib_protocol/michelson_v1_primitives.mli index 45f7ae7b0f94400061f5845fa4941236f77b1361..6d14592c1f7a0778f128aa89f24bea05a9bf76b7 100644 --- a/src/proto_alpha/lib_protocol/michelson_v1_primitives.mli +++ b/src/proto_alpha/lib_protocol/michelson_v1_primitives.mli @@ -164,6 +164,7 @@ type prim = | I_SPLIT_TICKET | I_JOIN_TICKETS | I_OPEN_CHEST + | I_EMIT | T_bool | T_contract | T_int diff --git a/src/proto_alpha/lib_protocol/script_interpreter.ml b/src/proto_alpha/lib_protocol/script_interpreter.ml index ec6d0b45437038a17acf3bd009b55ef30914fa17..75f62824c613e58c7cd4d10ea3fa9993bbd8590f 100644 --- a/src/proto_alpha/lib_protocol/script_interpreter.ml +++ b/src/proto_alpha/lib_protocol/script_interpreter.ml @@ -514,7 +514,7 @@ and iview : type a b c d e f i o. (a, b, c, d, e, f, i, o) iview_type = (step [@ocaml.tailcall]) (ctxt, sc) gas k ks None stack in match addr.destination with - | Contract (Implicit _) | Tx_rollup _ | Sc_rollup _ -> + | Contract (Implicit _) | Tx_rollup _ | Sc_rollup _ | Event _ -> (return_none [@ocaml.tailcall]) ctxt | Contract (Originated contract_hash as c) -> ( Contract.get_script ctxt contract_hash >>=? fun (ctxt, script_opt) -> @@ -1531,7 +1531,12 @@ and step : type a s b t r f. (a, s, b, t, r, f) step_type = | Bogus_cipher -> R false | Bogus_opening -> R true) in - (step [@ocaml.tailcall]) g gas k ks accu stack) + (step [@ocaml.tailcall]) g gas k ks accu stack + | IEmit {tag; ty = event_type; k; addr = event_address; loc = _} -> + let event_data = accu in + emit_event (ctxt, sc) gas ~event_address ~event_type ~tag ~event_data + >>=? fun (accu, ctxt, gas) -> + (step [@ocaml.tailcall]) (ctxt, sc) gas k ks accu stack) (* diff --git a/src/proto_alpha/lib_protocol/script_interpreter.mli b/src/proto_alpha/lib_protocol/script_interpreter.mli index 3d25819227559a5f42bb0e6aea6d54b9b41264ac..797a02f17f824d551ded98d018a0e0f2bf4a0f75 100644 --- a/src/proto_alpha/lib_protocol/script_interpreter.mli +++ b/src/proto_alpha/lib_protocol/script_interpreter.mli @@ -50,6 +50,7 @@ type error += Cannot_serialize_storage type error += Michelson_too_many_recursive_calls +(** The result from script interpretation. *) type execution_result = { script : Script_ir_translator.ex_script; code_size : int; diff --git a/src/proto_alpha/lib_protocol/script_interpreter_defs.ml b/src/proto_alpha/lib_protocol/script_interpreter_defs.ml index 3b51e990b1d3a006ebf54c01e0b43f41f5f851a2..15655fe1757f30112d4bf3a1c1f025b4e88f2373 100644 --- a/src/proto_alpha/lib_protocol/script_interpreter_defs.ml +++ b/src/proto_alpha/lib_protocol/script_interpreter_defs.ml @@ -38,7 +38,7 @@ open Script_typed_ir open Script_ir_translator open Local_gas_counter -type error += Rollup_invalid_transaction_amount +type error += Rollup_invalid_transaction_amount | Event_invalid_destination let () = register_error_kind @@ -53,7 +53,22 @@ let () = Format.pp_print_string ppf "Transaction amount to a rollup must be zero.") Data_encoding.unit (function Rollup_invalid_transaction_amount -> Some () | _ -> None) - (fun () -> Rollup_invalid_transaction_amount) + (fun () -> Rollup_invalid_transaction_amount) ; + register_error_kind + `Permanent + ~id:"operation.event_invalid_destination" + ~title:"Event sinks are invalid transaction destinations" + ~description: + "Event sinks are not real transaction destinations, and therefore \ + operations targeting an event sink are invalid. To emit events, use \ + EMIT instead." + ~pp:(fun ppf () -> + Format.pp_print_string + ppf + "Event sinks are invalid transaction destinations.") + Data_encoding.unit + (function Event_invalid_destination -> Some () | _ -> None) + (fun () -> Event_invalid_destination) (* @@ -351,6 +366,7 @@ let cost_of_instr : type a s r f. (a, s, r, f) kinstr -> a -> s -> Gas.cost = | IOpen_chest _ -> let _chest_key = accu and chest, (time, _) = stack in Interp_costs.open_chest ~chest ~time:(Script_int.to_zint time) + | IEmit _ -> Interp_costs.emit | ILog _ -> Gas.free [@@ocaml.inline always] [@@coq_axiom_with_reason "unreachable expression `.` not handled"] @@ -549,6 +565,26 @@ let make_transaction_to_sc_rollup ctxt ~destination ~amount ~entrypoint }, ctxt ) ) +(** [emit_event] generates an internal operation that will effect an event emission + if the contract code returns this successfully. *) +let emit_event (type t tc) (ctxt, sc) gas ~event_address + ~(event_type : (t, tc) ty) ~tag ~(event_data : t) = + let ctxt = update_context gas ctxt in + (* No need to take care of lazy storage as only packable types are allowed *) + let lazy_storage_diff = None in + unparse_data ctxt Optimized event_type event_data + >>=? fun (unparsed_data, ctxt) -> + Gas.consume ctxt (Script.strip_locations_cost unparsed_data) >>?= fun ctxt -> + let unparsed_data = Micheline.strip_locations unparsed_data in + fresh_internal_nonce ctxt >>?= fun (ctxt, nonce) -> + let operation = + Transaction_to_event {addr = event_address; tag; unparsed_data} + in + let iop = {source = Contract.Originated sc.self; operation; nonce} in + let res = {piop = Internal_operation iop; lazy_storage_diff} in + let gas, ctxt = local_gas_counter_and_outdated_context ctxt in + return (res, ctxt, gas) + (* [transfer (ctxt, sc) gas tez parameters_ty parameters destination entrypoint] creates an operation that transfers an amount of [tez] to a destination and an entrypoint instantiated with argument [parameters] of type @@ -593,7 +629,8 @@ let transfer (ctxt, sc) gas amount location parameters_ty parameters ~amount ~entrypoint ~parameters_ty - ~parameters) + ~parameters + | Event _ -> fail Event_invalid_destination) >>=? fun (operation, ctxt) -> fresh_internal_nonce ctxt >>?= fun (ctxt, nonce) -> let iop = {source = Contract.Originated sc.self; operation; nonce} in diff --git a/src/proto_alpha/lib_protocol/script_interpreter_logging.ml b/src/proto_alpha/lib_protocol/script_interpreter_logging.ml index 58a09366227ddd1a8cf39724578221569c181155..1a325f56c5d28c75196244a0b8a68f96c643b443 100644 --- a/src/proto_alpha/lib_protocol/script_interpreter_logging.ml +++ b/src/proto_alpha/lib_protocol/script_interpreter_logging.ml @@ -1586,6 +1586,16 @@ let kinstr_split : continuation = k; reconstruct = (fun k -> IMin_block_time (loc, k)); } + | IEmit {loc; ty; addr; tag; k}, Item_t (_, s) -> + let s = Item_t (operation_t, s) in + ok + @@ Ex_split_kinstr + { + cont_init_stack = s; + continuation = k; + reconstruct = (fun k -> IEmit {loc; ty; addr; tag; k}); + } + | IEmit _, Bot_t -> . | IHalt loc, _s -> ok @@ Ex_split_halt loc | ILog (loc, _stack_ty, event, logger, continuation), stack -> ok diff --git a/src/proto_alpha/lib_protocol/script_ir_translator.ml b/src/proto_alpha/lib_protocol/script_ir_translator.ml index 314297165775ad4f3f3ebed688f0d81477d8a724..15ff0c35128189fe791969f6e98ec0b49c4cd51f 100644 --- a/src/proto_alpha/lib_protocol/script_ir_translator.ml +++ b/src/proto_alpha/lib_protocol/script_ir_translator.ml @@ -593,6 +593,11 @@ let hash_comparable_data ctxt ty data = pack_comparable_data ctxt ty data >>=? fun (bytes, ctxt) -> Lwt.return @@ hash_bytes ctxt bytes +let hash_event_ty ctxt unparsed = + pack_node unparsed ctxt >>? fun (bytes, ctxt) -> + Gas.consume ctxt (Michelson_v1_gas.Cost_of.Interpreter.blake2b bytes) + >|? fun ctxt -> (Contract_event.Hash.hash_bytes [bytes], ctxt) + (* ---- Tickets ------------------------------------------------------------ *) (* @@ -1489,6 +1494,7 @@ let parse_storage_ty : (Ex_ty ty, ctxt)) | _ -> (parse_normal_storage_ty [@tailcall]) ctxt ~stack_depth ~legacy node +(* check_packable: determine if a `ty` is packable into Michelson *) let check_packable ~legacy loc root = let rec check : type t tc. (t, tc) ty -> unit tzresult = function (* /!\ When adding new lazy storage kinds, be sure to return an error. /!\ @@ -2538,7 +2544,7 @@ let[@coq_axiom_with_reason "gadt"] rec parse_data : >>=? fun (({destination; entrypoint = _}, (contents, amount)), ctxt) -> match destination with | Contract ticketer -> return ({ticketer; contents; amount}, ctxt) - | Tx_rollup _ | Sc_rollup _ -> + | Tx_rollup _ | Sc_rollup _ | Event _ -> fail (Unexpected_ticket_owner destination) else traced_fail (Unexpected_forged_value (location expr)) (* Sets *) @@ -4639,6 +4645,14 @@ and[@coq_axiom_with_reason "gadt"] parse_instr : Item_t (Chest_key_t, Item_t (Chest_t, Item_t (Nat_t, rest))) ) -> let instr = {apply = (fun k -> IOpen_chest (loc, k))} in typed ctxt loc instr (Item_t (union_bytes_bool_t, rest)) + | Prim (loc, I_EMIT, [ty_node], annot), Item_t (data, rest) -> + parse_packable_ty ctxt ~stack_depth:(stack_depth + 1) ~legacy ty_node + >>?= fun (Ex_ty ty, ctxt) -> + check_item_ty ctxt ty data loc I_EMIT 1 2 >>?= fun (Eq, ctxt) -> + parse_entrypoint_annot_strict loc annot >>?= fun tag -> + hash_event_ty ctxt ty_node >>?= fun (addr, ctxt) -> + let instr = {apply = (fun k -> IEmit {loc; tag; ty = data; addr; k})} in + typed ctxt loc instr (Item_t (Operation_t, rest)) (* Primitive parsing errors *) | ( Prim ( loc, @@ -4663,7 +4677,7 @@ and[@coq_axiom_with_reason "gadt"] parse_instr : ( loc, (( I_NONE | I_LEFT | I_RIGHT | I_NIL | I_MAP | I_ITER | I_EMPTY_SET | I_LOOP | I_LOOP_LEFT | I_CONTRACT | I_CAST | I_UNPACK - | I_CREATE_CONTRACT ) as name), + | I_CREATE_CONTRACT | I_EMIT ) as name), (([] | _ :: _ :: _) as l), _ ), _ ) -> @@ -4993,6 +5007,7 @@ and[@coq_axiom_with_reason "complex mutually recursive definition"] parse_contra entrypoint_arg >|? fun (entrypoint, arg_ty) -> let address = {destination; entrypoint} in Typed_contract {arg_ty; address} )) + | Event _ -> return (error ctxt (fun _ -> No_such_entrypoint entrypoint)) (* Same as [parse_contract], but does not fail when the contact is missing or if the expected type doesn't match the actual one. In that case None is diff --git a/src/proto_alpha/lib_protocol/script_ir_translator.mli b/src/proto_alpha/lib_protocol/script_ir_translator.mli index 8cd92a8d6a4daec238bc4f7ff2bd98c1d0766b0f..995a6aa199650148a4fe299a333dd8fd2e1fa2c2 100644 --- a/src/proto_alpha/lib_protocol/script_ir_translator.mli +++ b/src/proto_alpha/lib_protocol/script_ir_translator.mli @@ -191,6 +191,7 @@ val parse_comparable_data : Script.node -> ('a * context) tzresult Lwt.t +(* Parsing a Micheline node data into an IR-typed data. *) val parse_data : ?type_logger:type_logger -> context -> @@ -200,6 +201,7 @@ val parse_data : Script.node -> ('a * context) tzresult Lwt.t +(* Unparsing an IR-typed data back into a Micheline node data *) val unparse_data : context -> unparsing_mode -> @@ -425,6 +427,12 @@ val hash_data : 'a -> (Script_expr_hash.t * context) tzresult Lwt.t +(** [hash_event_ty ctxt ty_node] generates a contract event address + from the original Michelson type as specified by + the original node [ty_node] while accounting for the necessary gas *) +val hash_event_ty : + context -> Script.node -> (Contract_event.t * context) tzresult + type lazy_storage_ids val no_lazy_storage_id : lazy_storage_ids diff --git a/src/proto_alpha/lib_protocol/script_tc_errors.ml b/src/proto_alpha/lib_protocol/script_tc_errors.ml index 1f0c39d222b8bda47d16e6cd30e5bca766cbb951..9b2b1edbc5d571a17816254da742ebd4480c8e77 100644 --- a/src/proto_alpha/lib_protocol/script_tc_errors.ml +++ b/src/proto_alpha/lib_protocol/script_tc_errors.ml @@ -80,6 +80,9 @@ type error += Tx_rollup_addresses_disabled of Script.location type error += Sc_rollup_disabled of Script.location +(* Event errors *) +type error += Event_invalid_destination of Script.location + (* Instruction typing errors *) type error += Fail_not_in_tail_position of Script.location diff --git a/src/proto_alpha/lib_protocol/script_tc_errors_registration.ml b/src/proto_alpha/lib_protocol/script_tc_errors_registration.ml index 70b586a528edb42f72d9a28b7fd7e8c357fefed5..9ed007b23a80c42efa3a1d5ebedd2770eea9d711 100644 --- a/src/proto_alpha/lib_protocol/script_tc_errors_registration.ml +++ b/src/proto_alpha/lib_protocol/script_tc_errors_registration.ml @@ -273,6 +273,17 @@ let () = (obj1 (req "location" Script.location_encoding)) (function Sc_rollup_disabled loc -> Some loc | _ -> None) (fun loc -> Sc_rollup_disabled loc) ; + (* Event bad parameter *) + register_error_kind + `Permanent + ~id:"michelson_v1.event_invalid_destination" + ~title:"Event parameter is malformed" + ~description: + "An event address is not a valid destination to transfer any token to. \ + Events should be sent with EMIT instructions." + (obj1 (req "location" Script.location_encoding)) + (function Event_invalid_destination loc -> Some loc | _ -> None) + (fun loc -> Event_invalid_destination loc) ; (* Duplicate entrypoint *) register_error_kind `Permanent diff --git a/src/proto_alpha/lib_protocol/script_typed_ir.ml b/src/proto_alpha/lib_protocol/script_typed_ir.ml index ca2bac4a1ef47403669b181a912ed8c8c51cc024..cc5879aa236bfd5c8992b06f9d66e75cfd742d88 100644 --- a/src/proto_alpha/lib_protocol/script_typed_ir.ml +++ b/src/proto_alpha/lib_protocol/script_typed_ir.ml @@ -1083,6 +1083,14 @@ and ('before_top, 'before, 'result_top, 'result) kinstr = 'r, 'f ) kinstr + | IEmit : { + loc : Script.location; + addr : Contract_event.t; + tag : Entrypoint.t; + ty : ('a, _) ty; + k : (operation, 's, 'r, 'f) kinstr; + } + -> ('a, 's, 'r, 'f) kinstr (* Internal control instructions ----------------------------- @@ -1357,6 +1365,12 @@ and 'kind manager_operation = unparsed_parameters : Script.expr; } -> Kind.transaction manager_operation + | Transaction_to_event : { + addr : Contract_event.t; + tag : Entrypoint.t; + unparsed_data : Script.expr; + } + -> Kind.transaction manager_operation | Origination : { delegate : Signature.Public_key_hash.t option; code : Script.expr; @@ -1413,6 +1427,7 @@ let manager_kind : type kind. kind manager_operation -> kind Kind.manager = | Transaction_to_contract _ -> Kind.Transaction_manager_kind | Transaction_to_tx_rollup _ -> Kind.Transaction_manager_kind | Transaction_to_sc_rollup _ -> Kind.Transaction_manager_kind + | Transaction_to_event _ -> Kind.Transaction_manager_kind | Origination _ -> Kind.Origination_manager_kind | Delegation _ -> Kind.Delegation_manager_kind @@ -1572,9 +1587,10 @@ let kinstr_location : type a s b f. (a, s, b, f) kinstr -> Script.location = | IRead_ticket (loc, _, _) -> loc | ISplit_ticket (loc, _) -> loc | IJoin_tickets (loc, _, _) -> loc + | IOpen_chest (loc, _) -> loc + | IEmit {loc; _} -> loc | IHalt loc -> loc | ILog (loc, _, _, _, _) -> loc - | IOpen_chest (loc, _) -> loc let meta_basic = {size = Type_size.one} @@ -1973,6 +1989,7 @@ let kinstr_traverse i init f = | ISplit_ticket (_, k) -> (next [@ocaml.tailcall]) k | IJoin_tickets (_, _, k) -> (next [@ocaml.tailcall]) k | IOpen_chest (_, k) -> (next [@ocaml.tailcall]) k + | IEmit {k; _} -> (next [@ocaml.tailcall]) k | IHalt _ -> (return [@ocaml.tailcall]) () | ILog (_, _, _, _, k) -> (next [@ocaml.tailcall]) k in diff --git a/src/proto_alpha/lib_protocol/script_typed_ir.mli b/src/proto_alpha/lib_protocol/script_typed_ir.mli index 6a87cd3433730a2383e8995e2e1634cb4ccfd9ac..35eee3e9b30a72ab6d7f8f12ec0d169155bfb143 100644 --- a/src/proto_alpha/lib_protocol/script_typed_ir.mli +++ b/src/proto_alpha/lib_protocol/script_typed_ir.mli @@ -1080,6 +1080,14 @@ and ('before_top, 'before, 'result_top, 'result) kinstr = 'r, 'f ) kinstr + | IEmit : { + loc : Script.location; + addr : Contract_event.t; + tag : Entrypoint.t; + ty : ('a, _) ty; + k : (operation, 's, 'r, 'f) kinstr; + } + -> ('a, 's, 'r, 'f) kinstr (* Internal control instructions @@ -1493,6 +1501,12 @@ and 'kind manager_operation = unparsed_parameters : Script.expr; } -> Kind.transaction manager_operation + | Transaction_to_event : { + addr : Contract_event.t; + tag : Entrypoint.t; + unparsed_data : Script.expr; + } + -> Kind.transaction manager_operation | Origination : { delegate : Signature.Public_key_hash.t option; code : Script.expr; diff --git a/src/proto_alpha/lib_protocol/script_typed_ir_size.ml b/src/proto_alpha/lib_protocol/script_typed_ir_size.ml index 7113b344c145066b0f162c6c0503581495f71003..661ac6846d39e325a134a1e223b54e2ba050de94 100644 --- a/src/proto_alpha/lib_protocol/script_typed_ir_size.ml +++ b/src/proto_alpha/lib_protocol/script_typed_ir_size.ml @@ -582,6 +582,13 @@ and kinstr_size : | IJoin_tickets (_, cty, _) -> ret_succ_adding (accu ++ ty_size cty) (base +! word_size) | IOpen_chest (_, _) -> ret_succ_adding accu base + | IEmit {loc = _; tag; ty; addr = _; k = _} -> + ret_succ_adding + (accu ++ ty_size ty) + (base + +! Entrypoint.in_memory_size tag + +! (word_size *? 3) + +! Saturation_repr.safe_int Contract_event.Hash.size) | IHalt _ -> ret_succ_adding accu h1w | ILog _ -> (* This instruction is ignored because it is only used for testing. *) diff --git a/src/proto_alpha/lib_protocol/test/helpers/block.ml b/src/proto_alpha/lib_protocol/test/helpers/block.ml index 29ea1dc32e7c775ff676366d2292973d49d4b7dd..953b74c3071ddb642f50c6f56cc7f75e817afd64 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/block.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/block.ml @@ -772,7 +772,8 @@ let bake_n_with_all_balance_updates ?(baking_mode = Application) ?policy | Sc_rollup_publish_result _ | Sc_rollup_refute_result _ | Sc_rollup_timeout_result _ | Sc_rollup_execute_outbox_message_result _ - | Sc_rollup_recover_bond_result _ -> + | Sc_rollup_recover_bond_result _ + | Transaction_result (Transaction_to_event_result _) -> balance_updates_rev | Transaction_result ( Transaction_to_contract_result {balance_updates; _} diff --git a/src/proto_alpha/lib_protocol/test/integration/michelson/contracts/emit.tz b/src/proto_alpha/lib_protocol/test/integration/michelson/contracts/emit.tz new file mode 100644 index 0000000000000000000000000000000000000000..e57179214e091e05a204c353de1b67f73e0ed7e6 --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/integration/michelson/contracts/emit.tz @@ -0,0 +1,16 @@ +parameter unit; +storage unit; +code { DROP ; + UNIT ; + PUSH string "right" ; + RIGHT nat ; + EMIT %tag1 (or (nat %int) (string %str)) ; + PUSH nat 2 ; + LEFT string ; + EMIT %tag2 (or (nat %int) (string %str)) ; + NIL operation ; + SWAP ; + CONS ; + SWAP ; + CONS ; + PAIR } diff --git a/src/proto_alpha/lib_protocol/test/integration/michelson/main.ml b/src/proto_alpha/lib_protocol/test/integration/michelson/main.ml index bee2ed0453d1662e7e95b5d28b0194add6ad2d1c..ae8d90161a219b7511644c75b0c46ca1da703f69 100644 --- a/src/proto_alpha/lib_protocol/test/integration/michelson/main.ml +++ b/src/proto_alpha/lib_protocol/test/integration/michelson/main.ml @@ -53,5 +53,6 @@ let () = ("script cache", Test_script_cache.tests); ("block time instructions", Test_block_time_instructions.tests); ("annotations", Test_annotations.tests); + ("event logging", Test_contract_event.tests); ] |> Lwt_main.run diff --git a/src/proto_alpha/lib_protocol/test/integration/michelson/test_contract_event.ml b/src/proto_alpha/lib_protocol/test/integration/michelson/test_contract_event.ml new file mode 100644 index 0000000000000000000000000000000000000000..1823b52adb918d20e64119250f9fbd1ecf57c9d0 --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/integration/michelson/test_contract_event.ml @@ -0,0 +1,160 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Marigold, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context +open Lwt_result_syntax + +(** Testing + ------- + Component: Protocol (event logging) + Invocation: dune exec \ + src/proto_alpha/lib_protocol/test/integration/michelson/main.exe \ + -- test '^event logging$' + Subject: This module tests that the event logs can be written to the receipt + in correct order and expected format. +*) + +let wrap m = m >|= Environment.wrap_tzresult + +(** Parse a Michelson contract from string. *) +let originate_contract file storage src b = + let load_file f = + let ic = open_in f in + let res = really_input_string ic (in_channel_length ic) in + close_in ic ; + res + in + let contract_string = load_file file in + let code = Expr.toplevel_from_string contract_string in + let storage = Expr.from_string storage in + let script = + Alpha_context.Script.{code = lazy_expr code; storage = lazy_expr storage} + in + let* operation, dst = + Op.contract_origination (B b) src ~fee:(Test_tez.of_int 10) ~script + in + let* incr = Incremental.begin_construction b in + let* incr = Incremental.add_operation incr operation in + let+ b = Incremental.finalize_block incr in + (dst, b) + +(** Run emit.tz and assert that both the order of events and data content are correct *) +let contract_test () = + let* b, src = Context.init1 ~consensus_threshold:0 () in + let* dst, b = originate_contract "contracts/emit.tz" "Unit" src b in + let fee = Test_tez.of_int 10 in + let parameters = Script.unit_parameter in + let* operation = + Op.transaction ~fee ~parameters (B b) src dst (Test_tez.of_int 0) + in + let* incr = Incremental.begin_construction b in + let* incr = Incremental.add_operation incr operation in + match Incremental.rev_tickets incr with + | [ + Operation_metadata + { + contents = + Single_result + (Manager_operation_result + { + internal_operation_results = + [ + Internal_manager_operation_result + ( { + operation = + Transaction + { + entrypoint = tag1; + parameters = data1; + amount = amount1; + destination = Destination.Event addr1; + }; + _; + }, + Applied + (ITransaction_result (Transaction_to_event_result _)) + ); + Internal_manager_operation_result + ( { + operation = + Transaction + { + entrypoint = tag2; + parameters = data2; + amount = amount2; + destination = Destination.Event addr2; + }; + _; + }, + Applied + (ITransaction_result (Transaction_to_event_result _)) + ); + ]; + _; + }); + }; + ] -> + let ctxt = Gas.set_unlimited (Incremental.alpha_ctxt incr) in + let* data1, ctxt = + Lwt.return @@ Environment.wrap_tzresult + @@ Script.force_decode_in_context + ~consume_deserialization_gas:When_needed + ctxt + data1 + in + let* data2, _ = + Lwt.return @@ Environment.wrap_tzresult + @@ Script.force_decode_in_context + ~consume_deserialization_gas:When_needed + ctxt + data2 + in + let open Micheline in + ((match root data1 with + | Prim (_, D_Right, [String (_, "right")], _) -> () + | _ -> assert false) ; + + match root data2 with + | Prim (_, D_Left, [Int (_, n)], _) -> assert (Z.to_int n = 2) + | _ -> assert false) ; + let addr1 = Contract_event.to_b58check addr1 in + let addr2 = Contract_event.to_b58check addr2 in + assert (Entrypoint.to_string tag1 = "tag1") ; + assert (Entrypoint.to_string tag2 = "tag2") ; + assert (addr1 = "ev12m5E1yW14mc9rsrcdGAWVfDSdmRGuctykrVU55bHZBGv9kmdhW") ; + assert (addr2 = "ev12m5E1yW14mc9rsrcdGAWVfDSdmRGuctykrVU55bHZBGv9kmdhW") ; + assert (Tez.(amount1 = zero)) ; + assert (Tez.(amount2 = zero)) ; + return_unit + | _ -> assert false + +let tests = + [ + Tztest.tztest + "contract emits event with correct data in proper order" + `Quick + contract_test; + ] diff --git a/src/proto_alpha/lib_protocol/test/unit/test_destination_repr.ml b/src/proto_alpha/lib_protocol/test/unit/test_destination_repr.ml index 6bb7638b99f2388a62d184df835bc627c4933ad4..51d2d1b23c746807634f01b94df671eaf5616d4d 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_destination_repr.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_destination_repr.ml @@ -81,6 +81,9 @@ let tx_rollup_address = "txr1YNMEtkj5Vkqsbdmt7xaxBTMRZjzS96UAi" *) let sc_rollup_address = "scr1HLXM32GacPNDrhHDLAssZG88eWqCUbyLF" +(* The following address has been extracted from [../integration/michelson/test_emit.ml]. *) +let event_address = "ev12m5E1yW14mc9rsrcdGAWVfDSdmRGuctykrVU55bHZBGv9kmdhW" + let assert_compat contract destination = match destination with | Destination_repr.Contract contract' @@ -180,11 +183,13 @@ let test_compare_destination () = let kt1 = !!(Destination_repr.of_b58check liquidity_baking_dex) in let txr1 = !!(Destination_repr.of_b58check tx_rollup_address) in let scr1 = !!(Destination_repr.of_b58check sc_rollup_address) in + let ev1 = !!(Destination_repr.of_b58check event_address) in assert (Destination_repr.(tz1 < kt1)) ; assert (Destination_repr.(kt1 < txr1)) ; assert (Destination_repr.(tz1 < txr1)) ; assert (Destination_repr.(txr1 < scr1)) ; + assert (Destination_repr.(scr1 < ev1)) ; return_unit diff --git a/src/proto_alpha/lib_protocol/ticket_operations_diff.ml b/src/proto_alpha/lib_protocol/ticket_operations_diff.ml index 26a022546c0f79eb6699f6b042eee3b565b16e7e..d965bb84155a86449fa4ce0089acbfc354428319 100644 --- a/src/proto_alpha/lib_protocol/ticket_operations_diff.ml +++ b/src/proto_alpha/lib_protocol/ticket_operations_diff.ml @@ -234,7 +234,7 @@ let tickets_of_operation ctxt ~allow_zero_amount_tickets ~preorigination ~storage_type ~storage - | Delegation _ -> return (None, ctxt) + | Delegation _ | Transaction_to_event _ -> return (None, ctxt) let add_transfer_to_token_map ctxt token_map {destination; tickets} = List.fold_left_es diff --git a/tests_python/contracts_alpha/opcodes/emit.tz b/tests_python/contracts_alpha/opcodes/emit.tz new file mode 100644 index 0000000000000000000000000000000000000000..8d758d903e7c4e251f52c80860e6d6c7435742d1 --- /dev/null +++ b/tests_python/contracts_alpha/opcodes/emit.tz @@ -0,0 +1,16 @@ +{ parameter unit ; + storage unit ; + code { DROP ; + UNIT ; + PUSH nat 10 ; + LEFT string ; + EMIT %event (or (nat %number) (string %words)) ; + PUSH string "lorem ipsum" ; + RIGHT nat ; + EMIT %event (or (nat %number) (string %words)) ; + NIL operation ; + SWAP ; + CONS ; + SWAP ; + CONS ; + PAIR } } \ No newline at end of file diff --git a/tests_python/tests_alpha/_regtest_outputs/test_contract.TestScriptHashRegression::test_contract_hash[client_regtest_custom_scrubber0].out b/tests_python/tests_alpha/_regtest_outputs/test_contract.TestScriptHashRegression::test_contract_hash[client_regtest_custom_scrubber0].out index 4b6d78f0ba4e7d615123c4659fc30613334b94fd..c3202dcb353f1fef84aeb0238a90ed36dc95189a 100644 --- a/tests_python/tests_alpha/_regtest_outputs/test_contract.TestScriptHashRegression::test_contract_hash[client_regtest_custom_scrubber0].out +++ b/tests_python/tests_alpha/_regtest_outputs/test_contract.TestScriptHashRegression::test_contract_hash[client_regtest_custom_scrubber0].out @@ -167,6 +167,7 @@ exprujbvaRkoroj5eVgboUyeP3578oJgScTQ78eBYMgfdLVaGWPPTW [CONTRACT_PATH]/opcodes/d exprur99uFkrwM63FXSTqSTypHcfEmzb5KQ9EypTRqZWDY5NP6tCHL [CONTRACT_PATH]/opcodes/dup-n.tz exprtarW6tiguR7YAo6SxhCUDs1pLDhx9xJEG78h6GnXLAWpt3H1pT [CONTRACT_PATH]/opcodes/ediv.tz exprvQhRaLYxiWN3QsJgT6VKf6DmGVuXR8DxeU63P9A8iNWA4TVQfs [CONTRACT_PATH]/opcodes/ediv_mutez.tz +exprtnW41ZGRWUYs6dHuHJm9W6Hw1CYynpQMWiEt9NxR8mysqK43XT [CONTRACT_PATH]/opcodes/emit.tz exprv8Sy3DsiKMm3ZQPmi46kZGn9ctTigpLB2t6xYFdYkkeVHMXy6z [CONTRACT_PATH]/opcodes/empty_map.tz exprufqf2G8PoZN768K2YGex6M7zmz7bYHE5LF5QHJBVvFtAFLi6qr [CONTRACT_PATH]/opcodes/exec_concat.tz exprut53jocMPdPP8FXrKRDYSoRaxk1FXqCt7o46ak2wQaocjsSwwx [CONTRACT_PATH]/opcodes/first.tz diff --git a/tests_python/tests_alpha/_regtest_outputs/test_contract.TestTypecheck::test_typecheck[opcodes--emit.tz].out b/tests_python/tests_alpha/_regtest_outputs/test_contract.TestTypecheck::test_typecheck[opcodes--emit.tz].out new file mode 100644 index 0000000000000000000000000000000000000000..06520e911c7ba97ecd1b7b8bec24cf538fedf162 --- /dev/null +++ b/tests_python/tests_alpha/_regtest_outputs/test_contract.TestTypecheck::test_typecheck[opcodes--emit.tz].out @@ -0,0 +1,34 @@ +tests_alpha/test_contract.py::TestTypecheck::test_typecheck[opcodes/emit.tz] + +Well typed +Gas remaining: 1039987.674 units remaining +{ parameter unit ; + storage unit ; + code { DROP + /* [] */ ; + UNIT + /* [ unit ] */ ; + PUSH nat 10 + /* [ nat : unit ] */ ; + LEFT string + /* [ or nat string : unit ] */ ; + EMIT %event (or (nat %number) (string %words)) + /* [ operation : unit ] */ ; + PUSH string "lorem ipsum" + /* [ string : operation : unit ] */ ; + RIGHT nat + /* [ or nat string : operation : unit ] */ ; + EMIT %event (or (nat %number) (string %words)) + /* [ operation : operation : unit ] */ ; + NIL operation + /* [ list operation : operation : operation : unit ] */ ; + SWAP + /* [ operation : list operation : operation : unit ] */ ; + CONS + /* [ list operation : operation : unit ] */ ; + SWAP + /* [ operation : list operation : unit ] */ ; + CONS + /* [ list operation : unit ] */ ; + PAIR + /* [ pair (list operation) unit ] */ } } diff --git a/tests_python/tests_alpha/_regtest_outputs/test_contract_opcodes.TestContractOpcodes::test_contract_input_output[emit.tz-Unit-Unit-Unit].out b/tests_python/tests_alpha/_regtest_outputs/test_contract_opcodes.TestContractOpcodes::test_contract_input_output[emit.tz-Unit-Unit-Unit].out new file mode 100644 index 0000000000000000000000000000000000000000..3497655daa1ac26988a342bbfde2632b4db1f33c --- /dev/null +++ b/tests_python/tests_alpha/_regtest_outputs/test_contract_opcodes.TestContractOpcodes::test_contract_input_output[emit.tz-Unit-Unit-Unit].out @@ -0,0 +1,74 @@ +tests_alpha/test_contract_opcodes.py::TestContractOpcodes::test_contract_input_output[emit.tz-Unit-Unit-Unit] + +storage + Unit +emitted operations + Internal Transaction: + Amount: ꜩ0 + From: KT1BEqzn5Wx8uJrZNvuS9DVHmLvG9td3fDLi + To: ev13o2yWTf9YocjZXWyyHhuTDzoeDuKKe2RmKp9sBnPFeywtpdFsp + Entrypoint: event + Parameter: (Left 10) + Internal Transaction: + Amount: ꜩ0 + From: KT1BEqzn5Wx8uJrZNvuS9DVHmLvG9td3fDLi + To: ev13o2yWTf9YocjZXWyyHhuTDzoeDuKKe2RmKp9sBnPFeywtpdFsp + Entrypoint: event + Parameter: (Right "lorem ipsum") +big_map diff + +trace + - location: 7 (remaining gas: 1039983.624 units remaining) + [ (Pair Unit Unit) ] + - location: 7 (remaining gas: 1039983.614 units remaining) + [ ] + - location: 8 (remaining gas: 1039983.604 units remaining) + [ Unit ] + - location: 9 (remaining gas: 1039983.594 units remaining) + [ 10 + Unit ] + - location: 12 (remaining gas: 1039983.584 units remaining) + [ (Left 10) + Unit ] + - location: 14 (remaining gas: 1039983.362 units remaining) + [ 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + Unit ] + - location: 18 (remaining gas: 1039983.352 units remaining) + [ "lorem ipsum" + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + Unit ] + - location: 21 (remaining gas: 1039983.342 units remaining) + [ (Right "lorem ipsum") + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + Unit ] + - location: 23 (remaining gas: 1039983.120 units remaining) + [ 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000001010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000120508010000000b6c6f72656d20697073756d + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + Unit ] + - location: 27 (remaining gas: 1039983.110 units remaining) + [ {} + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000001010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000120508010000000b6c6f72656d20697073756d + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + Unit ] + - location: 29 (remaining gas: 1039983.100 units remaining) + [ 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000001010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000120508010000000b6c6f72656d20697073756d + {} + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + Unit ] + - location: 30 (remaining gas: 1039983.090 units remaining) + [ { 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000001010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000120508010000000b6c6f72656d20697073756d } + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + Unit ] + - location: 31 (remaining gas: 1039983.080 units remaining) + [ 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a + { 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000001010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000120508010000000b6c6f72656d20697073756d } + Unit ] + - location: 32 (remaining gas: 1039983.070 units remaining) + [ { 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a ; + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000001010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000120508010000000b6c6f72656d20697073756d } + Unit ] + - location: 33 (remaining gas: 1039983.060 units remaining) + [ (Pair { 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000000010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000040505000a ; + 0x011d23c1d3d2f8a4ea5e8784b8f7ecf2ad304c0fe6000001010004ac785bfba0f68d61a64f5baa2d45550f6494440a2831f1387df36f41dd770fdd00ffff056576656e74000000120508010000000b6c6f72656d20697073756d } + Unit) ] + diff --git a/tests_python/tests_alpha/test_contract_opcodes.py b/tests_python/tests_alpha/test_contract_opcodes.py index 11dfb9167d3f216785e7785c0a2a5126ef95a663..fb1254e60712319648c936778eba64103879c42b 100644 --- a/tests_python/tests_alpha/test_contract_opcodes.py +++ b/tests_python/tests_alpha/test_contract_opcodes.py @@ -46,6 +46,7 @@ class TestContractOpcodes: '{ 1 ; 2 ; 3 ; 0 }', '{ 1 ; 3 ; 5 ; 3 }', ), + ('emit.tz', 'Unit', 'Unit', 'Unit'), # Reverse a list ('reverse.tz', '{""}', '{}', '{}'), ( diff --git a/tezt/lib_tezos/RPC_legacy.ml b/tezt/lib_tezos/RPC_legacy.ml index 50b40eb92ee360b35539000e2505d8d4df1bf539..6a3671465c3039ab9c41a73475161a410100271e 100644 --- a/tezt/lib_tezos/RPC_legacy.ml +++ b/tezt/lib_tezos/RPC_legacy.ml @@ -217,6 +217,13 @@ let post_simulate_operation ?endpoint ?hooks ?(chain = "main") ?(block = "head") in Client.rpc ?endpoint ?hooks ~data POST path client +let post_compute_event_address ?endpoint ?hooks ?(chain = "main") + ?(block = "head") ~data client = + let path = + ["chains"; chain; "blocks"; block; "helpers"; "scripts"; "event_address"] + in + Client.rpc ?endpoint ?hooks ~data POST path client + module Big_maps = struct let get ?endpoint ?hooks ?(chain = "main") ?(block = "head") ~id ~key_hash client = diff --git a/tezt/lib_tezos/RPC_legacy.mli b/tezt/lib_tezos/RPC_legacy.mli index 3da2ecb5b11a7cebd5fe1b23255861347f663665..be1c439cb359f44715942dbb9cd21b9fb98e77ae 100644 --- a/tezt/lib_tezos/RPC_legacy.mli +++ b/tezt/lib_tezos/RPC_legacy.mli @@ -195,6 +195,16 @@ val post_simulate_operation : Client.t -> JSON.t Lwt.t +(** Call RPC /chain/[chain]/blocks/[block]/helpers/scripts/event_address *) +val post_compute_event_address : + ?endpoint:Client.endpoint -> + ?hooks:Process.hooks -> + ?chain:string -> + ?block:string -> + data:JSON.u -> + Client.t -> + JSON.t Lwt.t + (** {2 Protocol RPCs} *) type ctxt_type = Bytes | Json diff --git a/tezt/lib_tezos/client.ml b/tezt/lib_tezos/client.ml index 921987916535afbba65241304591697f4fd88e8b..fae0916e808396414db54048fdf48b5fa57136dc 100644 --- a/tezt/lib_tezos/client.ml +++ b/tezt/lib_tezos/client.ml @@ -1890,6 +1890,13 @@ let contract_storage ?unparsing_mode address client = @ optional_arg "unparsing-mode" normalize_mode_to_string unparsing_mode) |> Process.check_and_read_stdout +let contract_code ?unparsing_mode address client = + spawn_command + client + (["get"; "contract"; "code"; "for"; address] + @ optional_arg "unparsing-mode" normalize_mode_to_string unparsing_mode) + |> Process.check_and_read_stdout + let sign_bytes ~signer ~data client = let* output = spawn_command client ["sign"; "bytes"; data; "for"; signer] diff --git a/tezt/lib_tezos/client.mli b/tezt/lib_tezos/client.mli index 62f4bd513e4b4a38c66fe418ba3045950f671c03..feb253e9d7680e385de4efb56a53a06550b4c39f 100644 --- a/tezt/lib_tezos/client.mli +++ b/tezt/lib_tezos/client.mli @@ -1409,6 +1409,11 @@ val register_key : string -> t -> unit Lwt.t val contract_storage : ?unparsing_mode:normalize_mode -> string -> t -> string Lwt.t +(** Get contract code for a contract. Returns a Micheline expression + representing the code as a string. *) +val contract_code : + ?unparsing_mode:normalize_mode -> string -> t -> string Lwt.t + (** Sign a string of bytes with secret key of the given account. *) val sign_bytes : signer:string -> data:string -> t -> string Lwt.t diff --git a/tezt/tests/contracts/proto_alpha/emit_events.tz b/tezt/tests/contracts/proto_alpha/emit_events.tz new file mode 100644 index 0000000000000000000000000000000000000000..3a14c0dc185495e372e1385830f6a6cc29157dc9 --- /dev/null +++ b/tezt/tests/contracts/proto_alpha/emit_events.tz @@ -0,0 +1,16 @@ +parameter unit; +storage unit; +code { DROP; + UNIT; + PUSH string "right"; + RIGHT nat; + EMIT %tag1 (or (nat %int) (string %str)) ; + PUSH nat 2; + LEFT string; + EMIT %tag2 (or (nat %int) (string %str)) ; + NIL operation ; + SWAP ; + CONS ; + SWAP ; + CONS ; + PAIR } \ No newline at end of file diff --git a/tezt/tests/events.ml b/tezt/tests/events.ml new file mode 100644 index 0000000000000000000000000000000000000000..aad40dc8c9a7b13c1417d3b4c54dc1cb48eb11ec --- /dev/null +++ b/tezt/tests/events.ml @@ -0,0 +1,101 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Marigold *) +(* *) +(* 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 + ------- + Components: Client + Invocation: dune exec tezt/tests/main.exe -- check_client_events + Subject: Test that the client shows the contract events in correct order +*) + +let test_emit_event protocol = + let* node = Node.init [Synchronisation_threshold 0; Connections 0] in + let* client = Client.init ~endpoint:(Node node) () in + let* () = Client.activate_protocol ~protocol client in + let* _ = Node.wait_for_level node 1 in + let* contract_id = + Client.originate_contract + ~alias:"emit_events.tz" + ~amount:Tez.zero + ~src:"bootstrap1" + ~prg:"file:./tezt/tests/contracts/proto_alpha/emit_events.tz" + ~init:"Unit" + ~burn_cap:Tez.one + client + in + let* () = Client.bake_for client in + let* () = + Client.transfer + ~gas_limit:100_000 + ~fee:Tez.one + ~amount:Tez.zero + ~burn_cap:Tez.one + ~storage_limit:10000 + ~giver:"bootstrap1" + ~receiver:contract_id + ~arg:"Unit" + ~force:true + client + in + let* () = Client.bake_for client in + let* first_manager_operation = + Client.rpc + Client.GET + ["chains"; "main"; "blocks"; "head"; "operations"; "3"; "0"] + client + in + let open JSON in + let events = + first_manager_operation |-> "contents" |=> 0 |-> "metadata" + |-> "internal_operation_results" + in + let event = events |=> 0 in + assert ( + event |-> "destination" |> as_string + = "ev12m5E1yW14mc9rsrcdGAWVfDSdmRGuctykrVU55bHZBGv9kmdhW") ; + let data = event |-> "parameters" |-> "value" in + assert (data |-> "prim" |> as_string = "Right") ; + assert (data |-> "args" |=> 0 |-> "string" |> as_string = "right") ; + let tag = event |-> "parameters" |-> "entrypoint" |> as_string in + assert (tag = "tag1") ; + let event = events |=> 1 in + assert ( + event |-> "destination" |> as_string + = "ev12m5E1yW14mc9rsrcdGAWVfDSdmRGuctykrVU55bHZBGv9kmdhW") ; + let data = event |-> "parameters" |-> "value" in + assert (data |-> "prim" |> as_string = "Left") ; + assert (data |-> "args" |=> 0 |-> "int" |> as_string = "2") ; + let tag = event |-> "parameters" |-> "entrypoint" |> as_string in + assert (tag = "tag2") ; + return () + +let check_client_events = + Protocol.register_test + ~__FILE__ + ~title:"Events: events from client" + ~tags:["check_client_events"] + test_emit_event + +let register ~protocols = check_client_events protocols diff --git a/tezt/tests/main.ml b/tezt/tests/main.ml index d1dde59d9a8678e5043c78e01bb8603a9a5af7c3..6cfcbcdeb07ad78f820fb8285a46f40a26490c19 100644 --- a/tezt/tests/main.ml +++ b/tezt/tests/main.ml @@ -128,6 +128,7 @@ let () = Client_run_view.register ~protocols:[Alpha; Jakarta] ; Multinode_snapshot.register ~protocols:[Alpha] ; Config.register () ; + Events.register ~protocols:[Alpha] ; (* Relies on a feature only available since K. *) Op_validation.register ~protocols ; (* Test.run () should be the last statement, don't register afterwards! *)