#pragma once

/**
\defgroup public_msig Контракт MSIG

* Системный контракт для создания предложений транзакций, требующих авторизации от списка аккаунтов.
*/

/**
\defgroup public_msig_processes Процессы
\ingroup public_msig
*/

/**
\defgroup public_msig_actions Действия
\ingroup public_msig
*/

/**
\defgroup public_msig_tables Таблицы
\ingroup public_msig
*/

/**
\defgroup public_msig_consts Константы
\ingroup public_msig
*/

#include <eosio/binary_extension.hpp>
#include <eosio/eosio.hpp>
#include <eosio/ignore.hpp>
#include <eosio/transaction.hpp>

namespace eosio {
   /**
    * The `eosio.msig` system contract allows for creation of proposed transactions which require authorization from a list of accounts, approval of the proposed transactions by those accounts required to approve it, and finally, it also allows the execution of the approved transactions on the blockchain.
    *
    * In short, the workflow to propose, review, approve and then executed a transaction it can be described by the following:
    * - first you create a transaction json file,
    * - then you submit this proposal to the `eosio.msig` contract, and you also insert the account permissions required to approve this proposal into the command that submits the proposal to the blockchain,
    * - the proposal then gets stored on the blockchain by the `eosio.msig` contract, and is accessible for review and approval to those accounts required to approve it,
    * - after each of the appointed accounts required to approve the proposed transactions reviews and approves it, you can execute the proposed transaction. The `eosio.msig` contract will execute it automatically, but not before validating that the transaction has not expired, it is not cancelled, and it has been signed by all the permissions in the initial proposal's required permission list.
    */
   class [[eosio::contract("eosio.msig")]] multisig : public contract {
      public:
         using contract::contract;

         /**
          * Propose action, creates a proposal containing one transaction.
          * Allows an account `proposer` to make a proposal `proposal_name` which has `requested`
          * permission levels expected to approve the proposal, and if approved by all expected
          * permission levels then `trx` transaction can we executed by this proposal.
          * The `proposer` account is authorized and the `trx` transaction is verified if it was
          * authorized by the provided keys and permissions, and if the proposal name doesn’t
          * already exist; if all validations pass the `proposal_name` and `trx` trasanction are
          * saved in the proposals table and the `requested` permission levels to the
          * approvals table (for the `proposer` context). Storage changes are billed to `proposer`.
          *
          * @param proposer - The account proposing a transaction
          * @param proposal_name - The name of the proposal (should be unique for proposer)
          * @param requested - Permission levels expected to approve the proposal
          * @param trx - Proposed transaction
          */
         [[eosio::action]]
         void propose(name proposer, name proposal_name,
                      std::vector<permission_level> requested, ignore<transaction> trx);
         /**
          * Approve action approves an existing proposal. Allows an account, the owner of `level` permission, to approve a proposal `proposal_name`
          * proposed by `proposer`. If the proposal's requested approval list contains the `level`
          * permission then the `level` permission is moved from internal `requested_approvals` list to
          * internal `provided_approvals` list of the proposal, thus persisting the approval for
          * the `proposal_name` proposal. Storage changes are billed to `proposer`.
          *
          * @param proposer - The account proposing a transaction
          * @param proposal_name - The name of the proposal (should be unique for proposer)
          * @param level - Permission level approving the transaction
          * @param proposal_hash - Transaction's checksum
          */
         [[eosio::action]]
         void approve( name proposer, name proposal_name, permission_level level,
                       const eosio::binary_extension<eosio::checksum256>& proposal_hash );
         /**
          * Unapprove action revokes an existing proposal. This action is the reverse of the `approve` action: if all validations pass
          * the `level` permission is erased from internal `provided_approvals` and added to the internal
          * `requested_approvals` list, and thus un-approve or revoke the proposal.
          *
          * @param proposer - The account proposing a transaction
          * @param proposal_name - The name of the proposal (should be an existing proposal)
          * @param level - Permission level revoking approval for proposal
          */
         [[eosio::action]]
         void unapprove( name proposer, name proposal_name, permission_level level );
         /**
          * Cancel action cancels an existing proposal.
          *
          * @param proposer - The account proposing a transaction
          * @param proposal_name - The name of the proposal (should be an existing proposal)
          * @param canceler - The account cancelling the proposal (only the proposer can cancel an unexpired transaction, and the canceler has to be different than the proposer)
          *
          * Allows the `canceler` account to cancel the `proposal_name` proposal, created by a `proposer`,
          * only after time has expired on the proposed transaction. It removes corresponding entries from
          * internal proptable and from approval (or old approvals) tables as well.
          */
         [[eosio::action]]
         void cancel( name proposer, name proposal_name, name canceler );
         /**
          * Exec action allows an `executer` account to execute a proposal.
          *
          * Preconditions:
          * - `executer` has authorization,
          * - `proposal_name` is found in the proposals table,
          * - all requested approvals are received,
          * - proposed transaction is not expired,
          * - and approval accounts are not found in invalidations table.
          *
          * If all preconditions are met the transaction is executed as a deferred transaction,
          * and the proposal is erased from the proposals table.
          *
          * @param proposer - The account proposing a transaction
          * @param proposal_name - The name of the proposal (should be an existing proposal)
          * @param executer - The account executing the transaction
          */
         [[eosio::action]]
         void exec( name proposer, name proposal_name, name executer );
         /**
          * Invalidate action allows an `account` to invalidate itself, that is, its name is added to
          * the invalidations table and this table will be cross referenced when exec is performed.
          *
          * @param account - The account invalidating the transaction
          */
         [[eosio::action]]
         void invalidate( name account );

