Raw File
receipt_repr.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com>     *)
(* Copyright (c) 2020 Metastate AG <hello@metastate.dev>                     *)
(*                                                                           *)
(* 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 Token = struct
  type 'token t =
    | Tez : Tez_repr.t t
    | Staking_pseudotoken : Staking_pseudotoken_repr.t t

  let eq :
      type token1 token2.
      token1 t -> token2 t -> (token1, token2) Equality_witness.eq option =
   fun t1 t2 ->
    match (t1, t2) with
    | Tez, Tez -> Some Refl
    | Tez, _ | _, Tez -> None
    | Staking_pseudotoken, Staking_pseudotoken -> Some Refl

  let equal : type token. token t -> token -> token -> bool = function
    | Tez -> Tez_repr.( = )
    | Staking_pseudotoken -> Staking_pseudotoken_repr.( = )

  let is_zero : type token. token t -> token -> bool =
   fun token t ->
    match token with
    | Tez -> Tez_repr.(t = zero)
    | Staking_pseudotoken -> Staking_pseudotoken_repr.(t = zero)

  let le : type token. token t -> token -> token -> bool = function
    | Tez -> Tez_repr.( <= )
    | Staking_pseudotoken -> Staking_pseudotoken_repr.( <= )

  let add : type token. token t -> token -> token -> token tzresult = function
    | Tez -> Tez_repr.( +? )
    | Staking_pseudotoken -> Staking_pseudotoken_repr.( +? )

  let sub : type token. token t -> token -> token -> token tzresult = function
    | Tez -> Tez_repr.( -? )
    | Staking_pseudotoken -> Staking_pseudotoken_repr.( -? )

  let pp_tez =
    let tez_sym = "\xEA\x9C\xA9" in
    fun ppf tez -> Format.fprintf ppf "%s%a" tez_sym Tez_repr.pp tez

  let pp : type token. token t -> Format.formatter -> token -> unit = function
    | Tez -> pp_tez
    | Staking_pseudotoken -> Staking_pseudotoken_repr.pp
end

type 'token balance =
  | Contract : Contract_repr.t -> Tez_repr.t balance
  | Block_fees : Tez_repr.t balance
  | Deposits : Frozen_staker_repr.t -> Tez_repr.t balance
  | Unstaked_deposits :
      Unstaked_frozen_staker_repr.t * Cycle_repr.t
      -> Tez_repr.t balance
  | Nonce_revelation_rewards : Tez_repr.t balance
  | Attesting_rewards : Tez_repr.t balance
  | Baking_rewards : Tez_repr.t balance
  | Baking_bonuses : Tez_repr.t balance
  | Storage_fees : Tez_repr.t balance
  | Double_signing_punishments : Tez_repr.t balance
  | Lost_attesting_rewards :
      Signature.Public_key_hash.t * bool * bool
      -> Tez_repr.t balance
  | Liquidity_baking_subsidies : Tez_repr.t balance
  | Burned : Tez_repr.t balance
  | Commitments : Blinded_public_key_hash.t -> Tez_repr.t balance
  | Bootstrap : Tez_repr.t balance
  | Invoice : Tez_repr.t balance
  | Initial_commitments : Tez_repr.t balance
  | Minted : Tez_repr.t balance
  | Frozen_bonds : Contract_repr.t * Bond_id_repr.t -> Tez_repr.t balance
  | Sc_rollup_refutation_punishments : Tez_repr.t balance
  | Sc_rollup_refutation_rewards : Tez_repr.t balance
  | Staking_delegator_numerator : {
      delegator : Contract_repr.t;
    }
      -> Staking_pseudotoken_repr.t balance
  | Staking_delegate_denominator : {
      delegate : Signature.public_key_hash;
    }
      -> Staking_pseudotoken_repr.t balance

let token_of_balance : type token. token balance -> token Token.t = function
  | Contract _ -> Token.Tez
  | Block_fees -> Token.Tez
  | Deposits _ -> Token.Tez
  | Unstaked_deposits _ -> Token.Tez
  | Nonce_revelation_rewards -> Token.Tez
  | Attesting_rewards -> Token.Tez
  | Baking_rewards -> Token.Tez
  | Baking_bonuses -> Token.Tez
  | Storage_fees -> Token.Tez
  | Double_signing_punishments -> Token.Tez
  | Lost_attesting_rewards _ -> Token.Tez
  | Liquidity_baking_subsidies -> Token.Tez
  | Burned -> Token.Tez
  | Commitments _ -> Token.Tez
  | Bootstrap -> Token.Tez
  | Invoice -> Token.Tez
  | Initial_commitments -> Token.Tez
  | Minted -> Token.Tez
  | Frozen_bonds _ -> Token.Tez
  | Sc_rollup_refutation_punishments -> Token.Tez
  | Sc_rollup_refutation_rewards -> Token.Tez
  | Staking_delegator_numerator _ -> Token.Staking_pseudotoken
  | Staking_delegate_denominator _ -> Token.Staking_pseudotoken

let is_not_zero c = not (Compare.Int.equal c 0)

let compare_balance :
    type token1 token2. token1 balance -> token2 balance -> int =
 fun ba bb ->
  match (ba, bb) with
  | Contract ca, Contract cb -> Contract_repr.compare ca cb
  | Deposits sa, Deposits sb -> Frozen_staker_repr.compare sa sb
  | Unstaked_deposits (sa, ca), Unstaked_deposits (sb, cb) ->
      Compare.or_else (Unstaked_frozen_staker_repr.compare sa sb) (fun () ->
          Cycle_repr.compare ca cb)
  | Lost_attesting_rewards (pkha, pa, ra), Lost_attesting_rewards (pkhb, pb, rb)
    ->
      let c = Signature.Public_key_hash.compare pkha pkhb in
      if is_not_zero c then c
      else
        let c = Compare.Bool.compare pa pb in
        if is_not_zero c then c else Compare.Bool.compare ra rb
  | Commitments bpkha, Commitments bpkhb ->
      Blinded_public_key_hash.compare bpkha bpkhb
  | Frozen_bonds (ca, ra), Frozen_bonds (cb, rb) ->
      let c = Contract_repr.compare ca cb in
      if is_not_zero c then c else Bond_id_repr.compare ra rb
  | ( Staking_delegator_numerator {delegator = ca},
      Staking_delegator_numerator {delegator = cb} ) ->
      Contract_repr.compare ca cb
  | ( Staking_delegate_denominator {delegate = pkha},
      Staking_delegate_denominator {delegate = pkhb} ) ->
      Signature.Public_key_hash.compare pkha pkhb
  | _, _ ->
      let index : type token. token balance -> int = function
        | Contract _ -> 0
        | Block_fees -> 1
        | Deposits _ -> 2
        | Unstaked_deposits _ -> 3
        | Nonce_revelation_rewards -> 4
        | Attesting_rewards -> 5
        | Baking_rewards -> 6
        | Baking_bonuses -> 7
        | Storage_fees -> 8
        | Double_signing_punishments -> 9
        | Lost_attesting_rewards _ -> 10
        | Liquidity_baking_subsidies -> 11
        | Burned -> 12
        | Commitments _ -> 13
        | Bootstrap -> 14
        | Invoice -> 15
        | Initial_commitments -> 16
        | Minted -> 17
        | Frozen_bonds _ -> 18
        | Sc_rollup_refutation_punishments -> 19
        | Sc_rollup_refutation_rewards -> 20
        | Staking_delegator_numerator _ -> 21
        | Staking_delegate_denominator _ -> 22
        (* don't forget to add parameterized cases in the first part of the function *)
      in
      Compare.Int.compare (index ba) (index bb)

type 'token balance_update = Debited of 'token | Credited of 'token

type balance_and_update =
  | Ex_token : 'token balance * 'token balance_update -> balance_and_update

let is_zero_update : type token. token Token.t -> token balance_update -> bool =
 fun token -> function Debited t | Credited t -> Token.is_zero token t

let conv_balance_update encoding =
  Data_encoding.conv
    (function Credited v -> `Credited v | Debited v -> `Debited v)
    (function `Credited v -> Credited v | `Debited v -> Debited v)
    encoding

let tez_balance_update_encoding =
  let open Data_encoding in
  def "operation_metadata.alpha.tez_balance_update"
  @@ obj1 (req "change" (conv_balance_update Tez_repr.balance_update_encoding))

let staking_pseudotoken_balance_update_encoding =
  let open Data_encoding in
  def "operation_metadata.alpha.staking_abstract_quantity"
  @@ obj1
       (req
          "change"
          (conv_balance_update Staking_pseudotoken_repr.balance_update_encoding))

let balance_and_update_encoding ~use_legacy_attestation_name =
  let open Data_encoding in
  let case = function
    | Tag tag ->
        (* The tag was used by old variant. It have been removed in
           protocol proposal O, it can be unblocked in the future. *)
        let tx_rollup_reserved_tag = [22; 23] in
        assert (
          not @@ List.exists (Compare.Int.equal tag) tx_rollup_reserved_tag) ;
        case (Tag tag)
    | _ as c -> case c
  in
  let tez_case ~title tag enc (proj : Tez_repr.t balance -> _ option) inj =
    case
      ~title
      tag
      (merge_objs enc tez_balance_update_encoding)
      (fun (Ex_token (balance, update)) ->
        match token_of_balance balance with
        | Tez -> proj balance |> Option.map (fun x -> (x, update))
        | _ -> None)
      (fun (x, update) -> Ex_token (inj x, update))
  in
  let staking_pseudotoken_case ~title tag enc
      (proj : Staking_pseudotoken_repr.t balance -> _ option) inj =
    case
      ~title
      tag
      (merge_objs enc staking_pseudotoken_balance_update_encoding)
      (fun (Ex_token (balance, update)) ->
        match token_of_balance balance with
        | Staking_pseudotoken ->
            proj balance |> Option.map (fun x -> (x, update))
        | _ -> None)
      (fun (x, update) -> Ex_token (inj x, update))
  in
  def
    (if use_legacy_attestation_name then
     "operation_metadata_with_legacy_attestation_name.alpha.balance_and_update"
    else "operation_metadata.alpha.balance_and_update")
  @@ union
       [
         tez_case
           (Tag 0)
           ~title:"Contract"
           (obj2
              (req "kind" (constant "contract"))
              (req "contract" Contract_repr.encoding))
           (function Contract c -> Some ((), c) | _ -> None)
           (fun ((), c) -> Contract c);
         tez_case
           (Tag 2)
           ~title:"Block_fees"
           (obj2
              (req "kind" (constant "accumulator"))
              (req "category" (constant "block fees")))
           (function Block_fees -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Block_fees);
         tez_case
           (Tag 4)
           ~title:"Deposits"
           (obj3
              (req "kind" (constant "freezer"))
              (req "category" (constant "deposits"))
              (req "staker" Frozen_staker_repr.encoding))
           (function Deposits staker -> Some ((), (), staker) | _ -> None)
           (fun ((), (), staker) -> Deposits staker);
         tez_case
           (Tag 5)
           ~title:"Nonce_revelation_rewards"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "nonce revelation rewards")))
           (function Nonce_revelation_rewards -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Nonce_revelation_rewards);
         (* 6 was for Double_signing_evidence_rewards that has been removed.
            https://gitlab.com/tezos/tezos/-/merge_requests/7758 *)
         tez_case
           (Tag 7)
           ~title:
             (if use_legacy_attestation_name then "Endorsing_rewards"
             else "Attesting_rewards")
           (obj2
              (req "kind" (constant "minted"))
              (req
                 "category"
                 (constant
                    (if use_legacy_attestation_name then "endorsing rewards"
                    else "attesting rewards"))))
           (function Attesting_rewards -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Attesting_rewards);
         tez_case
           (Tag 8)
           ~title:"Baking_rewards"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "baking rewards")))
           (function Baking_rewards -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Baking_rewards);
         tez_case
           (Tag 9)
           ~title:"Baking_bonuses"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "baking bonuses")))
           (function Baking_bonuses -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Baking_bonuses);
         tez_case
           (Tag 11)
           ~title:"Storage_fees"
           (obj2
              (req "kind" (constant "burned"))
              (req "category" (constant "storage fees")))
           (function Storage_fees -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Storage_fees);
         tez_case
           (Tag 12)
           ~title:"Double_signing_punishments"
           (obj2
              (req "kind" (constant "burned"))
              (req "category" (constant "punishments")))
           (function Double_signing_punishments -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Double_signing_punishments);
         tez_case
           (Tag 13)
           ~title:
             (if use_legacy_attestation_name then "Lost_endorsing_rewards"
             else "Lost_attesting_rewards")
           (obj5
              (req "kind" (constant "burned"))
              (req
                 "category"
                 (constant
                    (if use_legacy_attestation_name then
                     "lost endorsing rewards"
                    else "lost attesting rewards")))
              (req "delegate" Signature.Public_key_hash.encoding)
              (req "participation" Data_encoding.bool)
              (req "revelation" Data_encoding.bool))
           (function
             | Lost_attesting_rewards (d, p, r) -> Some ((), (), d, p, r)
             | _ -> None)
           (fun ((), (), d, p, r) -> Lost_attesting_rewards (d, p, r));
         tez_case
           (Tag 14)
           ~title:"Liquidity_baking_subsidies"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "subsidy")))
           (function Liquidity_baking_subsidies -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Liquidity_baking_subsidies);
         tez_case
           (Tag 15)
           ~title:"Burned"
           (obj2
              (req "kind" (constant "burned"))
              (req "category" (constant "burned")))
           (function Burned -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Burned);
         tez_case
           (Tag 16)
           ~title:"Commitments"
           (obj3
              (req "kind" (constant "commitment"))
              (req "category" (constant "commitment"))
              (req "committer" Blinded_public_key_hash.encoding))
           (function Commitments bpkh -> Some ((), (), bpkh) | _ -> None)
           (fun ((), (), bpkh) -> Commitments bpkh);
         tez_case
           (Tag 17)
           ~title:"Bootstrap"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "bootstrap")))
           (function Bootstrap -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Bootstrap);
         tez_case
           (Tag 18)
           ~title:"Invoice"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "invoice")))
           (function Invoice -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Invoice);
         tez_case
           (Tag 19)
           ~title:"Initial_commitments"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "commitment")))
           (function Initial_commitments -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Initial_commitments);
         tez_case
           (Tag 20)
           ~title:"Minted"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "minted")))
           (function Minted -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Minted);
         tez_case
           (Tag 21)
           ~title:"Frozen_bonds"
           (obj4
              (req "kind" (constant "freezer"))
              (req "category" (constant "bonds"))
              (req "contract" Contract_repr.encoding)
              (req "bond_id" Bond_id_repr.encoding))
           (function Frozen_bonds (c, r) -> Some ((), (), c, r) | _ -> None)
           (fun ((), (), c, r) -> Frozen_bonds (c, r));
         tez_case
           (Tag 24)
           ~title:"Smart_rollup_refutation_punishments"
           (obj2
              (req "kind" (constant "burned"))
              (req "category" (constant "smart_rollup_refutation_punishments")))
           (function
             | Sc_rollup_refutation_punishments -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Sc_rollup_refutation_punishments);
         tez_case
           (Tag 25)
           ~title:"Smart_rollup_refutation_rewards"
           (obj2
              (req "kind" (constant "minted"))
              (req "category" (constant "smart_rollup_refutation_rewards")))
           (function
             | Sc_rollup_refutation_rewards -> Some ((), ()) | _ -> None)
           (fun ((), ()) -> Sc_rollup_refutation_rewards);
         tez_case
           (Tag 26)
           ~title:"Unstaked_deposits"
           (obj4
              (req "kind" (constant "freezer"))
              (req "category" (constant "unstaked_deposits"))
              (req "staker" Unstaked_frozen_staker_repr.encoding)
              (req "cycle" Cycle_repr.encoding))
           (function
             | Unstaked_deposits (staker, cycle) -> Some ((), (), staker, cycle)
             | _ -> None)
           (fun ((), (), staker, cycle) -> Unstaked_deposits (staker, cycle));
         staking_pseudotoken_case
           (Tag 27)
           ~title:"Staking_delegator_numerator"
           (obj3
              (req "kind" (constant "staking"))
              (req "category" (constant "delegator_numerator"))
              (req "delegator" Contract_repr.encoding))
           (function
             | Staking_delegator_numerator {delegator} ->
                 Some ((), (), delegator)
             | _ -> None)
           (fun ((), (), delegator) -> Staking_delegator_numerator {delegator});
         staking_pseudotoken_case
           (Tag 28)
           ~title:"Staking_delegate_denominator"
           (obj3
              (req "kind" (constant "staking"))
              (req "category" (constant "delegate_denominator"))
              (req "delegate" Signature.Public_key_hash.encoding))
           (function
             | Staking_delegate_denominator {delegate} -> Some ((), (), delegate)
             | _ -> None)
           (fun ((), (), delegate) -> Staking_delegate_denominator {delegate});
       ]

