aleph_client 3.0.0

This crate provides a Rust application interface for submitting transactions to `aleph-node` chain.
Documentation
use subxt::ext::sp_runtime::MultiAddress;

use crate::{
    api,
    connections::{AsConnection, TxInfo},
    frame_support::PalletId,
    pallet_treasury::pallet::Call::{approve_proposal, reject_proposal},
    pallets::{committee_management::CommitteeManagementApi, staking::StakingApi},
    sp_core::TypeId,
    sp_runtime::{traits::AccountIdConversion, Perbill},
    AccountId, Balance, BlockHash,
    Call::Treasury,
    ConnectionApi, RootConnection, SignedConnectionApi, SudoCall, TxStatus, MILLISECS_PER_BLOCK,
};

// Copied from `frame_support`.
impl TypeId for PalletId {
    const TYPE_ID: [u8; 4] = *b"modl";
}

/// Pallet treasury read-only api.
#[async_trait::async_trait]
pub trait TreasuryApi {
    /// Returns an unique account id for all treasury transfers.
    async fn treasury_account(&self) -> AccountId;

    /// Returns storage `proposals_count`.
    /// * `at` - an optional block hash to query state from
    async fn proposals_count(&self, at: Option<BlockHash>) -> Option<u32>;

    /// Returns storage `approvals`.
    /// * `at` - an optional block hash to query state from
    async fn approvals(&self, at: Option<BlockHash>) -> Vec<u32>;
}

/// Pallet treasury api.
#[async_trait::async_trait]
pub trait TreasuryUserApi {
    /// API for [`propose_spend`](https://paritytech.github.io/substrate/master/pallet_treasury/pallet/struct.Pallet.html#method.propose_spend) call.
    async fn propose_spend(
        &self,
        amount: Balance,
        beneficiary: AccountId,
        status: TxStatus,
    ) -> anyhow::Result<TxInfo>;

    /// API for [`approve_proposal`](https://paritytech.github.io/substrate/master/pallet_treasury/pallet/struct.Pallet.html#method.approve_proposal) call.
    async fn approve(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo>;

    /// API for [`reject_proposal`](https://paritytech.github.io/substrate/master/pallet_treasury/pallet/struct.Pallet.html#method.reject_proposal) call.
    async fn reject(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo>;
}

/// Pallet treasury funcionality that is not directly related to any pallet call.
#[async_trait::async_trait]
pub trait TreasureApiExt {
    /// When `staking.payout_stakers` is done, what amount of AZERO is transferred to.
    /// the treasury
    async fn possible_treasury_payout(&self) -> anyhow::Result<Balance>;
}

/// Pallet treasury api issued by the sudo account.
#[async_trait::async_trait]
pub trait TreasurySudoApi {
    /// API for [`approve_proposal`](https://paritytech.github.io/substrate/master/pallet_treasury/pallet/struct.Pallet.html#method.approve_proposal) call.
    /// wrapped  in [`sudo_unchecked_weight`](https://paritytech.github.io/substrate/master/pallet_sudo/pallet/struct.Pallet.html#method.sudo_unchecked_weight)
    async fn approve(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo>;

    /// API for [`reject_proposal`](https://paritytech.github.io/substrate/master/pallet_treasury/pallet/struct.Pallet.html#method.reject_proposal) call.
    /// wrapped [`sudo_unchecked_weight`](https://paritytech.github.io/substrate/master/pallet_sudo/pallet/struct.Pallet.html#method.sudo_unchecked_weight)
    async fn reject(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo>;
}

#[async_trait::async_trait]
impl<C: ConnectionApi> TreasuryApi for C {
    async fn treasury_account(&self) -> AccountId {
        PalletId(*b"a0/trsry").into_account_truncating()
    }

    async fn proposals_count(&self, at: Option<BlockHash>) -> Option<u32> {
        let addrs = api::storage().treasury().proposal_count();

        self.get_storage_entry_maybe(&addrs, at).await
    }

    async fn approvals(&self, at: Option<BlockHash>) -> Vec<u32> {
        let addrs = api::storage().treasury().approvals();

        self.get_storage_entry(&addrs, at).await.0
    }
}

#[async_trait::async_trait]
impl<S: SignedConnectionApi> TreasuryUserApi for S {
    async fn propose_spend(
        &self,
        amount: Balance,
        beneficiary: AccountId,
        status: TxStatus,
    ) -> anyhow::Result<TxInfo> {
        let tx = api::tx()
            .treasury()
            .propose_spend(amount, MultiAddress::Id(beneficiary));

        self.send_tx(tx, status).await
    }

    async fn approve(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo> {
        let tx = api::tx().treasury().approve_proposal(proposal_id);

        self.send_tx(tx, status).await
    }

    async fn reject(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo> {
        let tx = api::tx().treasury().reject_proposal(proposal_id);

        self.send_tx(tx, status).await
    }
}

#[async_trait::async_trait]
impl TreasurySudoApi for RootConnection {
    async fn approve(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo> {
        let call = Treasury(approve_proposal { proposal_id });

        self.sudo_unchecked(call, status).await
    }

    async fn reject(&self, proposal_id: u32, status: TxStatus) -> anyhow::Result<TxInfo> {
        let call = Treasury(reject_proposal { proposal_id });

        self.sudo_unchecked(call, status).await
    }
}

#[async_trait::async_trait]
impl<C: AsConnection + Sync> TreasureApiExt for C {
    async fn possible_treasury_payout(&self) -> anyhow::Result<Balance> {
        let session_period = self.get_session_period().await?;
        let sessions_per_era = self.get_session_per_era().await?;
        let millisecs_per_era =
            MILLISECS_PER_BLOCK * session_period as u64 * sessions_per_era as u64;
        Ok(era_payout(millisecs_per_era))
    }
}

fn era_payout(miliseconds_per_era: u64) -> Balance {
    const MILLISECONDS_PER_YEAR: u64 = 1000 * 3600 * 24 * 36525 / 100;
    const TOKEN: u128 = 10u128.pow(12);
    const YEARLY_INFLATION: Balance = 30_000_000 * TOKEN;
    const VALIDATOR_REWARD: Perbill = Perbill::from_percent(90);

    let portion = Perbill::from_rational(miliseconds_per_era, MILLISECONDS_PER_YEAR);
    let total_payout = portion * YEARLY_INFLATION;

    VALIDATOR_REWARD * total_payout
}