Manages the set of current members, their profile, status.
Membership
These are constants that are only part of making this document legible, they are not part of the public interface of the module.
const DEFAULT_FIRST_MEMBER_ID: u64 = 1;
const FIRST_PAID_TERMS_ID: u64 = 1;
const DEFAULT_PAID_TERM_ID: u64 = 0;
const DEFAULT_PAID_TERM_FEE: u64 = 100;
const DEFAULT_PAID_TERM_TEXT: &str = "Default Paid Term TOS...";
const DEFAULT_MIN_HANDLE_LENGTH: u32 = 5;
const DEFAULT_MAX_HANDLE_LENGTH: u32 = 40;
const DEFAULT_MAX_AVATAR_URI_LENGTH: u32 = 1024;
const DEFAULT_MAX_ABOUT_TEXT_LENGTH: u32 = 2048;
Trait
These are the traits which provide the interfaces to services external to this module, such as peer modules which must run in the same runtime for example.
Event type. ???
Member identifier type.
Paid term identifier type.
Subscription identifier type.
Roles module type.
These are the public type of the module, that is they are used in the storage system and in signature of public methods. All other types are omitted.
pub enum EntryMethod<T: Trait> {
Paid(T::PaidTermId),
Screening(T::AccountId),
}
pub struct Profile<T: Trait> {
pub id: T::MemberId,
pub handle: Vec<u8>,
pub avatar_uri: Vec<u8>,
pub about: Vec<u8>,
pub registered_at_block: T::BlockNumber,
pub registered_at_time: T::Moment,
pub entry: EntryMethod<T>,
pub suspended: bool,
pub subscription: Option<T::SubscriptionId>,
}
pub struct PaidMembershipTerms<T: Trait> {
/// Unique identifier - the term id
pub id: T::PaidTermId,
/// Quantity of native tokens which must be provably burned
pub fee: BalanceOf<T>,
/// String of capped length describing human readable conditions which are being agreed upon
pub text: Vec<u8>,
}
pub struct UserInfo {
pub handle: Option<Vec<u8>>,
pub avatar_uri: Option<Vec<u8>>,
pub about: Option<Vec<u8>>,
}
The following is the payload used with decl_storage
.
/// MemberId's start at this value
pub FirstMemberId get(first_member_id) config(first_member_id): T::MemberId = T::MemberId::sa(DEFAULT_FIRST_MEMBER_ID);
/// MemberId to assign to next member that is added to the registry
pub NextMemberId get(next_member_id) build(|config: &GenesisConfig<T>| config.first_member_id): T::MemberId = T::MemberId::sa(DEFAULT_FIRST_MEMBER_ID);
/// Mapping of member ids to their corresponding primary accountid
pub AccountIdByMemberId get(account_id_by_member_id) : map T::MemberId => T::AccountId;
/// Mapping of members' account ids to their member id.
pub MemberIdByAccountId get(member_id_by_account_id) : map T::AccountId => Option<T::MemberId>;
/// Mapping of member's id to their membership profile
// Value is Option<Profile> because it is not meaningful to have a Default value for Profile
pub MemberProfile get(member_profile) : map T::MemberId => Option<Profile<T>>;
/// Registered unique handles and their mapping to their owner
pub Handles get(handles) : map Vec<u8> => Option<T::MemberId>;
/// Next paid membership terms id
pub NextPaidMembershipTermsId get(next_paid_membership_terms_id) : T::PaidTermId = T::PaidTermId::sa(FIRST_PAID_TERMS_ID);
/// Paid membership terms record
// Remember to add _genesis_phantom_data: std::marker::PhantomData{} to membership
// genesis config if not providing config() or extra_genesis
pub PaidMembershipTermsById get(paid_membership_terms_by_id) build(|config: &GenesisConfig<T>| {
// This method only gets called when initializing storage, and is
// compiled as native code. (Will be called when building `raw` chainspec)
// So it can't be relied upon to initialize storage for runtimes updates.
// Initialization for updated runtime is done in run_migration()
let mut terms: PaidMembershipTerms<T> = Default::default();
terms.fee = config.default_paid_membership_fee;
vec![(terms.id, terms)]
}) : map T::PaidTermId => Option<PaidMembershipTerms<T>>;
/// Active Paid membership terms
pub ActivePaidMembershipTerms get(active_paid_membership_terms) : Vec<T::PaidTermId> = vec![T::PaidTermId::sa(DEFAULT_PAID_TERM_ID)];
/// Is the platform is accepting new members or not
pub NewMembershipsAllowed get(new_memberships_allowed) : bool = true;
pub ScreeningAuthority get(screening_authority) : Option<T::AccountId>;
// User Input Validation parameters - do these really need to be state variables
// I don't see a need to adjust these in future?
pub MinHandleLength get(min_handle_length) : u32 = DEFAULT_MIN_HANDLE_LENGTH;
pub MaxHandleLength get(max_handle_length) : u32 = DEFAULT_MAX_HANDLE_LENGTH;
pub MaxAvatarUriLength get(max_avatar_uri_length) : u32 = DEFAULT_MAX_AVATAR_URI_LENGTH;
pub MaxAboutTextLength get(max_about_text_length) : u32 = DEFAULT_MAX_ABOUT_TEXT_LENGTH;
...
The following is the payload used with decl_events
.
pub enum Event<T> where
<T as system::Trait>::AccountId,
<T as Trait>::MemberId {
MemberRegistered(MemberId, AccountId),
MemberUpdatedAboutText(MemberId),
MemberUpdatedAvatar(MemberId),
MemberUpdatedHandle(MemberId),
}
buy_membership
origin: Origin, paid_terms_id: PaidTermId, user_info: UserInfo
Establish new membership through payment.
!ensure_signed(origin)
!<NewMembershipsAllowed<T>>::get()
<MemberIdByAccountId<T>>::exists(origin)
T::Roles::is_role_account(origin)
!<ActivePaidMembershipTerms<T>>::iter().any(|x| x == paid_terms_id)
Note: This is a bug, should not be checked
!<PaidMembershipTermsById<T>>::exists(paid_terms_id)
!T::Currency::can_slash(who, terms.fee)
where
let terms = <PaidMembershipTermsById<T>>::get(paid_terms_id);
...
...
...
...
...
NO_ERROR
The following conditions must all hold
[T::Currency::balance]'(who) == T::Currency::balance(who) - <PaidMembershipTermsById<T>>::get(paid_terms_id).fee &&
[<MemberIdByAccountId<T>>]' == <MemberIdByAccountId<T>> [+] (who, <NextMemberId<T>>::get()) &&
[<AccountIdByMemberId<T>>]' == <AccountIdByMemberId<T>> [+] (<NextMemberId<T>>::get(), who)) == who &&
[<MemberProfile<T>>]' == <MemberProfile<T>> [+] (<NextMemberId<T>>::get(), profile) &&
[<Handles<T>>]' == <Handles<T>> [+] (user_info.handle.clone(), <NextMemberId<T>>::get()) &&
[<NextMemberId<T>>]' == <NextMemberId<T>> + 1
where
let profile = Profile {
id: <NextMemberId<T>>::get(),
handle: user_info.handle,
avatar_uri: user_info.avatar_uri,
about: user_info.about,
registered_at_block: <system::Module<T>>::block_number(),
registered_at_time: <timestamp::Module<T>>::now(),
entry: EntryMethod::Paid(paid_terms_id)),
suspended: false,
subscription: None,
}
Ok(<NextMemberId<T>>::get())
MemberRegistered(<NextMemberId<T>>::get(), who.clone())
change_member_about_text
origin: Origin, text: Vec<u8>
Change about text on membership.
!ensure_signed(origin)
!<MemberIdByAccountId<T>>::exists(who)
!<AccountIdByMemberId<T>>::exists(member_id)
where
let member_id = <MemberIdByAccountId<T>>::get(who)
!<MemberProfile<T>>::exists(member_id)
NO_ERROR
The following conditions must all hold
<MemberProfile<T>>::get(member_id).text === text.truncate(Self::max_about_text_length() as usize);
Ok()
RawEvent::MemberUpdatedAboutText(id)
change_member_avatar
fill in
change_member_handle
fill in
update_profile
fill in
add_screened_member
fill in
set_screening_authority
fill in
is_active_member
...
lookup_member_id
...
lookup_account_by_member_id