#![allow(clippy::string_lit_as_bytes)]
#![allow(clippy::redundant_closure_call)]
#![allow(clippy::type_complexity)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod tests;
use util::{
bank::{OffChainTreasuryID, Payment, PaymentConfirmation},
court::Evidence,
organization::{FormedOrganization, ShareID},
traits::{
GenerateUniqueID, GetInnerOuterShareGroups, IDIsAvailable, OffChainBank, OrgChecks,
OrganizationDNS, RegisterOffChainBankAccount, RegisterShareGroup, ShareGroupChecks,
SupervisorPermissions, SupportedOrganizationShapes, WeightedShareIssuanceWrapper,
WeightedShareWrapper,
},
};
use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, traits::Currency};
use frame_system::{self as system, ensure_signed};
use sp_runtime::{DispatchError, DispatchResult, Permill};
use sp_std::prelude::*;
pub type IpfsReference = Vec<u8>;
pub type OrgId = u32;
pub type SharesOf<T> = <<T as Trait>::Organization as WeightedShareWrapper<
u32,
u32,
<T as frame_system::Trait>::AccountId,
>>::Shares;
type BalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
pub trait Trait: system::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type Currency: Currency<Self::AccountId>;
type Organization: OrgChecks<u32, Self::AccountId>
+ ShareGroupChecks<u32, ShareID, Self::AccountId>
+ GetInnerOuterShareGroups<u32, ShareID, Self::AccountId>
+ SupervisorPermissions<u32, ShareID, Self::AccountId>
+ WeightedShareWrapper<u32, u32, Self::AccountId>
+ WeightedShareIssuanceWrapper<u32, u32, Self::AccountId, Permill>
+ RegisterShareGroup<u32, ShareID, Self::AccountId, SharesOf<Self>>
+ OrganizationDNS<u32, Self::AccountId, IpfsReference>;
}
decl_event!(
pub enum Event<T>
where
<T as frame_system::Trait>::AccountId,
Balance = BalanceOf<T>,
{
NewOffChainTreasuryRegisteredForOrg(u32, OffChainTreasuryID),
NewOffChainTreasuryRegisteredForFlatShareGroup(u32, u32, OffChainTreasuryID),
NewOffChainTreasuryRegisteredForWeightedShareGroup(u32, u32, OffChainTreasuryID),
SenderClaimsPaymentSent(OffChainTreasuryID, AccountId, AccountId, Balance, u32),
RecipientConfirmsPaymentReceived(OffChainTreasuryID, AccountId, AccountId, Balance, u32),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
MustHaveCertainAuthorityToRegisterOffChainBankAccountForOrg,
CannotUseOffChainBankThatDNE,
FlatShareGroupNotFound,
WeightedShareGroupNotFound,
MustBeAMemberToUseOffChainBankAccountToClaimPaymentSent,
SenderMustClaimPaymentSentForRecipientToConfirm,
CannotRegisterOffChainBankForOrgThatDNE,
}
}
decl_storage! {
trait Store for Module<T: Trait> as Bank {
OffChainTreasuryIDNonce get(fn off_chain_treasury_id_nonce): u32;
OffChainTreasuryIDs get(fn off_chain_treasury_ids): map
hasher(opaque_blake2_256) OffChainTreasuryID => Option<FormedOrganization>;
OffChainTransferEvidence get(fn off_chain_transfer_evidence): double_map
hasher(opaque_blake2_256) OffChainTreasuryID,
hasher(opaque_blake2_256) Evidence<T::AccountId, IpfsReference> => bool;
OffChainTransfers get(fn off_chain_transfers): double_map
hasher(opaque_blake2_256) OffChainTreasuryID,
hasher(opaque_blake2_256) Payment<T::AccountId, BalanceOf<T>> => Option<PaymentConfirmation>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
#[weight = 0]
fn register_offchain_bank_account_for_organization(
origin,
organization: OrgId,
) -> DispatchResult {
let caller = ensure_signed(origin)?;
let authentication: bool = Self::is_sudo_account(&caller)
|| Self::is_organization_supervisor(organization, &caller);
ensure!(authentication, Error::<T>::MustHaveCertainAuthorityToRegisterOffChainBankAccountForOrg);
let organization_exists = <<T as Trait>::Organization as OrgChecks<
u32,
T::AccountId
>>::check_org_existence(organization);
ensure!(organization_exists, Error::<T>::CannotRegisterOffChainBankForOrgThatDNE);
let formed_org: FormedOrganization = organization.into();
let off_chain_treasury_id = Self::register_off_chain_bank_account(formed_org)?;
Self::deposit_event(RawEvent::NewOffChainTreasuryRegisteredForOrg(organization, off_chain_treasury_id));
Ok(())
}
#[weight = 0]
fn register_offchain_bank_account_for_inner_flat_share_group(
origin,
organization: u32,
flat_share_group_id: u32,
) -> DispatchResult {
let _ = ensure_signed(origin)?;
let share_id = ShareID::Flat(flat_share_group_id);
let formed_org: FormedOrganization = (organization, share_id).into();
let off_chain_treasury_id = Self::register_off_chain_bank_account(formed_org)?;
Self::deposit_event(RawEvent::NewOffChainTreasuryRegisteredForFlatShareGroup(organization, flat_share_group_id, off_chain_treasury_id));
Ok(())
}
#[weight = 0]
fn register_offchain_bank_account_for_weighted_share_group(
origin,
organization: u32,
weighted_share_group_id: u32,
) -> DispatchResult {
let _ = ensure_signed(origin)?;
let share_id = ShareID::WeightedAtomic(weighted_share_group_id);
let formed_org: FormedOrganization = (organization, share_id).into();
let off_chain_treasury_id = Self::register_off_chain_bank_account(formed_org)?;
Self::deposit_event(RawEvent::NewOffChainTreasuryRegisteredForWeightedShareGroup(organization, weighted_share_group_id, off_chain_treasury_id));
Ok(())
}
#[weight = 0]
fn use_offchain_bank_account_to_claim_payment_sent(
origin,
treasury_id: u32,
recipient: T::AccountId,
amount: BalanceOf<T>,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let formed_org = OffChainTreasuryIDs::get(treasury_id).ok_or(Error::<T>::CannotUseOffChainBankThatDNE)?;
let authentication: bool = Self::account_is_member_of_formed_organization(formed_org, &sender);
ensure!(authentication, Error::<T>::MustBeAMemberToUseOffChainBankAccountToClaimPaymentSent);
let payment_claimed_sent = Payment::new(0u32, sender.clone(), recipient.clone(), amount);
let new_payment_salt = Self::sender_claims_payment_sent(treasury_id, payment_claimed_sent).salt();
Self::deposit_event(RawEvent::SenderClaimsPaymentSent(treasury_id, sender, recipient, amount, new_payment_salt));
Ok(())
}
#[weight = 0]
fn use_offchain_bank_account_to_confirm_payment_received(
origin,
treasury_id: u32,
salt: u32,
sender: T::AccountId,
amount: BalanceOf<T>,
) -> DispatchResult {
let recipient = ensure_signed(origin)?;
let _ = OffChainTreasuryIDs::get(treasury_id).ok_or(Error::<T>::CannotUseOffChainBankThatDNE)?;
let payment_confirmed_received = Payment::new(salt, sender.clone(), recipient.clone(), amount);
Self::recipient_confirms_payment_received(treasury_id, payment_confirmed_received)?;
Self::deposit_event(RawEvent::RecipientConfirmsPaymentReceived(treasury_id, sender, recipient, amount, salt));
Ok(())
}
}
}
impl<T: Trait> Module<T> {
fn is_sudo_account(who: &T::AccountId) -> bool {
<<T as Trait>::Organization as SupervisorPermissions<u32, ShareID, T::AccountId>>::is_sudo_account(
who,
)
}
fn is_organization_supervisor(organization: u32, who: &T::AccountId) -> bool {
<<T as Trait>::Organization as SupervisorPermissions<u32, ShareID, T::AccountId>>::is_organization_supervisor(organization, who)
}
fn account_is_member_of_formed_organization(
org: FormedOrganization,
who: &T::AccountId,
) -> bool {
match org {
FormedOrganization::FlatOrg(org_id) => {
<<T as Trait>::Organization as OrgChecks<u32, T::AccountId>>::check_membership_in_org(org_id, who)
},
FormedOrganization::FlatShares(org_id, share_id) => {
<<T as Trait>::Organization as ShareGroupChecks<u32, ShareID, T::AccountId>>::check_membership_in_share_group(org_id, ShareID::Flat(share_id), who)
},
FormedOrganization::WeightedShares(org_id, share_id) => {
<<T as Trait>::Organization as ShareGroupChecks<u32, ShareID, T::AccountId>>::check_membership_in_share_group(org_id, ShareID::WeightedAtomic(share_id), who)
},
}
}
}
impl<T: Trait> IDIsAvailable<OffChainTreasuryID> for Module<T> {
fn id_is_available(id: OffChainTreasuryID) -> bool {
OffChainTreasuryIDs::get(id).is_none()
}
}
impl<T: Trait> IDIsAvailable<(OffChainTreasuryID, Payment<T::AccountId, BalanceOf<T>>)>
for Module<T>
{
fn id_is_available(id: (OffChainTreasuryID, Payment<T::AccountId, BalanceOf<T>>)) -> bool {
<OffChainTransfers<T>>::get(id.0, id.1).is_none()
}
}
pub trait GenerateUniqueKeyID<KeyId> {
fn generate_unique_key_id(proposed: KeyId) -> KeyId;
}
impl<T: Trait> GenerateUniqueKeyID<(OffChainTreasuryID, Payment<T::AccountId, BalanceOf<T>>)>
for Module<T>
{
fn generate_unique_key_id(
proposed: (OffChainTreasuryID, Payment<T::AccountId, BalanceOf<T>>),
) -> (OffChainTreasuryID, Payment<T::AccountId, BalanceOf<T>>) {
if !Self::id_is_available(proposed.clone()) {
let mut new_deposit_id = proposed.1.iterate_salt();
while !Self::id_is_available((proposed.0, new_deposit_id.clone())) {
new_deposit_id = new_deposit_id.iterate_salt();
}
(proposed.0, new_deposit_id)
} else {
proposed
}
}
}
impl<T: Trait> GenerateUniqueID<OffChainTreasuryID> for Module<T> {
fn generate_unique_id() -> OffChainTreasuryID {
let mut current_nonce = OffChainTreasuryIDNonce::get() + 1;
while !Self::id_is_available(current_nonce) {
current_nonce += 1;
}
OffChainTreasuryIDNonce::put(current_nonce);
current_nonce
}
}
impl<T: Trait> SupportedOrganizationShapes for Module<T> {
type FormedOrgId = FormedOrganization;
}
impl<T: Trait> RegisterOffChainBankAccount for Module<T> {
type TreasuryId = OffChainTreasuryID;
fn register_off_chain_bank_account(
org: Self::FormedOrgId,
) -> Result<Self::TreasuryId, DispatchError> {
let generated_id = Self::generate_unique_id();
OffChainTreasuryIDs::insert(generated_id, org);
Ok(generated_id)
}
}
impl<T: Trait> OffChainBank for Module<T> {
type Payment = Payment<T::AccountId, BalanceOf<T>>;
fn sender_claims_payment_sent(id: Self::TreasuryId, payment: Self::Payment) -> Self::Payment {
let generated_id = Self::generate_unique_key_id((id, payment));
let new_confirmation = PaymentConfirmation::from_sender_claims();
<OffChainTransfers<T>>::insert(id, generated_id.1.clone(), new_confirmation);
generated_id.1
}
fn recipient_confirms_payment_received(
id: Self::TreasuryId,
payment: Self::Payment,
) -> DispatchResult {
let current_confirmation = <OffChainTransfers<T>>::get(id, payment.clone())
.ok_or(Error::<T>::SenderMustClaimPaymentSentForRecipientToConfirm)?;
let full_confirm = current_confirmation.put_recipient_confirms();
<OffChainTransfers<T>>::insert(id, payment, full_confirm);
Ok(())
}
fn check_payment_confirmation(id: Self::TreasuryId, payment: Self::Payment) -> bool {
if let Some(transfer_state) = <OffChainTransfers<T>>::get(id, payment) {
return transfer_state.total_confirmation();
}
false
}
}