         using propose_action = eosio::action_wrapper<"propose"_n, &multisig::propose>;
         using approve_action = eosio::action_wrapper<"approve"_n, &multisig::approve>;
         using unapprove_action = eosio::action_wrapper<"unapprove"_n, &multisig::unapprove>;
         using cancel_action = eosio::action_wrapper<"cancel"_n, &multisig::cancel>;
         using exec_action = eosio::action_wrapper<"exec"_n, &multisig::exec>;
         using invalidate_action = eosio::action_wrapper<"invalidate"_n, &multisig::invalidate>;

   /**
   * @brief Таблица предложений хранит информацию о предложенных транзакциях.
   * @ingroup public_tables
   * @ingroup public_msig_tables

   * @par Область памяти (scope): proposer
   * @par Имя таблицы (table): proposal
   */
   struct [[eosio::table, eosio::contract("eosio.msig")]] proposal {
      name                                                            proposal_name; ///< Имя предложения
      std::vector<char>                                               packed_transaction; ///< Упакованная транзакция
      eosio::binary_extension< std::optional<time_point> >            earliest_exec_time; ///< Самое раннее время выполнения

      uint64_t primary_key()const { return proposal_name.value; } ///< Первичный ключ (1)
   };
   typedef eosio::multi_index< "proposal"_n, proposal > proposals;

   /**
   * @brief Таблица старых одобрений хранит информацию об одобрениях предложений (устаревшая версия).
   * @ingroup public_tables
   * @ingroup public_msig_tables

   * @par Область памяти (scope): proposer
   * @par Имя таблицы (table): approvals
   */
   struct [[eosio::table, eosio::contract("eosio.msig")]] old_approvals_info {
      name                            proposal_name; ///< Имя предложения
      std::vector<permission_level>   requested_approvals; ///< Запрошенные одобрения
      std::vector<permission_level>   provided_approvals; ///< Предоставленные одобрения
      uint64_t primary_key()const { return proposal_name.value; } ///< Первичный ключ (1)
   };
   typedef eosio::multi_index< "approvals"_n, old_approvals_info > old_approvals;
   struct approval {
      permission_level level;
      time_point       time;
   };

   /**
   * @brief Таблица информации об одобрениях хранит информацию об одобрениях предложений.
   * @ingroup public_tables
   * @ingroup public_msig_tables

   * @par Область памяти (scope): proposer
   * @par Имя таблицы (table): approvals2
   */
   struct [[eosio::table, eosio::contract("eosio.msig")]] approvals_info {
      uint8_t                 version = 1; ///< Версия структуры
      name                    proposal_name; ///< Имя предложения
      //requested approval doesn't need to contain time, but we want requested approval
      //to be of exactly the same size as provided approval, in this case approve/unapprove
      //doesn't change serialized data size. So, we use the same type.
      std::vector<approval>   requested_approvals; ///< Запрошенные одобрения
      std::vector<approval>   provided_approvals; ///< Предоставленные одобрения
      uint64_t primary_key()const { return proposal_name.value; } ///< Первичный ключ (1)
   };
   typedef eosio::multi_index< "approvals2"_n, approvals_info > approvals;

   /**
   * @brief Таблица инвалидаций хранит информацию об инвалидированных аккаунтах.
   * @ingroup public_tables
   * @ingroup public_msig_tables

   * @par Область памяти (scope): eosio.msig
   * @par Имя таблицы (table): invals
   */
   struct [[eosio::table, eosio::contract("eosio.msig")]] invalidation {
         name         account; ///< Аккаунт
         time_point   last_invalidation_time; ///< Время последней инвалидации

         uint64_t primary_key() const { return account.value; } ///< Первичный ключ (1)
      };

      typedef eosio::multi_index< "invals"_n, invalidation > invalidations;
   };
} /// namespace eosio