let balance_and_update_encoding_with_legacy_attestation_name =
  balance_and_update_encoding ~use_legacy_attestation_name:true

let balance_and_update_encoding =
  balance_and_update_encoding ~use_legacy_attestation_name:false

type update_origin =
  | Block_application
  | Protocol_migration
  | Subsidy
  | Simulation
  | Delayed_operation of {operation_hash : Operation_hash.t}

let compare_update_origin oa ob =
  match (oa, ob) with
  | ( Delayed_operation {operation_hash = oha},
      Delayed_operation {operation_hash = ohb} ) ->
      Operation_hash.compare oha ohb
  | _, _ ->
      let index o =
        match o with
        | Block_application -> 0
        | Protocol_migration -> 1
        | Subsidy -> 2
        | Simulation -> 3
        | Delayed_operation _ -> 4
        (* don't forget to add parameterized cases in the first part of the function *)
      in
      Compare.Int.compare (index oa) (index ob)

let update_origin_encoding =
  let open Data_encoding in
  def "operation_metadata.alpha.update_origin"
  @@ union
       [
         case
           (Tag 0)
           ~title:"Block_application"
           (obj1 (req "origin" (constant "block")))
           (function Block_application -> Some () | _ -> None)
           (fun () -> Block_application);
         case
           (Tag 1)
           ~title:"Protocol_migration"
           (obj1 (req "origin" (constant "migration")))
           (function Protocol_migration -> Some () | _ -> None)
           (fun () -> Protocol_migration);
         case
           (Tag 2)
           ~title:"Subsidy"
           (obj1 (req "origin" (constant "subsidy")))
           (function Subsidy -> Some () | _ -> None)
           (fun () -> Subsidy);
         case
           (Tag 3)
           ~title:"Simulation"
           (obj1 (req "origin" (constant "simulation")))
           (function Simulation -> Some () | _ -> None)
           (fun () -> Simulation);
         case
           (Tag 4)
           ~title:"Delayed_operation"
           (obj2
              (req "origin" (constant "delayed_operation"))
              (req "delayed_operation_hash" Operation_hash.encoding))
           (function
             | Delayed_operation {operation_hash} -> Some ((), operation_hash)
             | _ -> None)
           (fun ((), operation_hash) -> Delayed_operation {operation_hash});
       ]

type balance_update_item =
  | Balance_update_item :
      'token balance * 'token balance_update * update_origin
      -> balance_update_item

let item balance balance_update update_origin =
  Balance_update_item (balance, balance_update, update_origin)

let item_encoding_with_legacy_attestation_name =
  let open Data_encoding in
  conv
    (function
      | Balance_update_item (balance, balance_update, update_origin) ->
          (Ex_token (balance, balance_update), update_origin))
    (fun (Ex_token (balance, balance_update), update_origin) ->
      Balance_update_item (balance, balance_update, update_origin))
    (merge_objs
       balance_and_update_encoding_with_legacy_attestation_name
       update_origin_encoding)

let item_encoding =
  let open Data_encoding in
  conv
    (function
      | Balance_update_item (balance, balance_update, update_origin) ->
          (Ex_token (balance, balance_update), update_origin))
    (fun (Ex_token (balance, balance_update), update_origin) ->
      Balance_update_item (balance, balance_update, update_origin))
    (merge_objs balance_and_update_encoding update_origin_encoding)

type balance_updates = balance_update_item list

let balance_updates_encoding_with_legacy_attestation_name =
  let open Data_encoding in
  def "operation_metadata_with_legacy_attestation_name.alpha.balance_updates"
  @@ list item_encoding_with_legacy_attestation_name

let balance_updates_encoding =
  let open Data_encoding in
  def "operation_metadata.alpha.balance_updates" @@ list item_encoding

module MakeBalanceMap (T : sig
  type token
end) =
struct
  include Map.Make (struct
    type t = T.token balance * update_origin

    let compare (ba, ua) (bb, ub) =
      let c = compare_balance ba bb in
      if is_not_zero c then c else compare_update_origin ua ub
  end)

  let update_r key (f : 'a option -> 'b option tzresult) map =
    let open Result_syntax in
    let* v_opt = f (find key map) in
    match v_opt with
    | Some v -> return (add key v map)
    | None -> return (remove key map)
end

module TezBalanceMap = MakeBalanceMap (struct
  type token = Tez_repr.t
end)

module StakingPseudotokenMap = MakeBalanceMap (struct
  type token = Staking_pseudotoken_repr.t
end)

type 'a balance_maps = {
  tez : Tez_repr.t balance_update TezBalanceMap.t;
  staking_pt : Staking_pseudotoken_repr.t balance_update StakingPseudotokenMap.t;
}

let group_balance_updates balance_updates =
  let open Result_syntax in
  let update_map token update_r key update map =
    update_r
      key
      (function
        | None -> return_some update
        | Some balance -> (
            match (balance, update) with
            | Credited a, Debited b | Debited b, Credited a ->
                (* Remove the binding since it just fell down to zero *)
                if Token.equal token a b then return_none
                else if Token.le token b a then
                  let* update = Token.sub token a b in
                  return_some (Credited update)
                else
                  let* update = Token.sub token b a in
                  return_some (Debited update)
            | Credited a, Credited b ->
                let* update = Token.add token a b in
                return_some (Credited update)
            | Debited a, Debited b ->
                let* update = Token.add token a b in
                return_some (Debited update)))
      map
  in
  let* {tez; staking_pt} =
    List.fold_left_e
      (fun acc (Balance_update_item (b, update, o)) ->
        (* Do not do anything if the update is zero *)
        let token = token_of_balance b in
        if is_zero_update token update then return acc
        else
          match token with
          | Tez ->
              let+ tez =
                update_map token TezBalanceMap.update_r (b, o) update acc.tez
              in
              {acc with tez}
          | Staking_pseudotoken ->
              let+ staking_pt =
                update_map
                  token
                  StakingPseudotokenMap.update_r
                  (b, o)
                  update
                  acc.staking_pt
              in
              {acc with staking_pt})
      {tez = TezBalanceMap.empty; staking_pt = StakingPseudotokenMap.empty}
      balance_updates
  in
  return
    (StakingPseudotokenMap.fold
       (fun (b, o) u acc -> Balance_update_item (b, u, o) :: acc)
       staking_pt
       (TezBalanceMap.fold
          (fun (b, o) u acc -> Balance_update_item (b, u, o) :: acc)
          tez
          []))
back to top