diff --git a/.gitignore b/.gitignore index 93684ba..be44988 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ cache/ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +client/tmp/ .DS_Store \ No newline at end of file diff --git a/client/Cargo.toml b/client/Cargo.toml index 73f6d2b..94a8efb 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -9,27 +9,31 @@ readme = "README.md" description = "Submit extrinsics (transactions) to the sunshine node via RPC" keywords = ["sunshine", "substrate", "blockchain"] +[features] +light-client = ["substrate-subxt-light-client", "sunshine-node"] + [dependencies] async-std = "=1.5.0" ipld-block-builder = "0.3.0" libipld = { version = "0.3.0", features = ["dag-json"] } serde = { version = "1.0.111", features = ["derive"] } -# TODO export error in libipld sled = "0.32.0-rc1" serde_json = "1.0.53" ipfs-embed = "0.1.0" -# point at git master, could be done with patch.crates.io = giturl substrate-subxt = "0.8.0" surf = "1.0.3" thiserror = "1.0.19" -utils-identity = { version = "0.1.0", path = "../utils" } -# substrate sp-runtime = { version = "2.0.0-rc3", default-features = false } sp-core = { version = "2.0.0-rc3", default-features = false } codec = { version = "1.3.0", package = "parity-scale-codec" } frame-support = "2.0.0-rc3" pallet-indices = "2.0.0-rc3" +derive-new = {version = "0.5", default-features=false} +substrate-subxt-light-client = { package = "substrate-subxt", git = "https://github.com/dvc94ch/substrate-subxt", branch = "lightclient", optional = true } +sunshine-node = { path = "../node", default-features = false, optional = true } +keystore = {package = "keybase-keystore", git = "https://github.com/sunshine-protocol/substrate-identity"} # local deps +utils-identity = { version = "0.1.0", path = "../utils" } util = { package = "sunshine-util", path = "../modules/util", default-features = false } org = {package = "sunshine-org", path = "../modules/org", default-features=false } vote = { package = "sunshine-vote", path = "../modules/vote", default-features=false} diff --git a/client/examples/org.rs b/client/examples/org.rs new file mode 100644 index 0000000..e35c720 --- /dev/null +++ b/client/examples/org.rs @@ -0,0 +1,42 @@ +use sp_keyring::AccountKeyring; +//#[cfg(feature = "light-client")] +//use sunshine_client::ChainType; +use ipfs_embed::{Config, Store}; +use ipld_block_builder::{BlockBuilder, Codec}; +use keystore::{DeviceKey, KeyStore, Password}; +use sp_core::crypto::Pair; +use sunshine_client::{ClientBuilder, Error, Runtime, SunClient}; +// use libipld::cid::{Cid, Codec}; +// use libipld::multihash::Sha2_256; +// use utils_identity::cid::CidBytes; + +#[async_std::main] +async fn main() -> Result<(), Error> { + env_logger::init(); + let subxt = ClientBuilder::new().build().await.unwrap(); + let db = sled::open("tmp/db")?; + let ipld_tree = db.open_tree("ipld_tree")?; + let config = Config::from_tree(ipld_tree); + let store = Store::new(config)?; + let codec = Codec::new(); + let ipld = BlockBuilder::new(store, codec); + let keystore = KeyStore::new("/tmp/keystore"); + let alice_seed: [u8; 32] = AccountKeyring::Alice.into(); + let _ = keystore.initialize( + &DeviceKey::from_seed(alice_seed), + &Password::from("password".to_string()), + )?; + // //#[cfg(not(feature = "light-client"))] + let client = SunClient::new(subxt, keystore, ipld); + // #[cfg(feature = "light-client")] + // let client = Sunshine::new("/tmp/db", signer, ChainType::Development).await?; + let account_id = sp_keyring::AccountKeyring::Alice.to_account_id(); + client.issue_shares(1u64, account_id, 10u64).await?; + + // println!( + // "Account {:?} was issued {:?} shares for organization {:?}", + // event.who, event.amount, event.org, + // ); + + Ok(()) +} diff --git a/client/examples/reserve_shares_and_watch.rs b/client/examples/reserve_shares_and_watch.rs deleted file mode 100644 index 466641c..0000000 --- a/client/examples/reserve_shares_and_watch.rs +++ /dev/null @@ -1,23 +0,0 @@ -use sp_keyring::AccountKeyring; -#[cfg(feature = "light-client")] -use sunshine_client::ChainType; -use sunshine_client::{Error, Sunshine}; - -#[async_std::main] -async fn main() -> Result<(), Error> { - env_logger::init(); - let signer = AccountKeyring::Alice.pair(); - #[cfg(not(feature = "light-client"))] - let client = Sunshine::new("/tmp/db", signer).await?; - #[cfg(feature = "light-client")] - let client = Sunshine::new("/tmp/db", signer, ChainType::Development).await?; - - let event = client.reserve_shares(1, 1).await?; - - println!( - "Account {:?} reserved {:?} shares with share id {:?} for organization {:?}", - event.account, event.reserved, event.share, event.org, - ); - - Ok(()) -} diff --git a/client/src/error.rs b/client/src/error.rs index 68670c1..5927190 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -11,6 +11,12 @@ pub enum Error { Sled(#[from] sled::Error), #[error("{0}")] Ipfs(#[from] ipfs_embed::Error), + #[error(transparent)] + Keystore(#[from] keystore::Error), + #[error("keystore already initialized")] + KeystoreInitialized, #[error("event not found")] EventNotFound, } + +pub type Result = core::result::Result; diff --git a/client/src/lib.rs b/client/src/lib.rs index a6e263c..243fdc5 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -3,6 +3,9 @@ #[macro_use] extern crate substrate_subxt; +#[macro_use] +extern crate derive_new; + mod error; #[cfg(feature = "light-client")] mod light_client; @@ -13,4 +16,5 @@ mod sunshine; pub use error::Error; #[cfg(feature = "light-client")] pub use light_client::ChainType; -pub use sunshine::Sunshine; +pub use runtime::{ClientBuilder, Runtime}; +pub use sunshine::SunClient; diff --git a/client/src/runtime.rs b/client/src/runtime.rs index 1445e1d..28da299 100644 --- a/client/src/runtime.rs +++ b/client/src/runtime.rs @@ -10,15 +10,21 @@ use sp_runtime::{ use std::marker::PhantomData; use substrate_subxt::{ balances::{AccountData, Balances}, - system::System, - CheckEra, CheckGenesis, CheckNonce, CheckSpecVersion, CheckTxVersion, CheckWeight, SignedExtra, + system::System, //sp_core::crypto::Pair, + CheckEra, + CheckGenesis, + CheckNonce, + CheckSpecVersion, + CheckTxVersion, + CheckWeight, + SignedExtra, }; use utils_identity::cid::CidBytes; pub type Pair = sp_core::sr25519::Pair; pub type ClientBuilder = substrate_subxt::ClientBuilder; pub type Client = substrate_subxt::Client; -//pub type XtBuilder = substrate_subxt::XtBuilder; +pub type PairSigner = substrate_subxt::PairSigner; /// Concrete type definitions compatible w/ sunshine's runtime aka `suntime` #[derive(Debug, Clone, Eq, PartialEq)] @@ -30,7 +36,7 @@ impl System for Runtime { type Hash = sp_core::H256; type Hashing = BlakeTwo256; type AccountId = <::Signer as IdentifyAccount>::AccountId; - type Address = pallet_indices::address::Address; + type Address = pallet_indices::address::Address; type Header = Header; type Extrinsic = OpaqueExtrinsic; type AccountData = AccountData<::Balance>; diff --git a/client/src/srml/bank.rs b/client/src/srml/bank.rs new file mode 100644 index 0000000..c41a3df --- /dev/null +++ b/client/src/srml/bank.rs @@ -0,0 +1,27 @@ +use codec::{Codec, Decode, Encode}; +use frame_support::Parameter; +use sp_runtime::traits::{AtLeast32Bit, MaybeSerializeDeserialize, Member, Zero}; +use std::fmt::Debug; +use substrate_subxt::system::{System, SystemEventsDecoder}; +use util::{organization::Organization, share::ShareProfile}; +use crate::srml::org::Org; + +/// The subset of the bank trait and its inherited traits that the client must inherit +#[module] +pub trait Bank: System + Org { + /// Cid type + type IpfsReference: Parameter + Member + Default; + + /// Identifier for bank-related maps + type BankAssociatedId: Parameter + + Member + + AtLeast32Bit + + Codec + + Default + + Copy + + MaybeSerializeDeserialize + + Debug + + PartialOrd + + PartialEq + + Zero; +} \ No newline at end of file diff --git a/client/src/srml/mod.rs b/client/src/srml/mod.rs index 1cf05dc..2bf1852 100644 --- a/client/src/srml/mod.rs +++ b/client/src/srml/mod.rs @@ -1,2 +1,2 @@ pub mod org; -//pub mod vote; +pub mod vote; diff --git a/client/src/srml/org.rs b/client/src/srml/org.rs index 0135249..29bd9a2 100644 --- a/client/src/srml/org.rs +++ b/client/src/srml/org.rs @@ -69,7 +69,7 @@ pub struct TotalIssuanceStore { pub struct ProfileStore<'a, T: Org> { #[store(returns = ShareProfile)] pub org: T::OrgId, - pub account: &'a ::AccountId, + pub who: &'a ::AccountId, } #[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] @@ -84,6 +84,40 @@ pub struct OrganizationSizeStore { pub org: T::OrgId, } +// ~~ (Calls, Events) ~~ + +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct RegisterFlatOrgCall<'a, T: Org> { + pub sudo: Option<::AccountId>, + pub parent_org: Option, + pub constitution: T::IpfsReference, + pub members: &'a [::AccountId], +} + +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct FlatOrgRegisteredEvent { + pub caller: ::AccountId, + pub new_id: T::OrgId, + pub constitution: T::IpfsReference, + pub total: u32, +} + +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct RegisterWeightedOrgCall<'a, T: Org> { + pub sudo: Option<::AccountId>, + pub parent_org: Option, + pub constitution: T::IpfsReference, + pub members: &'a [(::AccountId, T::Shares)], +} + +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct WeightedOrgRegisteredEvent { + pub caller: ::AccountId, + pub new_id: T::OrgId, + pub constitution: T::IpfsReference, + pub total: T::Shares, +} + #[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] pub struct IssueSharesCall<'a, T: Org> { pub org: T::OrgId, @@ -91,6 +125,13 @@ pub struct IssueSharesCall<'a, T: Org> { pub amount: T::Shares, } +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct SharesIssuedEvent { + pub org: T::OrgId, + pub who: ::AccountId, + pub amount: T::Shares, +} + #[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] pub struct BurnSharesCall<'a, T: Org> { pub org: T::OrgId, @@ -98,60 +139,53 @@ pub struct BurnSharesCall<'a, T: Org> { pub amount: T::Shares, } -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct BatchIssueSharesCall<'a, T: Org> { +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct SharesBurnedEvent { pub org: T::OrgId, - pub new_accounts: &'a [(::AccountId, T::Shares)], + pub who: ::AccountId, + pub amount: T::Shares, } #[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct BatchBurnSharesCall<'a, T: Org> { +pub struct BatchIssueSharesCall<'a, T: Org> { pub org: T::OrgId, pub new_accounts: &'a [(::AccountId, T::Shares)], } -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct LockSharesCall<'a, T: Org> { +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct SharesBatchIssuedEvent { pub org: T::OrgId, - pub who: &'a ::AccountId, + pub amount: T::Shares, } #[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct UnlockSharesCall<'a, T: Org> { +pub struct BatchBurnSharesCall<'a, T: Org> { pub org: T::OrgId, - pub who: &'a ::AccountId, + pub old_accounts: &'a [(::AccountId, T::Shares)], } -#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct ReserveSharesCall<'a, T: Org> { +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct SharesBatchBurnedEvent { pub org: T::OrgId, - pub who: &'a ::AccountId, + pub amount: T::Shares, } #[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] -pub struct UnreserveSharesCall<'a, T: Org> { +pub struct LockSharesCall<'a, T: Org> { pub org: T::OrgId, pub who: &'a ::AccountId, } #[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct SharesReservedEvent { - pub org: T::OrgId, - pub who: ::AccountId, - pub amount: T::Shares, -} - -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct SharesUnReservedEvent { +pub struct SharesLockedEvent { pub org: T::OrgId, pub who: ::AccountId, - pub amount: T::Shares, } -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct SharesLockedEvent { +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct UnlockSharesCall<'a, T: Org> { pub org: T::OrgId, - pub who: ::AccountId, + pub who: &'a ::AccountId, } #[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] @@ -160,210 +194,28 @@ pub struct SharesUnlockedEvent { pub who: ::AccountId, } -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct SharesIssuedEvent { +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct ReserveSharesCall<'a, T: Org> { pub org: T::OrgId, - pub account: ::AccountId, - pub amount: T::Shares, + pub who: &'a ::AccountId, } #[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct SharesBurnedEvent { +pub struct SharesReservedEvent { pub org: T::OrgId, - pub account: ::AccountId, + pub who: ::AccountId, pub amount: T::Shares, } -#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct SharesBatchIssuedEvent { +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct UnreserveSharesCall<'a, T: Org> { pub org: T::OrgId, - pub amount: T::Shares, + pub who: &'a ::AccountId, } #[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] -pub struct SharesBatchBurnedEvent { +pub struct SharesUnReservedEvent { pub org: T::OrgId, + pub who: ::AccountId, pub amount: T::Shares, } - -#[cfg(test)] -mod tests { - use super::*; - use crate::runtime::{Runtime, RuntimeExtra}; - - subxt_test!({ - name: issue_shares, - runtime: Runtime, - extra: RuntimeExtra, - /*state: { - issuance: TotalIssuanceStore { - org: 1, - share: 1, - }, - },*/ - step: { - call: IssueSharesCall { - org: 1, - share: 1, - who: &alice, - amount: 10, - }, - event: SharesIssuedEvent { - org: 1, - share: 1, - account: alice.clone(), - amount: 10, - }, - /*assert: { - assert_eq!(pre.issuance + 20, post.issuance); - },*/ - }, - step: { - call: BurnSharesCall { - org: 1, - share: 1, - who: &alice, - amount: 10, - }, - event: SharesBurnedEvent { - org: 1, - share: 1, - account: alice.clone(), - amount: 10, - }, - /*assert: { - assert_eq!(pre.issuance - 20, post.issuance); - },*/ - } - }); - - subxt_test!({ - name: batch_issue_shares, - runtime: Runtime, - extra: RuntimeExtra, - /*state: { - issuance: TotalIssuanceStore { - org: 1, - share: 1, - }, - },*/ - step: { - call: BatchIssueSharesCall { - org: 1, - share: 1, - new_accounts: &[(alice.clone(), 10), (bob.clone(), 10)], - }, - event: SharesBatchIssuedEvent { - org: 1, - share: 1, - amount: 20, - }, - /*assert: { - assert_eq!(pre.issuance + 20, post.issuance); - },*/ - }, - step: { - call: BatchBurnSharesCall { - org: 1, - share: 1, - new_accounts: &[(alice.clone(), 10), (bob.clone(), 10)], - }, - event: SharesBatchBurnedEvent { - org: 1, - share: 1, - amount: 20, - }, - /*assert: { - assert_eq!(pre.issuance - 20, post.issuance); - },*/ - } - }); - - subxt_test!({ - name: reserve_shares, - runtime: Runtime, - extra: RuntimeExtra, - /*state: { - profile: ProfileStore { - prefix: UUID2::new(1, 1), - account_id: &alice, - }, - },*/ - step: { - call: ReserveSharesCall { - org: 1, - share: 1, - who: &alice, - }, - event: SharesReservedEvent { - org: 1, - share: 1, - account: alice.clone(), - reserved: event.reserved, //pre.profile.times_reserved() + 1, - }, - /*assert: { - assert_eq!(pre.profile.times_reserved() + 1, post.profile.times_reserved()); - },*/ - }, - step: { - call: UnreserveSharesCall { - org: 1, - share: 1, - who: &alice, - }, - event: SharesUnReservedEvent { - org: 1, - share: 1, - account: alice.clone(), - reserved: event.reserved, //pre.profile.times_reserved() - 1, - }, - /*assert: { - assert_eq!(pre.profile.times_reserved() - 1, post.profile.times_reserved()); - },*/ - } - }); - - subxt_test!({ - name: lock_shares, - runtime: Runtime, - extra: RuntimeExtra, - /*state: { - profile: ProfileStore { - prefix: UUID2::new(1, 1), - account_id: &alice, - }, - },*/ - step: { - call: LockSharesCall { - org: 1, - share: 1, - who: &alice, - }, - event: SharesLockedEvent { - org: 1, - share: 1, - account: alice.clone(), - }, - /*assert: { - assert_eq!(pre.profile.is_unlocked(), true); - assert_eq!(post.profile.is_unlocked(), false); - },*/ - }, - step: { - call: UnlockSharesCall { - org: 1, - share: 1, - who: &alice, - }, - event: SharesUnlockedEvent { - org: 1, - share: 1, - account: alice.clone(), - }, - /*assert: { - assert_eq!(pre.profile.is_unlocked(), false); - assert_eq!(post.profile.is_unlocked(), true); - },*/ - } - }); -} diff --git a/client/src/srml/vote.rs b/client/src/srml/vote.rs index 28f0cc3..31a32da 100644 --- a/client/src/srml/vote.rs +++ b/client/src/srml/vote.rs @@ -1,22 +1,17 @@ -//! Implements support for the vote_yesno module - -use codec::Codec; +use crate::srml::org::{Org, OrgEventsDecoder}; +use codec::{Codec, Decode, Encode}; use frame_support::Parameter; use sp_runtime::{ traits::{AtLeast32Bit, MaybeSerializeDeserialize, Member, Zero}, Permill, }; use std::fmt::Debug; -use substrate_subxt::{system::System, Call}; -use substrate_subxt_proc_macro::*; -use util::{ - traits::{GroupMembership, LockableProfile, ReservableProfile, ShareBank}, - voteyesno::VoterYesNoView, -}; +use substrate_subxt::system::{System, SystemEventsDecoder}; +use util::vote::{Vote as VoteVector, VoteState, VoterView}; -/// The subset of the `vote_yesno::Trait` that a client must implement. +/// The subset of the `vote::Trait` that a client must implement. #[module] -pub trait VoteYesNo: System { +pub trait Vote: System + Org { /// The identifier for each vote; ProposalId => Vec s.t. sum(VoteId::Outcomes) => ProposalId::Outcome type VoteId: Parameter + Member @@ -37,174 +32,88 @@ pub trait VoteYesNo: System { + MaybeSerializeDeserialize + Debug + Zero; +} + +// ~~ Values ~~ - /// An instance of the shares module - type ShareData: GroupMembership - + RegisterShareGroup - + ReservableProfile - + LockableProfile - + ShareBank; +#[derive(Clone, Debug, Eq, PartialEq, Encode)] +pub struct VoteIdCounterStore { + pub nonce: T::VoteId, } -/// The share identifier type -pub type Shares = - <::ShareData as ShareRegistration<::AccountId>>::ShareId; - -/// The organization identifier type -pub type OrgId = - <::ShareData as ShareRegistration<::AccountId>>::OrgId; - -const MODULE: &str = "VoteYesNo"; -const CREATE_SHARE_WEIGHTED_PERCENTAGE_VOTE_NO_EXPIRY: &str = - "create_share_weighted_percentage_threshold_vote"; -const CREATE_SHARE_WEIGHTED_COUNT_VOTE_NO_EXPIRY: &str = - "create_share_weighted_count_threshold_vote"; -const CREATE_1P1V_PERCENTAGE_VOTE_NO_EXPIRY: &str = "create_1p1v_percentage_threshold_vote"; -const CREATE_1P1V_COUNT_VOTE_NO_EXPIRY: &str = "create_1p1v_count_threshold_vote"; -const SUBMIT_VOTE: &str = "submit_vote"; - -/// Arguments for creating a share weighted vote with thresholds based on percents -#[derive(codec::Encode)] -pub struct CreateShareWeightedPercentageVoteArgs { - organization: OrgId, - share_id: ShareId, - support_requirement: Permill, - turnout_requirement: Permill, +#[derive(Clone, Debug, Eq, PartialEq, Encode)] +pub struct OpenVoteCounterStore { + pub counter: u32, } -/// Arguments for creating a share weighted vote with thresholds based on signal amounts -#[derive(codec::Encode)] -pub struct CreateShareWeightedCountVoteArgs { - organization: OrgId, - share_id: ShareId, - support_requirement: T::Signal, - turnout_requirement: T::Signal, +// ~~ Maps ~~ + +#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] +pub struct VoteStateStore { + #[store(returns = VoteState::BlockNumber, ::IpfsReference>)] + pub vote: T::VoteId, } -/// Arguments for creating a 1p1v vote with thresholds based on signal amounts -#[derive(codec::Encode)] -pub struct Create1P1VPercentageVoteArgs { - organization: OrgId, - share_id: ShareId, - support_requirement: T::Signal, - turnout_requirement: T::Signal, +#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] +pub struct TotalSignalIssuanceStore { + #[store(returns = T::Signal)] + pub vote: T::VoteId, } -/// Arguments for creating a 1p1v vote with thresholds based on signal amounts -#[derive(codec::Encode)] -pub struct Create1P1VCountVoteArgs { - organization: OrgId, - share_id: ShareId, - support_requirement: T::Signal, - turnout_requirement: T::Signal, +#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)] +pub struct VoteLoggerStore { + #[store(returns = VoteVector::IpfsReference>)] + pub vote: T::VoteId, + pub who: ::AccountId, } -/// Arguments for submitting a vote -#[derive(codec::Encode)] -pub struct SubmitVoteArgs { - organization: OrgId, - share_id: ShareId, - vote_id: T::Signal, - voter: ::AccountId, - direction: VoterYesNoView, - magnitude: Option, +// ~~ Calls ~~ + +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct CreateWeightedPercentageThresholdVoteCall { + pub topic: Option<::IpfsReference>, + pub organization: T::OrgId, + pub passage_threshold_pct: Permill, + pub turnout_threshold_pct: Permill, + pub duration: Option<::BlockNumber>, } -/// Create share weighted percentage threshold vote in the context of an organizational share group -pub fn create_share_weighted_percentage_vote( - organization: OrgId, - share_id: ShareId, - support_requirement: Permill, - turnout_requirement: Permill, -) -> Call> { - Call::new( - MODULE, - CREATE_SHARE_WEIGHTED_PERCENTAGE_VOTE_NO_EXPIRY, - CreateShareWeightedPercentageVoteArgs { - organization, - share_id, - support_requirement, - turnout_requirement, - }, - ) +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct CreateWeightedCountThresholdVoteCall { + pub topic: Option<::IpfsReference>, + pub organization: T::OrgId, + pub support_requirement: T::Signal, + pub turnout_requirement: T::Signal, + pub duration: Option<::BlockNumber>, } -/// Create share weighted count threshold vote in the context of an organizational share group -pub fn create_share_weighted_count_vote( - organization: OrgId, - share_id: ShareId, - support_requirement: T::Signal, - turnout_requirement: T::Signal, -) -> Call> { - Call::new( - MODULE, - CREATE_SHARE_WEIGHTED_COUNT_VOTE_NO_EXPIRY, - CreateShareWeightedCountVoteArgs { - organization, - share_id, - support_requirement, - turnout_requirement, - }, - ) +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct CreateUnanimousConsentVoteCall { + pub topic: Option<::IpfsReference>, + pub organization: T::OrgId, + pub duration: Option<::BlockNumber>, } -/// Create share weighted count threshold vote in the context of an organizational share group -pub fn create_1p1v_percentage_vote( - organization: OrgId, - share_id: ShareId, - support_requirement: T::Signal, - turnout_requirement: T::Signal, -) -> Call> { - Call::new( - MODULE, - CREATE_1P1V_PERCENTAGE_VOTE_NO_EXPIRY, - Create1P1VPercentageVoteArgs { - organization, - share_id, - support_requirement, - turnout_requirement, - }, - ) +#[derive(Clone, Debug, Eq, PartialEq, Call, Encode)] +pub struct SubmitVoteCall { + pub vote_id: T::VoteId, + pub direction: VoterView, + pub magnitude: Option, + pub justification: Option<::IpfsReference>, } -/// Create 1 account 1 vote count threshold vote in the context of an organizational share group -pub fn create_1p1v_count_vote( - organization: OrgId, - share_id: ShareId, - support_requirement: T::Signal, - turnout_requirement: T::Signal, -) -> Call> { - Call::new( - MODULE, - CREATE_1P1V_COUNT_VOTE_NO_EXPIRY, - Create1P1VCountVoteArgs { - organization, - share_id, - support_requirement, - turnout_requirement, - }, - ) +// ~~ Events ~~ + +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct NewVoteStartedEvent { + pub caller: ::AccountId, + pub org: T::OrgId, + pub new_vote_id: T::VoteId, } -/// Submits a vote -pub fn submit_vote( - organization: OrgId, - share_id: ShareId, - vote_id: T::Signal, - voter: ::AccountId, - direction: VoterYesNoView, - magnitude: Option, -) -> Call> { - Call::new( - MODULE, - SUBMIT_VOTE, - SubmitVoteArgs { - organization, - share_id, - vote_id, - voter, - direction, - magnitude, - }, - ) +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct VotedEvent { + pub vote_id: T::VoteId, + pub voter: ::AccountId, + pub view: VoterView, } diff --git a/client/src/sunshine.rs b/client/src/sunshine.rs index ed903c6..bd0ea70 100644 --- a/client/src/sunshine.rs +++ b/client/src/sunshine.rs @@ -1,53 +1,209 @@ -use crate::error::Error; +use crate::error::{Error, Result}; #[cfg(feature = "light-client")] use crate::light_client::ChainType; -use crate::runtime::{Client, Pair, Runtime}; //XtBuilder - // use crate::srml::org::*; -use ipfs_embed::{Config, Store}; +use crate::runtime::{Client, Pair, PairSigner, Runtime}; +use crate::srml::org::*; +use ipfs_embed::Store; use ipld_block_builder::{BlockBuilder, Codec}; -use sp_core::Pair as _; -use std::path::Path; -use substrate_subxt::system::*; +use keystore::{DeviceKey, KeyStore, Password}; +// use std::path::Path; +use substrate_subxt::sp_core::crypto::Pair as SubPair; +use substrate_subxt::sp_runtime::AccountId32; //Signer +use utils_identity::cid::CidBytes; -pub struct Sunshine { - account_id: ::AccountId, - subxt: Client, - // xt: XtBuilder, +#[derive(new)] +pub struct SunClient { + client: Client, + keystore: KeyStore, ipld: BlockBuilder, } -impl Sunshine { - //#[cfg(not(feature = "light-client"))] - pub async fn new>(path: T, signer: Pair) -> Result { - let db = sled::open(path)?; - let ipld_tree = db.open_tree("ipld_tree")?; - let config = Config::from_tree(ipld_tree); - let store = Store::new(config)?; - let codec = Codec::new(); - let ipld = BlockBuilder::new(store, codec); - let account_id = signer.public().into(); - let subxt = crate::runtime::ClientBuilder::new().build().await?; - // let xt = subxt.xt(signer, None).await?; - Ok(Self { - account_id, - subxt, - // xt, - ipld, - }) +impl SunClient { + /// Set device key, directly from substrate-identity to use with keystore + pub fn has_device_key(&self) -> bool { + self.keystore.is_initialized() + } + /// Set device key, directly from substrate-identity to use with keystore + pub fn set_device_key( + &self, + dk: &DeviceKey, + password: &Password, + force: bool, + ) -> Result { + if self.keystore.is_initialized() && !force { + return Err(Error::KeystoreInitialized); + } + let pair = Pair::from_seed(&::Seed::from(*dk.expose_secret())); + self.keystore.initialize(&dk, &password)?; + Ok(pair.public().into()) + } + /// Returns a signer for alice + pub fn signer(&self) -> Result { + // fetch device key from disk every time to make sure account is unlocked. + let dk = self.keystore.device_key()?; + Ok(PairSigner::new(Pair::from_seed( + &::Seed::from(*dk.expose_secret()), + ))) + } + /// Register flat organization + pub async fn register_flat_org( + &self, + sudo: Option, + parent_org: Option, + constitution: CidBytes, + members: &[AccountId32], + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .register_flat_org_and_watch(&signer, sudo, parent_org, constitution, members) + .await? + .flat_org_registered() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Register weighted organization + pub async fn register_weighted_org( + &self, + sudo: Option, + parent_org: Option, + constitution: CidBytes, + weighted_members: &[(AccountId32, u64)], + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .register_weighted_org_and_watch( + &signer, + sudo, + parent_org, + constitution, + weighted_members, + ) + .await? + .weighted_org_registered() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Issue shares + pub async fn issue_shares( + &self, + organization: u64, + who: AccountId32, + shares: u64, + ) -> Result<()> { + let signer = self.signer()?; + self.client + .issue_shares_and_watch(&signer, organization, &who, shares) + .await? + .shares_issued() + .map_err(|e| substrate_subxt::Error::Codec(e))?; + Ok(()) + } + /// Burn shares + pub async fn burn_shares( + &self, + organization: u64, + who: AccountId32, + shares: u64, + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .burn_shares_and_watch(&signer, organization, &who, shares) + .await? + .shares_burned() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Batch issue shares + pub async fn batch_issue_shares( + &self, + organization: u64, + new_accounts: &[(AccountId32, u64)], + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .batch_issue_shares_and_watch(&signer, organization, new_accounts) + .await? + .shares_batch_issued() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Batch burn shares + pub async fn batch_burn_shares( + &self, + organization: u64, + old_accounts: &[(AccountId32, u64)], + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .batch_burn_shares_and_watch(&signer, organization, old_accounts) + .await? + .shares_batch_burned() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Reserves shares for alice + pub async fn reserve_shares( + &self, + org: u64, + who: &AccountId32, + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .reserve_shares_and_watch(&signer, org, who) + .await? + .shares_reserved() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Reserves shares for alice + pub async fn unreserve_shares( + &self, + org: u64, + who: &AccountId32, + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .unreserve_shares_and_watch(&signer, org, who) + .await? + .shares_un_reserved() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Lock shares for alice + pub async fn lock_shares( + &self, + org: u64, + who: &AccountId32, + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .lock_shares_and_watch(&signer, org, who) + .await? + .shares_locked() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) + } + /// Unlock shares for alice + pub async fn unlock_shares( + &self, + org: u64, + who: &AccountId32, + ) -> Result> { + let signer = self.signer()?; + self.client + .clone() + .unlock_shares_and_watch(&signer, org, who) + .await? + .shares_unlocked() + .map_err(|e| substrate_subxt::Error::Codec(e))? + .ok_or(Error::EventNotFound) } - - // pub async fn reserve_shares( - // &self, - // org: u32, - // share: u32, - // ) -> Result, Error> { - // self.xt - // .clone() - // .watch() - // .reserve_shares(org, share, &self.account_id) - // .await? - // .shares_reserved() - // .map_err(|e| substrate_subxt::Error::Codec(e))? - // .ok_or(Error::EventNotFound) - // } } diff --git a/modules/bank/Cargo.toml b/modules/bank/Cargo.toml index b77a0b1..fb4530d 100644 --- a/modules/bank/Cargo.toml +++ b/modules/bank/Cargo.toml @@ -21,17 +21,18 @@ util = { package = "sunshine-util", path = "../util", default-features = false } org = { package = "sunshine-org", path = "../org", default-features=false} vote = { package = "sunshine-vote", path = "../vote", default-features=false} clear_on_drop = { version = "0.2.4", features = ["no_cc"] } # https://github.com/paritytech/substrate/issues/4179 +sp-core = { version = "2.0.0-rc3", default-features = false } [dev-dependencies] pallet-balances = { version = "2.0.0-rc3", default-features = false } sp-io = { version = "2.0.0-rc3", default-features = false } -sp-core = { version = "2.0.0-rc3", default-features = false } [features] default = ["std"] std = [ "serde", "codec/std", + "sp-core/std", "sp-std/std", "sp-runtime/std", "frame-support/std", diff --git a/modules/bank/src/lib.rs b/modules/bank/src/lib.rs index 79e8abe..ec0105b 100644 --- a/modules/bank/src/lib.rs +++ b/modules/bank/src/lib.rs @@ -9,21 +9,6 @@ #[cfg(test)] mod tests; -use util::{ - bank::{ - BankMapID, BankState, DepositInfo, InternalTransferInfo, OnChainTreasuryID, - ReservationInfo, WithdrawalPermissions, - }, - traits::{ - CalculateOwnership, CheckBankBalances, CommitAndTransfer, CommitSpendReservation, - DefaultBankPermissions, DepositIntoBank, DepositSpendOps, DepositsAndSpends, ExecuteSpends, - FreeToReserved, GenerateUniqueID, GetGroupSize, GroupMembership, IDIsAvailable, Increment, - MoveFundsOutCommittedOnly, MoveFundsOutUnCommittedOnly, OnChainBank, - OrganizationSupervisorPermissions, RegisterAccount, ReservationMachine, - SeededGenerateUniqueID, ShareInformation, ShareIssuance, TermSheetExit, - }, // if sudo, import ChainSudoPermissions -}; - use codec::Codec; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, ensure, @@ -32,6 +17,7 @@ use frame_support::{ Parameter, }; use frame_system::{self as system, ensure_signed}; +use sp_core::TypeId; use sp_runtime::{ traits::{AccountIdConversion, AtLeast32Bit, MaybeSerializeDeserialize, Member, Zero}, // CheckedAdd, CheckedSub DispatchError, @@ -39,6 +25,18 @@ use sp_runtime::{ Permill, }; use sp_std::{fmt::Debug, prelude::*}; +use util::{ + bank::{BankMapID, BankState, DepositInfo, InternalTransferInfo, ReservationInfo}, + traits::{ + CalculateOwnership, CheckBankBalances, CommitAndTransfer, CommitSpendReservation, + DefaultBankPermissions, DepositIntoBank, DepositSpendOps, DepositsAndSpends, ExecuteSpends, + FreeToReserved, GenerateUniqueID, GroupMembership, IDIsAvailable, Increment, + MoveFundsOutCommittedOnly, MoveFundsOutUnCommittedOnly, OnChainBank, + OrganizationSupervisorPermissions, RegisterAccount, ReservationMachine, + SeededGenerateUniqueID, ShareInformation, ShareIssuance, TermSheetExit, + }, // ChainSudoPermissions, GetGroupSize + uid::IdWrapper, +}; /// The balances type for this module type BalanceOf = @@ -47,6 +45,13 @@ type BalanceOf = pub trait Trait: system::Trait + org::Trait + vote::Trait { type Event: From> + Into<::Event>; + /// The currency type for on-chain transactions + type Currency: Currency; + + /// The unique identifier type for every on-chain bank, convertible into the address which holds all funds in the account (with `to_account_id()`) + type TreasuryId: TypeId + Codec + Default + Clone + PartialEq + Debug + Increment; + + /// The unique identifier for objects associated with each on-chain bank type BankAssociatedId: Parameter + Member + AtLeast32Bit @@ -59,9 +64,6 @@ pub trait Trait: system::Trait + org::Trait + vote::Trait { + PartialEq + Zero; - /// The currency type for on-chain transactions - type Currency: Currency; - /// The minimum amount for opening a bank account in the context of this module type MinimumInitialDeposit: Get>; } @@ -74,16 +76,17 @@ decl_event!( ::IpfsReference, ::BankAssociatedId, Balance = BalanceOf, + ::TreasuryId, { - RegisteredNewOnChainBank(AccountId, OnChainTreasuryID, Balance, OrgId, WithdrawalPermissions), - CapitalDepositedIntoOnChainBankAccount(AccountId, OnChainTreasuryID, Balance, IpfsReference), - SpendReservedForBankAccount(AccountId, OnChainTreasuryID, BankAssociatedId, IpfsReference, Balance, WithdrawalPermissions), - CommitSpendBeforeInternalTransfer(AccountId, OnChainTreasuryID, BankAssociatedId, Balance), - UnReserveUncommittedReservationToMakeFree(AccountId, OnChainTreasuryID, BankAssociatedId, Balance), - UnReserveCommittedReservationToMakeFree(AccountId, OnChainTreasuryID, BankAssociatedId, Balance), - InternalTransferExecutedAndSpendingPowerDoledOutToController(AccountId, OnChainTreasuryID, IpfsReference, BankAssociatedId, Balance, WithdrawalPermissions), - SpendRequestForInternalTransferApprovedAndExecuted(OnChainTreasuryID, AccountId, Balance, BankAssociatedId), - AccountLeftMembershipAndWithdrewProportionOfFreeCapitalInBank(OnChainTreasuryID, AccountId, Balance), + RegisteredNewOnChainBank(AccountId, TreasuryId, Balance, OrgId, Option), + CapitalDepositedIntoOnChainBankAccount(AccountId, TreasuryId, Balance, IpfsReference), + SpendReservedForBankAccount(AccountId, TreasuryId, BankAssociatedId, IpfsReference, Balance, OrgId), + CommitSpendBeforeInternalTransfer(AccountId, TreasuryId, BankAssociatedId, Balance), + UnReserveUncommittedReservationToMakeFree(AccountId, TreasuryId, BankAssociatedId, Balance), + UnReserveCommittedReservationToMakeFree(AccountId, TreasuryId, BankAssociatedId, Balance), + InternalTransferExecutedAndSpendingPowerDoledOutToController(AccountId, TreasuryId, IpfsReference, BankAssociatedId, Balance, OrgId), + SpendRequestForInternalTransferApprovedAndExecuted(TreasuryId, AccountId, Balance, BankAssociatedId), + AccountLeftMembershipAndWithdrewProportionOfFreeCapitalInBank(TreasuryId, AccountId, Balance), } ); @@ -105,8 +108,7 @@ decl_error! { NotEnoughFundsInSpendReservationUnCommittedToSatisfyUnreserveUnCommittedRequest, NotEnoughFundsInBankReservedToSatisfyUnReserveUnComittedRequest, RegistrationMustDepositAboveModuleMinimum, - AccountMatchesNoneOfTwoControllers, - AccountHasNoWeightedOwnershipInOrg, + AccountHasNoOwnershipInOrg, BankAccountNotFoundForCommittingAndTransferringInOneStep, BankAccountNotFoundForUnReservingCommitted, BankAccountNotFoundForUnReservingUnCommitted, @@ -123,33 +125,33 @@ decl_error! { decl_storage! { trait Store for Module as Bank { /// Counter for generating uniqe bank accounts - BankIDNonce get(fn bank_id_nonce): OnChainTreasuryID; + BankIDNonce get(fn bank_id_nonce): IdWrapper; /// Map for managing counters associated with associated maps BankAssociatedNonces get(fn bank_associated_nonces): double_map - hasher(blake2_128_concat) OnChainTreasuryID, + hasher(blake2_128_concat) T::TreasuryId, hasher(blake2_128_concat) BankMapID => T::BankAssociatedId; - /// Source of truth for OnChainTreasuryId uniqueness checks - /// WARNING: do not append a prefix because the keyspace is used directly for checking uniqueness of `OnChainTreasuryId` + /// Source of truth for T::TreasuryId uniqueness checks + /// WARNING: do not append a prefix because the keyspace is used directly for checking uniqueness of `T::TreasuryId` /// TODO: pre-reserve any known ModuleId's that could be accidentally generated that already exist elsewhere pub BankStores get(fn bank_stores): map - hasher(blake2_128_concat) OnChainTreasuryID => Option, BalanceOf>>; + hasher(blake2_128_concat) T::TreasuryId => Option>>; - /// All deposits made into the joint bank account represented by OnChainTreasuryID + /// All deposits made into the joint bank account represented by T::TreasuryId pub Deposits get(fn deposits): double_map - hasher(blake2_128_concat) OnChainTreasuryID, + hasher(blake2_128_concat) T::TreasuryId, hasher(blake2_128_concat) T::BankAssociatedId => Option>>; /// Spend reservations which designated a committee for formally transferring ownership to specific destination addresses pub SpendReservations get(fn spend_reservations): double_map - hasher(blake2_128_concat) OnChainTreasuryID, - hasher(blake2_128_concat) T::BankAssociatedId => Option, WithdrawalPermissions>>; + hasher(blake2_128_concat) T::TreasuryId, + hasher(blake2_128_concat) T::BankAssociatedId => Option, T::OrgId>>; /// Internal transfers of control over capital that allow transfer liquidity rights to the current controller pub InternalTransfers get(fn internal_transfers): double_map - hasher(blake2_128_concat) OnChainTreasuryID, - hasher(blake2_128_concat) T::BankAssociatedId => Option, WithdrawalPermissions>>; + hasher(blake2_128_concat) T::TreasuryId, + hasher(blake2_128_concat) T::BankAssociatedId => Option, T::OrgId>>; } } @@ -161,14 +163,14 @@ decl_module! { #[weight = 0] fn deposit_from_signer_for_bank_account( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, amount: BalanceOf, //savings_tax: Option, // l8rrr reason: T::IpfsReference, ) -> DispatchResult { let depositer = ensure_signed(origin)?; - Self::deposit_into_bank(depositer.clone(), bank_id, amount, reason.clone())?; + Self::deposit_into_bank(depositer.clone(), bank_id.clone(), amount, reason.clone())?; Self::deposit_event(RawEvent::CapitalDepositedIntoOnChainBankAccount(depositer, bank_id, amount, reason)); Ok(()) } @@ -177,100 +179,100 @@ decl_module! { origin, seed: BalanceOf, hosting_org: T::OrgId, // pre-requisite is registered organization - bank_controller: WithdrawalPermissions, + bank_operator: Option, ) -> DispatchResult { let seeder = ensure_signed(origin)?; - let authentication = Self::can_register_account(seeder.clone(), hosting_org) && Self::withdrawal_permissions_satisfy_org_standards(hosting_org, bank_controller.clone()); + let authentication = Self::can_register_account(seeder.clone(), hosting_org) && + if let Some(op) = bank_operator { Self::operator_satisfies_requirements(hosting_org, op.clone()) } else {true}; ensure!(authentication, Error::::CannotRegisterBankAccountBCPermissionsCheckFails); - let new_bank_id = Self::register_account(hosting_org, seeder.clone(), seed, bank_controller.clone())?; - Self::deposit_event(RawEvent::RegisteredNewOnChainBank(seeder, new_bank_id, seed, hosting_org, bank_controller)); + let new_bank_id = Self::register_account(hosting_org, seeder.clone(), seed, bank_operator.clone())?; + Self::deposit_event(RawEvent::RegisteredNewOnChainBank(seeder, new_bank_id, seed, hosting_org, bank_operator)); Ok(()) } #[weight = 0] fn reserve_spend_for_bank_account( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, reason: T::IpfsReference, amount: BalanceOf, - controller: WithdrawalPermissions, + controller: T::OrgId, ) -> DispatchResult { let reserver = ensure_signed(origin)?; - let authentication = Self::can_reserve_for_spend(reserver.clone(), bank_id)?; + let authentication = Self::can_reserve_for_spend(reserver.clone(), bank_id.clone())?; ensure!(authentication, Error::::CannotReserveSpendBCPermissionsCheckFails); - let new_reservation_id = Self::reserve_for_spend(bank_id, reason.clone(), amount, controller.clone())?; + let new_reservation_id = Self::reserve_for_spend(bank_id.clone(), reason.clone(), amount, controller.clone())?; Self::deposit_event(RawEvent::SpendReservedForBankAccount(reserver, bank_id, new_reservation_id, reason, amount, controller)); Ok(()) } #[weight = 0] fn commit_reserved_spend_for_transfer_inside_bank_account( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, reservation_id: T::BankAssociatedId, reason: T::IpfsReference, amount: BalanceOf, - future_controller: WithdrawalPermissions, ) -> DispatchResult { let committer = ensure_signed(origin)?; - let authentication = Self::can_commit_reserved_spend_for_transfer(committer.clone(), bank_id)?; + let authentication = Self::can_commit_reserved_spend_for_transfer(committer.clone(), bank_id.clone())?; ensure!(authentication, Error::::CannotCommitSpendBCPermissionsCheckFails); - Self::commit_reserved_spend_for_transfer(bank_id, reservation_id, amount)?; + Self::commit_reserved_spend_for_transfer(bank_id.clone(), reservation_id, amount)?; Self::deposit_event(RawEvent::CommitSpendBeforeInternalTransfer(committer, bank_id, reservation_id, amount)); Ok(()) } #[weight = 0] fn unreserve_uncommitted_reservation_to_make_free( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, reservation_id: T::BankAssociatedId, amount: BalanceOf, ) -> DispatchResult { let qualified_bank_controller = ensure_signed(origin)?; - let authentication = Self::can_unreserve_uncommitted_to_make_free(qualified_bank_controller.clone(), bank_id)?; + let authentication = Self::can_unreserve_uncommitted_to_make_free(qualified_bank_controller.clone(), bank_id.clone())?; ensure!(authentication, Error::::CannotUnReserveUncommittedBCPermissionsCheckFails); - Self::unreserve_uncommitted_to_make_free(bank_id, reservation_id, amount)?; + Self::unreserve_uncommitted_to_make_free(bank_id.clone(), reservation_id, amount)?; Self::deposit_event(RawEvent::UnReserveUncommittedReservationToMakeFree(qualified_bank_controller, bank_id, reservation_id, amount)); Ok(()) } #[weight = 0] fn unreserve_committed_reservation_to_make_free( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, reservation_id: T::BankAssociatedId, amount: BalanceOf, ) -> DispatchResult { let qualified_spend_reservation_controller = ensure_signed(origin)?; - let authentication = Self::can_unreserve_committed_to_make_free(qualified_spend_reservation_controller.clone(), bank_id)?; + let authentication = Self::can_unreserve_committed_to_make_free(qualified_spend_reservation_controller.clone(), bank_id.clone())?; ensure!(authentication, Error::::CannotUnReserveCommittedBCPermissionsCheckFails); - Self::unreserve_committed_to_make_free(bank_id, reservation_id, amount)?; + Self::unreserve_committed_to_make_free(bank_id.clone(), reservation_id, amount)?; Self::deposit_event(RawEvent::UnReserveCommittedReservationToMakeFree(qualified_spend_reservation_controller, bank_id, reservation_id, amount)); Ok(()) } #[weight = 0] fn transfer_spending_for_spend_commitment( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, reason: T::IpfsReference, reservation_id: T::BankAssociatedId, amount: BalanceOf, - committed_controller: WithdrawalPermissions, + committed_controller: T::OrgId, ) -> DispatchResult { let qualified_spend_reservation_controller = ensure_signed(origin)?; - let authentication = Self::can_transfer_spending_power(qualified_spend_reservation_controller.clone(), bank_id)?; + let authentication = Self::can_transfer_spending_power(qualified_spend_reservation_controller.clone(), bank_id.clone())?; ensure!(authentication, Error::::CannotTransferSpendingPowerBCPermissionsCheckFails); - Self::transfer_spending_power(bank_id, reason.clone(), reservation_id, amount, committed_controller.clone())?; + Self::transfer_spending_power(bank_id.clone(), reason.clone(), reservation_id, amount, committed_controller.clone())?; Self::deposit_event(RawEvent::InternalTransferExecutedAndSpendingPowerDoledOutToController(qualified_spend_reservation_controller, bank_id, reason, reservation_id, amount, committed_controller)); Ok(()) } #[weight = 0] fn withdraw_by_referencing_internal_transfer( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, transfer_id: T::BankAssociatedId, amount: BalanceOf, ) -> DispatchResult { let requester = ensure_signed(origin)?; let amount_received = Self::spend_from_transfers( - bank_id, + bank_id.clone(), transfer_id, requester.clone(), amount, @@ -281,10 +283,10 @@ decl_module! { #[weight = 0] fn burn_all_shares_to_leave_weighted_membership_bank_and_withdraw_related_free_capital( origin, - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, ) -> DispatchResult { let leaving_member = ensure_signed(origin)?; - let amount_withdrawn_by_burning_shares = Self::burn_shares_to_exit_bank_ownership(leaving_member.clone(), bank_id)?; + let amount_withdrawn_by_burning_shares = Self::burn_shares_to_exit_bank_ownership(leaving_member.clone(), bank_id.clone())?; Self::deposit_event(RawEvent::AccountLeftMembershipAndWithdrewProportionOfFreeCapitalInBank(bank_id, leaving_member, amount_withdrawn_by_burning_shares)); Ok(()) } @@ -292,12 +294,12 @@ decl_module! { } impl Module { - pub fn account_id(id: OnChainTreasuryID) -> T::AccountId { + pub fn account_id(id: T::TreasuryId) -> T::AccountId { id.into_account() } // deposits pub fn get_deposits_by_account( - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, depositer: T::AccountId, ) -> Option>>> { let depositers_deposits = >::iter() @@ -311,7 +313,7 @@ impl Module { } } pub fn total_capital_deposited_by_account( - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, depositer: T::AccountId, ) -> BalanceOf { >::iter() @@ -322,7 +324,7 @@ impl Module { } // reservations pub fn get_amount_left_in_spend_reservation( - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, reservation_id: T::BankAssociatedId, ) -> Option> { if let Some(spend_reservation) = >::get(bank_id, reservation_id) { @@ -331,37 +333,23 @@ impl Module { None } } - pub fn get_reservations_for_governance_config( - bank_id: OnChainTreasuryID, - invoker: WithdrawalPermissions, - ) -> Option< - Vec< - ReservationInfo< - T::IpfsReference, - BalanceOf, - WithdrawalPermissions, - >, - >, - > { + pub fn get_reservations_for_organization( + bank_id: T::TreasuryId, + invoker: T::OrgId, + ) -> Option, T::OrgId>>> { let ret = >::iter() .filter(|(id, _, reservation)| id == &bank_id && reservation.controller() == invoker) .map(|(_, _, reservation)| reservation) - .collect::, - WithdrawalPermissions, - >, - >>(); + .collect::, T::OrgId>>>(); if ret.is_empty() { None } else { Some(ret) } } - pub fn total_capital_reserved_for_governance_config( - bank_id: OnChainTreasuryID, - invoker: WithdrawalPermissions, + pub fn total_capital_reserved_for_organization( + bank_id: T::TreasuryId, + invoker: T::OrgId, ) -> BalanceOf { >::iter() .filter(|(id, _, reservation)| id == &bank_id && reservation.controller() == invoker) @@ -371,7 +359,7 @@ impl Module { } // transfers pub fn get_amount_left_in_approved_transfer( - bank_id: OnChainTreasuryID, + bank_id: T::TreasuryId, transfer_id: T::BankAssociatedId, ) -> Option> { if let Some(internal_transfer) = >::get(bank_id, transfer_id) { @@ -381,28 +369,16 @@ impl Module { } } pub fn get_transfers_for_governance_config( - bank_id: OnChainTreasuryID, - invoker: WithdrawalPermissions, + bank_id: T::TreasuryId, + invoker: T::OrgId, ) -> Option< - Vec< - InternalTransferInfo< - T::BankAssociatedId, - T::IpfsReference, - BalanceOf, - WithdrawalPermissions, - >, - >, + Vec, T::OrgId>>, > { let ret = >::iter() .filter(|(id, _, transfer)| id == &bank_id && transfer.controller() == invoker) .map(|(_, _, transfer)| transfer) .collect::, - WithdrawalPermissions, - >, + InternalTransferInfo, T::OrgId>, >>(); if ret.is_empty() { None @@ -411,8 +387,8 @@ impl Module { } } pub fn total_capital_transferred_to_governance_config( - bank_id: OnChainTreasuryID, - invoker: WithdrawalPermissions, + bank_id: T::TreasuryId, + invoker: T::OrgId, ) -> BalanceOf { >::iter() .filter(|(id, _, transfer)| id == &bank_id && transfer.controller() == invoker) @@ -422,14 +398,14 @@ impl Module { } } -impl IDIsAvailable for Module { - fn id_is_available(id: OnChainTreasuryID) -> bool { - >::get(id).is_none() +impl IDIsAvailable> for Module { + fn id_is_available(id: IdWrapper) -> bool { + >::get(id.id).is_none() } } -impl IDIsAvailable<(OnChainTreasuryID, BankMapID, T::BankAssociatedId)> for Module { - fn id_is_available(id: (OnChainTreasuryID, BankMapID, T::BankAssociatedId)) -> bool { +impl IDIsAvailable<(T::TreasuryId, BankMapID, T::BankAssociatedId)> for Module { + fn id_is_available(id: (T::TreasuryId, BankMapID, T::BankAssociatedId)) -> bool { match id.1 { BankMapID::Deposit => >::get(id.0, id.2).is_none(), BankMapID::Reservation => >::get(id.0, id.2).is_none(), @@ -438,12 +414,12 @@ impl IDIsAvailable<(OnChainTreasuryID, BankMapID, T::BankAssociatedId) } } -impl SeededGenerateUniqueID +impl SeededGenerateUniqueID for Module { - fn seeded_generate_unique_id(seed: (OnChainTreasuryID, BankMapID)) -> T::BankAssociatedId { - let mut new_id = >::get(seed.0, seed.1) + 1u32.into(); - while !Self::id_is_available((seed.0, seed.1, new_id)) { + fn seeded_generate_unique_id(seed: (T::TreasuryId, BankMapID)) -> T::BankAssociatedId { + let mut new_id = >::get(seed.0.clone(), seed.1) + 1u32.into(); + while !Self::id_is_available((seed.0.clone(), seed.1, new_id)) { new_id += 1u32.into(); } >::insert(seed.0, seed.1, new_id); @@ -451,35 +427,28 @@ impl SeededGenerateUniqueID GenerateUniqueID for Module { - fn generate_unique_id() -> OnChainTreasuryID { - let mut treasury_nonce_id = BankIDNonce::get().increment(); - while !Self::id_is_available(treasury_nonce_id) { - treasury_nonce_id = treasury_nonce_id.increment(); +impl GenerateUniqueID for Module { + fn generate_unique_id() -> T::TreasuryId { + let mut treasury_nonce_id = >::get().id.clone().increment(); + while !Self::id_is_available(IdWrapper::new(treasury_nonce_id.clone())) { + treasury_nonce_id = treasury_nonce_id.id.clone().increment(); } - BankIDNonce::put(treasury_nonce_id); + >::put(IdWrapper::new(treasury_nonce_id.clone())); treasury_nonce_id } } impl OnChainBank for Module { - type TreasuryId = OnChainTreasuryID; + type TreasuryId = T::TreasuryId; type AssociatedId = T::BankAssociatedId; } -impl - RegisterAccount< - T::OrgId, - T::AccountId, - WithdrawalPermissions, - BalanceOf, - > for Module -{ +impl RegisterAccount> for Module { fn register_account( owners: T::OrgId, from: T::AccountId, amount: BalanceOf, - operators: WithdrawalPermissions, + operators: Option, ) -> Result { ensure!( amount >= T::MinimumInitialDeposit::get(), @@ -489,11 +458,11 @@ impl let new_bank = BankState::new_from_deposit(owners, amount, operators); // generate bank id let generated_id = Self::generate_unique_id(); - let to = Self::account_id(generated_id); + let to = Self::account_id(generated_id.clone()); // make transfer to seed joint bank account with amount T::Currency::transfer(&from, &to, amount, ExistenceRequirement::KeepAlive)?; // insert new bank store - >::insert(generated_id, new_bank); + >::insert(generated_id.clone(), new_bank); Ok(generated_id) } fn verify_owner(bank_id: Self::TreasuryId, org: T::OrgId) -> bool { @@ -505,50 +474,23 @@ impl } } -impl - CalculateOwnership< - T::OrgId, - T::AccountId, - WithdrawalPermissions, - BalanceOf, - Permill, - > for Module -{ +impl CalculateOwnership, Permill> for Module { fn calculate_proportion_ownership_for_account( account: T::AccountId, - group: WithdrawalPermissions, + group: T::OrgId, ) -> Result { - match group { - WithdrawalPermissions::TwoAccounts(acc1, acc2) => { - // assumes that we never use this with acc1 == acc2; use sudo in that situation - if acc1 == account || acc2 == account { - Ok(Permill::from_percent(50)) - } else { - Err(Error::::AccountMatchesNoneOfTwoControllers.into()) - } - } - WithdrawalPermissions::JointOrgAccount(org_id) => { - let issuance = >::total_issuance(org_id); - if issuance > T::Shares::zero() { - // weighted group - let acc_ownership = >::get_share_profile(org_id, &account) - .ok_or(Error::::AccountHasNoWeightedOwnershipInOrg)?; - Ok(Permill::from_rational_approximation( - acc_ownership.total(), - issuance, - )) - } else { - // flat group - let organization_size = >::get_size_of_group(org_id); - Ok(Permill::from_rational_approximation(1, organization_size)) - } - } - } + let issuance = >::total_issuance(group); + let acc_ownership = >::get_share_profile(group, &account) + .ok_or(Error::::AccountHasNoOwnershipInOrg)?; + Ok(Permill::from_rational_approximation( + acc_ownership.total(), + issuance, + )) } fn calculate_proportional_amount_for_account( amount: BalanceOf, account: T::AccountId, - group: WithdrawalPermissions, + group: T::OrgId, ) -> Result, DispatchError> { let proportion_due = Self::calculate_proportion_ownership_for_account(account, group)?; Ok(proportion_due * amount) @@ -556,7 +498,7 @@ impl } impl DepositsAndSpends> for Module { - type Bank = BankState, BalanceOf>; + type Bank = BankState<::OrgId, BalanceOf>; fn make_infallible_deposit_into_free(bank: Self::Bank, amount: BalanceOf) -> Self::Bank { bank.deposit_into_free(amount) } @@ -599,25 +541,13 @@ impl CheckBankBalances> for Module { // We could NOT have the extra storage lookup in here but // the API design is much more extensible this way. See issue #85 -impl - DefaultBankPermissions< - T::OrgId, - T::AccountId, - BalanceOf, - WithdrawalPermissions, - > for Module -{ +impl DefaultBankPermissions> for Module { fn can_register_account(account: T::AccountId, on_behalf_of: T::OrgId) -> bool { // only the organization supervisor can register a bank account >::is_organization_supervisor(on_behalf_of, &account) } - fn withdrawal_permissions_satisfy_org_standards( - _org: T::OrgId, - _withdrawal_permissions: WithdrawalPermissions, - ) -> bool { - // an example might require that withdrawal_permissions is a subgroup - //or contains members of OrgId but I don't think that's necessary to - //impl as a default...sometimes no default is OK + fn operator_satisfies_requirements(_org: T::OrgId, _withdrawal_permissions: T::OrgId) -> bool { + // TODO: add default as a subgroup check? does this relation imply revocability of control true } // bank.operators() can make spend reservations (indicates funding intent by beginning the formal shift of capital control) @@ -627,11 +557,11 @@ impl ) -> Result { let bank_account = >::get(bank).ok_or(Error::::BankAccountNotFoundForSpendReservation)?; - let ret_bool = match bank_account.operators() { - WithdrawalPermissions::TwoAccounts(acc1, acc2) => acc1 == account || acc2 == account, - WithdrawalPermissions::JointOrgAccount(org_id) => { - >::is_member_of_group(org_id, &account) - } + let ret_bool = if let Some(org_id) = bank_account.operators() { + >::is_member_of_group(org_id, &account) + } else { + // if bank.operators().is_none() + >::is_member_of_group(bank_account.owners(), &account) }; Ok(ret_bool) } @@ -642,11 +572,11 @@ impl ) -> Result { let bank_account = >::get(bank).ok_or(Error::::BankAccountNotFoundForSpendCommitment)?; - let ret_bool = match bank_account.operators() { - WithdrawalPermissions::TwoAccounts(acc1, acc2) => acc1 == account || acc2 == account, - WithdrawalPermissions::JointOrgAccount(org_id) => { - >::is_member_of_group(org_id, &account) - } + let ret_bool = if let Some(org_id) = bank_account.operators() { + >::is_member_of_group(org_id, &account) + } else { + // if bank.operators().is_none() + >::is_member_of_group(bank_account.owners(), &account) }; Ok(ret_bool) } @@ -657,15 +587,12 @@ impl ) -> Result { let bank_account = >::get(bank) .ok_or(Error::::BankAccountNotFoundForUnReservingUnCommitted)?; - let ret_bool = >::is_member_of_group(bank_account.owners(), &account) - || match bank_account.operators() { - WithdrawalPermissions::TwoAccounts(acc1, acc2) => { - acc1 == account || acc2 == account - } - WithdrawalPermissions::JointOrgAccount(org_id) => { - >::is_member_of_group(org_id, &account) - } - }; + let ret_bool = if let Some(org_id) = bank_account.operators() { + >::is_member_of_group(org_id, &account) + } else { + // if bank.operators().is_none() + >::is_member_of_group(bank_account.owners(), &account) + }; Ok(ret_bool) } // ONLY bank.operators() can unreserve committed funds @@ -675,11 +602,11 @@ impl ) -> Result { let bank_account = >::get(bank) .ok_or(Error::::BankAccountNotFoundForUnReservingCommitted)?; - let ret_bool = match bank_account.operators() { - WithdrawalPermissions::TwoAccounts(acc1, acc2) => acc1 == account || acc2 == account, - WithdrawalPermissions::JointOrgAccount(org_id) => { - >::is_member_of_group(org_id, &account) - } + let ret_bool = if let Some(org_id) = bank_account.operators() { + >::is_member_of_group(org_id, &account) + } else { + // if bank.operators().is_none() + >::is_member_of_group(bank_account.owners(), &account) }; Ok(ret_bool) } @@ -690,15 +617,12 @@ impl ) -> Result { let bank_account = >::get(bank) .ok_or(Error::::BankAccountNotFoundForUnReservingUnCommitted)?; - let ret_bool = >::is_member_of_group(bank_account.owners(), &account) - || match bank_account.operators() { - WithdrawalPermissions::TwoAccounts(acc1, acc2) => { - acc1 == account || acc2 == account - } - WithdrawalPermissions::JointOrgAccount(org_id) => { - >::is_member_of_group(org_id, &account) - } - }; + let ret_bool = if let Some(org_id) = bank_account.operators() { + >::is_member_of_group(org_id, &account) + } else { + // if bank.operators().is_none() + >::is_member_of_group(bank_account.owners(), &account) + }; Ok(ret_bool) } // supervisor-oriented permissions for committing and transferring withdrawal permissions in a single step @@ -708,28 +632,18 @@ impl ) -> Result { let bank_account = >::get(bank) .ok_or(Error::::BankAccountNotFoundForCommittingAndTransferringInOneStep)?; - Ok( + let ret_bool = if let Some(org_id) = bank_account.operators() { + >::is_organization_supervisor(org_id, &account) + } else { + // if bank.operators().is_none() >::is_organization_supervisor(bank_account.owners(), &account) - || match bank_account.operators() { - WithdrawalPermissions::TwoAccounts(acc1, acc2) => { - acc1 == account || acc2 == account - } - WithdrawalPermissions::JointOrgAccount(org_id) => { - >::is_organization_supervisor(org_id, &account) - } - }, - ) + }; + Ok(ret_bool) } } -impl - DepositIntoBank< - T::OrgId, - T::AccountId, - WithdrawalPermissions, - BalanceOf, - T::IpfsReference, - > for Module +impl DepositIntoBank, T::IpfsReference> + for Module { fn deposit_into_bank( from: T::AccountId, @@ -738,17 +652,17 @@ impl reason: T::IpfsReference, ) -> Result { let bank_account = - >::get(to_bank_id).ok_or(Error::::BankAccountNotFoundForDeposit)?; + >::get(&to_bank_id).ok_or(Error::::BankAccountNotFoundForDeposit)?; // make the transfer - let dest = Self::account_id(to_bank_id); + let dest = Self::account_id(to_bank_id.clone()); T::Currency::transfer(&from, &dest, amount, ExistenceRequirement::KeepAlive)?; // update the amount stored in the bank let updated_bank_balance = Self::make_infallible_deposit_into_free(bank_account, amount); - >::insert(to_bank_id, updated_bank_balance); + >::insert(to_bank_id.clone(), updated_bank_balance); // form the deposit, no savings_pct allocated let new_deposit = DepositInfo::new(from, reason, amount); // generate unique deposit - let deposit_id = Self::seeded_generate_unique_id((to_bank_id, BankMapID::Deposit)); + let deposit_id = Self::seeded_generate_unique_id((to_bank_id.clone(), BankMapID::Deposit)); // TODO: when will we delete this, how long is this going to stay in storage? >::insert(to_bank_id, deposit_id, new_deposit); @@ -756,22 +670,16 @@ impl } } -impl - ReservationMachine< - T::OrgId, - T::AccountId, - WithdrawalPermissions, - BalanceOf, - T::IpfsReference, - > for Module +impl ReservationMachine, T::IpfsReference> + for Module { fn reserve_for_spend( bank_id: Self::TreasuryId, reason: T::IpfsReference, amount: BalanceOf, - controller: WithdrawalPermissions, + controller: T::OrgId, ) -> Result { - let bank_account = >::get(bank_id) + let bank_account = >::get(&bank_id) .ok_or(Error::::BankAccountNotFoundForSpendReservation)?; // create Reservation Info object with 100 percent of it uncommitted let new_spend_reservation = ReservationInfo::new(reason, amount, controller); @@ -779,9 +687,10 @@ impl let new_bank = bank_account .move_from_free_to_reserved(amount) .ok_or(Error::::NotEnoughFundsInFreeToAllowReservation)?; - let reservation_id = Self::seeded_generate_unique_id((bank_id, BankMapID::Reservation)); + let reservation_id = + Self::seeded_generate_unique_id((bank_id.clone(), BankMapID::Reservation)); // insert new bank account - >::insert(bank_id, new_bank); + >::insert(bank_id.clone(), new_bank); >::insert(bank_id, reservation_id, new_spend_reservation); Ok(reservation_id) } @@ -790,9 +699,9 @@ impl reservation_id: Self::AssociatedId, amount: BalanceOf, ) -> DispatchResult { - let _ = >::get(bank_id) + let _ = >::get(&bank_id) .ok_or(Error::::BankAccountNotFoundForSpendReservation)?; - let spend_reservation = >::get(bank_id, reservation_id) + let spend_reservation = >::get(bank_id.clone(), reservation_id) .ok_or(Error::::SpendReservationNotFound)?; // only commit the reserved part and return error if not enough funds let reservation_after_commit = spend_reservation @@ -808,9 +717,9 @@ impl reservation_id: Self::AssociatedId, amount: BalanceOf, ) -> DispatchResult { - let bank_account = >::get(bank_id) + let bank_account = >::get(&bank_id) .ok_or(Error::::BankAccountNotFoundForSpendReservation)?; - let spend_reservation = >::get(bank_id, reservation_id) + let spend_reservation = >::get(bank_id.clone(), reservation_id) .ok_or(Error::::SpendReservationNotFound)?; // this request must be approved by unreserving from the spend_reservation's uncommitted funds let new_spend_reservation = spend_reservation @@ -823,7 +732,7 @@ impl .ok_or(Error::::NotEnoughFundsInBankReservedToSatisfyUnReserveUnComittedRequest)? .deposit_into_free(amount); // insert new bank account - >::insert(bank_id, new_bank_account); + >::insert(bank_id.clone(), new_bank_account); // insert update spend reservation object (with the new, lower amount reserved) >::insert(bank_id, reservation_id, new_spend_reservation); Ok(()) @@ -833,9 +742,9 @@ impl reservation_id: Self::AssociatedId, amount: BalanceOf, ) -> DispatchResult { - let bank_account = >::get(bank_id) + let bank_account = >::get(&bank_id) .ok_or(Error::::BankAccountNotFoundForSpendReservation)?; - let spend_reservation = >::get(bank_id, reservation_id) + let spend_reservation = >::get(bank_id.clone(), reservation_id) .ok_or(Error::::SpendReservationNotFound)?; // ensure that the amount is less than the spend reservation amount let new_spend_reservation = spend_reservation @@ -847,7 +756,7 @@ impl .ok_or(Error::::NotEnoughFundsCommittedToSatisfyUnreserveAndFreeRequest)? .deposit_into_free(amount); // insert new bank account - >::insert(bank_id, new_bank_account); + >::insert(bank_id.clone(), new_bank_account); // insert update spend reservation object (with the new, lower amount reserved) >::insert(bank_id, reservation_id, new_spend_reservation); Ok(()) @@ -861,11 +770,11 @@ impl reservation_id: Self::AssociatedId, amount: BalanceOf, // move control of funds to new outer group which can reserve or withdraw directly - new_controller: WithdrawalPermissions, + new_controller: T::OrgId, ) -> Result { - let _ = >::get(bank_id) + let _ = >::get(&bank_id) .ok_or(Error::::BankAccountNotFoundForInternalTransfer)?; - let spend_reservation = >::get(bank_id, reservation_id) + let spend_reservation = >::get(bank_id.clone(), reservation_id) .ok_or(Error::::SpendReservationNotFound)?; // fallible spend from committed part of `ReservationInfo` object let new_spend_reservation = spend_reservation @@ -876,34 +785,28 @@ impl InternalTransferInfo::new(reservation_id, reason, amount, new_controller); // generate the unique transfer_id let new_transfer_id = - Self::seeded_generate_unique_id((bank_id, BankMapID::InternalTransfer)); + Self::seeded_generate_unique_id((bank_id.clone(), BankMapID::InternalTransfer)); // insert transfer_info, thereby unlocking the capital for the `new_controller` group - >::insert(bank_id, new_transfer_id, new_transfer); + >::insert(bank_id.clone(), new_transfer_id, new_transfer); // insert update reservation info after the transfer was made - >::insert(bank_id, reservation_id, new_spend_reservation); + >::insert(bank_id.clone(), reservation_id, new_spend_reservation); Ok(new_transfer_id) } } -impl - CommitAndTransfer< - T::OrgId, - T::AccountId, - WithdrawalPermissions, - BalanceOf, - T::IpfsReference, - > for Module +impl CommitAndTransfer, T::IpfsReference> + for Module { fn commit_and_transfer_spending_power( bank_id: Self::TreasuryId, reservation_id: Self::AssociatedId, reason: T::IpfsReference, amount: BalanceOf, - new_controller: WithdrawalPermissions, + new_controller: T::OrgId, ) -> Result { - let _ = >::get(bank_id) + let _ = >::get(&bank_id) .ok_or(Error::::BankAccountNotFoundForInternalTransfer)?; - let spend_reservation = >::get(bank_id, reservation_id) + let spend_reservation = >::get(&bank_id, reservation_id) .ok_or(Error::::SpendReservationNotFound)?; // ensure that the amount is less than the spend reservation amount let new_spend_reservation = spend_reservation @@ -914,24 +817,16 @@ impl InternalTransferInfo::new(reservation_id, reason, amount, new_controller); // generate the unique transfer_id let new_transfer_id = - Self::seeded_generate_unique_id((bank_id, BankMapID::InternalTransfer)); + Self::seeded_generate_unique_id((bank_id.clone(), BankMapID::InternalTransfer)); // insert transfer_info, thereby unlocking the capital for the `new_controller` group - >::insert(bank_id, new_transfer_id, new_transfer); + >::insert(bank_id.clone(), new_transfer_id, new_transfer); // insert update reservation info after the transfer was made >::insert(bank_id, reservation_id, new_spend_reservation); Ok(new_transfer_id) } } -impl - ExecuteSpends< - T::OrgId, - T::AccountId, - WithdrawalPermissions, - BalanceOf, - T::IpfsReference, - > for Module -{ +impl ExecuteSpends, T::IpfsReference> for Module { /// This method authenticates the spend by checking that the caller /// input follows the same shape as the bank's controller... /// => any method that calls this one will need to define local @@ -944,12 +839,12 @@ impl to: T::AccountId, amount: BalanceOf, ) -> DispatchResult { - let bank_account = >::get(from_bank_id) + let bank_account = >::get(&from_bank_id) .ok_or(Error::::BankAccountNotFoundForWithdrawal)?; // update the amount stored in the bank let bank_after_withdrawal = Self::fallible_spend_from_free(bank_account, amount)?; // make the transfer - let from = Self::account_id(from_bank_id); + let from = Self::account_id(from_bank_id.clone()); T::Currency::transfer(&from, &to, amount, ExistenceRequirement::KeepAlive)?; >::insert(from_bank_id, bank_after_withdrawal); Ok(()) @@ -963,9 +858,9 @@ impl to: T::AccountId, amount: BalanceOf, ) -> Result, DispatchError> { - let bank_account = >::get(from_bank_id) + let bank_account = >::get(&from_bank_id) .ok_or(Error::::BankAccountNotFoundForWithdrawal)?; - let transfer_certificate = >::get(from_bank_id, id) + let transfer_certificate = >::get(&from_bank_id, id) .ok_or(Error::::AllSpendsFromReserveMustReferenceInternalTransferNotFound)?; // calculate due amount let due_amount = Self::calculate_proportional_amount_for_account( @@ -983,10 +878,10 @@ impl // update the bank store let bank_after_withdrawal = Self::fallible_spend_from_reserved(bank_account, amount)?; // make the transfer - let from = Self::account_id(from_bank_id); + let from = Self::account_id(from_bank_id.clone()); T::Currency::transfer(&from, &to, amount, ExistenceRequirement::KeepAlive)?; // insert updated transfer certificate after amount is spent - >::insert(from_bank_id, id, new_transfer_certificate); + >::insert(from_bank_id.clone(), id, new_transfer_certificate); >::insert(from_bank_id, bank_after_withdrawal); Ok(amount) } @@ -998,18 +893,18 @@ impl TermSheetExit> for Module { rage_quitter: T::AccountId, bank_id: Self::TreasuryId, ) -> Result, DispatchError> { - let bank_account = - >::get(bank_id).ok_or(Error::::BankAccountNotFoundForTermSheetExit)?; + let bank_account = >::get(&bank_id) + .ok_or(Error::::BankAccountNotFoundForTermSheetExit)?; let withdrawal_amount = Self::calculate_proportional_amount_for_account( bank_account.free(), rage_quitter.clone(), - WithdrawalPermissions::JointOrgAccount(bank_account.owners()), + bank_account.owners(), )?; // // burn the shares first let _ = >::burn(bank_account.owners(), rage_quitter.clone(), None, false)?; // Then make the transfer - Self::spend_from_free(bank_id, rage_quitter, withdrawal_amount)?; + Self::spend_from_free(bank_id.clone(), rage_quitter, withdrawal_amount)?; // -> TODO: if transfer errs, there should be a dispute process to recover share ownership by presenting // the transaction that Erred Ok(withdrawal_amount) diff --git a/modules/bank/src/tests.rs b/modules/bank/src/tests.rs index c3eae23..b7c0119 100644 --- a/modules/bank/src/tests.rs +++ b/modules/bank/src/tests.rs @@ -3,7 +3,7 @@ use frame_support::{assert_noop, assert_ok}; use frame_support::{impl_outer_event, impl_outer_origin, parameter_types, weights::Weight}; use sp_core::H256; use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; -use util::{organization::Organization, traits::GroupMembership}; +use util::{bank::OnChainTreasuryID, organization::Organization, traits::GroupMembership}; // type aliases pub type AccountId = u64; @@ -89,8 +89,9 @@ parameter_types! { } impl Trait for Test { type Event = TestEvent; - type BankAssociatedId = u64; type Currency = Balances; + type TreasuryId = OnChainTreasuryID; + type BankAssociatedId = u64; type MinimumInitialDeposit = MinimumInitialDeposit; } pub type System = system::Module; @@ -98,7 +99,7 @@ pub type Balances = pallet_balances::Module; pub type Org = org::Module; pub type Bank = Module; -fn get_last_event() -> RawEvent { +fn get_last_event() -> RawEvent { System::events() .into_iter() .map(|r| r.event) diff --git a/modules/bounty/src/lib.rs b/modules/bounty/src/lib.rs index c985fb5..58d317b 100644 --- a/modules/bounty/src/lib.rs +++ b/modules/bounty/src/lib.rs @@ -26,7 +26,7 @@ use sp_runtime::{ use sp_std::{fmt::Debug, prelude::*}; use util::{ - bank::{BankMapID, OnChainTreasuryID, WithdrawalPermissions}, + bank::BankMapID, bounty::{ ApplicationState, BountyInformation, BountyMapID, GrantApplication, MilestoneStatus, MilestoneSubmission, ReviewBoard, TeamID, @@ -41,13 +41,16 @@ use util::{ StartTeamConsentPetition, SubmitGrantApplication, SubmitMilestone, SuperviseGrantApplication, TermSheetExit, UseTermsOfAgreement, }, // UpdateVoteTopic, VoteOnProposal + uid::IdWrapper, vote::{ThresholdConfig, VoteOutcome}, }; /// The balances type for this module pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -/// The associate identifier for the bank module +/// The identifier for banks in the bank module (and the address) +pub type TreasuryId = <::Bank as OnChainBank>::TreasuryId; +/// The identifier associated with bank state in the bank module pub type BankAssociatedId = <::Bank as OnChainBank>::AssociatedId; pub trait Trait: frame_system::Trait + org::Trait + vote::Trait { @@ -71,49 +74,20 @@ pub trait Trait: frame_system::Trait + org::Trait + vote::Trait { + Zero; /// The bank module - type Bank: IDIsAvailable - + IDIsAvailable<(OnChainTreasuryID, BankMapID, BankAssociatedId)> - + GenerateUniqueID - + SeededGenerateUniqueID, (OnChainTreasuryID, BankMapID)> + type Bank: IDIsAvailable> + + IDIsAvailable<(TreasuryId, BankMapID, BankAssociatedId)> + + GenerateUniqueID> + + SeededGenerateUniqueID, (TreasuryId, BankMapID)> + OnChainBank - + RegisterAccount< - Self::OrgId, - Self::AccountId, - WithdrawalPermissions, - BalanceOf, - > + CalculateOwnership< - Self::OrgId, - Self::AccountId, - WithdrawalPermissions, - BalanceOf, - Permill, - > + DepositsAndSpends> + + RegisterAccount> + + CalculateOwnership, Permill> + + DepositsAndSpends> + CheckBankBalances> - + DepositIntoBank< - Self::OrgId, - Self::AccountId, - WithdrawalPermissions, - BalanceOf, - Self::IpfsReference, - > + ReservationMachine< - Self::OrgId, - Self::AccountId, - WithdrawalPermissions, - BalanceOf, - Self::IpfsReference, - > + ExecuteSpends< - Self::OrgId, - Self::AccountId, - WithdrawalPermissions, - BalanceOf, - Self::IpfsReference, - > + CommitAndTransfer< - Self::OrgId, - Self::AccountId, - WithdrawalPermissions, - BalanceOf, - Self::IpfsReference, - > + TermSheetExit>; + + DepositIntoBank, Self::IpfsReference> + + ReservationMachine, Self::IpfsReference> + + ExecuteSpends, Self::IpfsReference> + + CommitAndTransfer, Self::IpfsReference> + + TermSheetExit>; /// This is the minimum percent of the total bounty that must be reserved as collateral type MinimumBountyCollateralRatio: Get; @@ -132,10 +106,11 @@ decl_event!( ::VoteId, ::BountyId, Currency = BalanceOf, + TreasuryId = TreasuryId, BankAssociatedId = BankAssociatedId, { - FoundationRegisteredFromOnChainBank(OrgId, OnChainTreasuryID), - FoundationPostedBounty(AccountId, OrgId, BountyId, OnChainTreasuryID, IpfsReference, Currency, Currency), + FoundationRegisteredFromOnChainBank(OrgId, TreasuryId), + FoundationPostedBounty(AccountId, OrgId, BountyId, TreasuryId, IpfsReference, Currency, Currency), // BountyId, Application Id (u32s) GrantApplicationSubmittedForBounty(AccountId, BountyId, BountyId, IpfsReference, Currency), // BountyId, Application Id (u32s) @@ -145,9 +120,9 @@ decl_event!( // BountyId, ApplicationId, MilestoneId (u32s) MilestoneSubmitted(AccountId, BountyId, BountyId, BountyId), // BountyId, MilestoneId (u32s) - MilestoneReviewTriggered(AccountId, BountyId, BountyId, MilestoneStatus), - SudoApprovedMilestone(AccountId, BountyId, BountyId, MilestoneStatus), - MilestonePolled(AccountId, BountyId, BountyId, MilestoneStatus), + MilestoneReviewTriggered(AccountId, BountyId, BountyId, MilestoneStatus), + SudoApprovedMilestone(AccountId, BountyId, BountyId, MilestoneStatus), + MilestonePolled(AccountId, BountyId, BountyId, MilestoneStatus), } ); @@ -202,16 +177,17 @@ decl_storage! { hasher(opaque_blake2_256) T::BountyId, hasher(opaque_blake2_256) BountyMapID => T::BountyId; - /// Unordered set for tracking foundations as relationships b/t OrgId and OnChainTreasuryID + /// Unordered set for tracking foundations as relationships b/t OrgId and TreasuryId RegisteredFoundations get(fn registered_foundations): double_map hasher(blake2_128_concat) T::OrgId, - hasher(blake2_128_concat) OnChainTreasuryID => bool; + hasher(blake2_128_concat) TreasuryId => bool; /// Posted bounty details FoundationSponsoredBounties get(fn foundation_sponsored_bounties): map hasher(opaque_blake2_256) T::BountyId => Option< BountyInformation< T::OrgId, + TreasuryId, BankAssociatedId, T::IpfsReference, BalanceOf, @@ -227,7 +203,7 @@ decl_storage! { /// All milestone submissions MilestoneSubmissions get(fn milestone_submissions): double_map hasher(opaque_blake2_256) T::BountyId, - hasher(opaque_blake2_256) T::BountyId => Option, T::AccountId, T::BountyId, MilestoneStatus>>>; + hasher(opaque_blake2_256) T::BountyId => Option, T::AccountId, T::BountyId, MilestoneStatus, BankAssociatedId>>>; } } @@ -240,7 +216,7 @@ decl_module! { pub fn direct__register_foundation_from_existing_bank( origin, registered_organization: T::OrgId, - bank_account: OnChainTreasuryID, + bank_account: TreasuryId, ) -> DispatchResult { let _ = ensure_signed(origin)?; // any authorization would need to be HERE @@ -254,7 +230,7 @@ decl_module! { origin, registered_organization: T::OrgId, description: T::IpfsReference, - bank_account: OnChainTreasuryID, + bank_account: TreasuryId, amount_reserved_for_bounty: BalanceOf, // collateral requirement amount_claimed_available: BalanceOf, // claimed available amount, not necessarily liquid acceptance_committee: ReviewBoard>, @@ -384,18 +360,8 @@ impl Module { } } -pub struct BIdWrapper { - pub id: T, -} - -impl BIdWrapper { - pub fn new(id: T) -> BIdWrapper { - BIdWrapper { id } - } -} - -impl IDIsAvailable> for Module { - fn id_is_available(id: BIdWrapper) -> bool { +impl IDIsAvailable> for Module { + fn id_is_available(id: IdWrapper) -> bool { >::get(id.id).is_none() } } @@ -423,7 +389,7 @@ impl SeededGenerateUniqueID f impl GenerateUniqueID for Module { fn generate_unique_id() -> T::BountyId { let mut id_counter = >::get() + 1u32.into(); - while !Self::id_is_available(BIdWrapper::new(id_counter)) { + while !Self::id_is_available(IdWrapper::new(id_counter)) { id_counter += 1u32.into(); } >::put(id_counter); @@ -432,7 +398,7 @@ impl GenerateUniqueID for Module { } impl RegisterFoundation, T::AccountId> for Module { - type BankId = OnChainTreasuryID; + type BankId = TreasuryId; // helper method to quickly bootstrap an organization from a donation // -> it should register an on-chain bank account and return the on-chain bank account identifier // TODO @@ -448,7 +414,6 @@ impl RegisterFoundation, T::AccountId> for Modu <::Bank as RegisterAccount< T::OrgId, T::AccountId, - WithdrawalPermissions, BalanceOf, >>::verify_owner(bank.into(), org), Error::::CannotRegisterFoundationFromOrgBankRelationshipThatDNE @@ -470,6 +435,7 @@ impl /// Bounty information type type BountyInfo = BountyInformation< T::OrgId, + TreasuryId, BankAssociatedId, T::IpfsReference, BalanceOf, @@ -522,14 +488,13 @@ impl let spend_reservation_id = <::Bank as ReservationMachine< T::OrgId, T::AccountId, - WithdrawalPermissions, BalanceOf, T::IpfsReference, >>::reserve_for_spend( bank_account.into(), description.clone(), amount_reserved_for_bounty, - acceptance_committee.clone().into(), // converts ReviewBoard Into WithdrawalPermissions + acceptance_committee.org(), // reserved for who? should be the ultimate recipient? )?; // form the bounty_info let new_bounty_info = BountyInformation::new( @@ -838,7 +803,7 @@ impl T::IpfsReference, BalanceOf, T::VoteId, - OnChainTreasuryID, + TreasuryId, BankAssociatedId, > for Module { @@ -847,9 +812,9 @@ impl BalanceOf, T::AccountId, T::BountyId, - MilestoneStatus>, + MilestoneStatus, BankAssociatedId>, >; - type MilestoneState = MilestoneStatus>; + type MilestoneState = MilestoneStatus, BankAssociatedId>; fn submit_milestone( caller: T::AccountId, // must be from the team, maybe check sudo || flat_org_member bounty_id: T::BountyId, @@ -912,7 +877,6 @@ impl <::Bank as ReservationMachine< T::OrgId, T::AccountId, - WithdrawalPermissions, BalanceOf, T::IpfsReference, >>::commit_reserved_spend_for_transfer( @@ -972,7 +936,6 @@ impl let new_transfer_id = <::Bank as CommitAndTransfer< T::OrgId, T::AccountId, - WithdrawalPermissions, BalanceOf, T::IpfsReference, >>::commit_and_transfer_spending_power( @@ -980,7 +943,7 @@ impl bounty_info.spend_reservation(), milestone_submission.submission(), // reason = hash of milestone submission milestone_submission.amount(), - WithdrawalPermissions::JointOrgAccount(team_org_id), + team_org_id, )?; let new_milestone_submission = milestone_submission .set_make_transfer(bounty_info.bank_account(), new_transfer_id) @@ -1023,7 +986,6 @@ impl let transfer_id = <::Bank as ReservationMachine< T::OrgId, T::AccountId, - WithdrawalPermissions, BalanceOf, T::IpfsReference, >>::transfer_spending_power( @@ -1031,7 +993,7 @@ impl milestone_submission.submission(), // reason = hash of milestone submission bounty_info.spend_reservation(), milestone_submission.amount(), - WithdrawalPermissions::JointOrgAccount(org_id), // uses the weighted share issuance by default to enforce payout structure + org_id, // uses the weighted share issuance by default to enforce payout structure )?; // insert updated application into storage >::insert( @@ -1068,7 +1030,6 @@ impl let transfer_id = <::Bank as ReservationMachine< T::OrgId, T::AccountId, - WithdrawalPermissions, BalanceOf, T::IpfsReference, >>::transfer_spending_power( @@ -1076,7 +1037,7 @@ impl milestone_submission.submission(), // reason = hash of milestone submission bounty_info.spend_reservation(), milestone_submission.amount(), - WithdrawalPermissions::JointOrgAccount(org_id), // uses the weighted share issuance by default to enforce payout structure + org_id, // uses the weighted share issuance by default to enforce payout structure )?; // insert updated application into storage >::insert( @@ -1116,7 +1077,6 @@ impl let transfer_id = <::Bank as ReservationMachine< T::OrgId, T::AccountId, - WithdrawalPermissions, BalanceOf, T::IpfsReference, >>::transfer_spending_power( @@ -1124,7 +1084,7 @@ impl milestone_submission.submission(), // reason = hash of milestone submission bounty_info.spend_reservation(), milestone_submission.amount(), - WithdrawalPermissions::JointOrgAccount(org_id), + org_id, )?; let new_milestone_submission = milestone_submission .set_make_transfer(bounty_info.bank_account(), transfer_id) diff --git a/modules/org/src/lib.rs b/modules/org/src/lib.rs index 794d6b9..feddd34 100644 --- a/modules/org/src/lib.rs +++ b/modules/org/src/lib.rs @@ -12,10 +12,10 @@ use util::{ organization::{Organization, OrganizationSource}, share::{ShareProfile, SimpleShareGenesis}, traits::{ - AccessGenesis, ChainSudoPermissions, ChangeGroupMembership, GenerateUniqueID, GetGroup, - GetGroupSize, GroupMembership, IDIsAvailable, LockProfile, - OrganizationSupervisorPermissions, RegisterOrganization, RemoveOrganization, - ReserveProfile, ShareInformation, ShareIssuance, VerifyShape, + AccessGenesis, ChainSudoPermissions, GenerateUniqueID, GetGroup, GetGroupSize, + GroupMembership, IDIsAvailable, LockProfile, OrganizationSupervisorPermissions, + RegisterOrganization, RemoveOrganization, ReserveProfile, ShareInformation, ShareIssuance, + VerifyShape, }, // AccessProfile, SeededGenerateUniqueID }; @@ -114,11 +114,8 @@ decl_error! { pub enum Error for Module { UnAuthorizedSwapSudoRequest, NoExistingSudoKey, - NotAuthorizedToChangeMembership, OrganizationMustExistToClearSupervisor, OrganizationMustExistToPutSupervisor, - CannotAddAnExistingMemberToOrg, - CannotRemoveNonMemberFromOrg, CannotBurnMoreThanTotalIssuance, NotEnoughSharesToSatisfyBurnRequest, IssuanceCannotGoNegative, @@ -236,63 +233,6 @@ decl_module! { Self::deposit_event(RawEvent::NewWeightedOrganizationRegistered(caller, new_id, constitution, wm_cpy.total())); Ok(()) } - #[weight = 0] - fn add_new_member_to_org( - origin, - organization: T::OrgId, - new_member: T::AccountId, - shares_issued: T::Shares, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - let authentication: bool = Self::is_sudo_key(&caller) || Self::is_organization_supervisor(organization, &caller); - ensure!(authentication, Error::::NotAuthorizedToChangeMembership); - - Self::add_member_to_org(organization, new_member.clone(), false)?; - Self::deposit_event(RawEvent::NewMemberAddedToOrg(organization, new_member, T::Shares::zero())); - Ok(()) - } - #[weight = 0] - fn remove_old_member_from_org( - origin, - organization: T::OrgId, - old_member: T::AccountId, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - let authentication: bool = Self::is_sudo_key(&caller) || Self::is_organization_supervisor(organization, &caller); - ensure!(authentication, Error::::NotAuthorizedToChangeMembership); - - Self::remove_member_from_org(organization, old_member.clone(), false)?; - Self::deposit_event(RawEvent::OldMemberRemovedFromOrg(organization, old_member, T::Shares::zero())); - Ok(()) - } - #[weight = 0] - fn add_new_members_to_org( - origin, - organization: T::OrgId, - new_members: Vec, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - let authentication: bool = Self::is_sudo_key(&caller) || Self::is_organization_supervisor(organization, &caller); - ensure!(authentication, Error::::NotAuthorizedToChangeMembership); - - Self::batch_add_members_to_org(organization, new_members)?; - Self::deposit_event(RawEvent::BatchMemberAdditionForOrg(caller, organization, T::Shares::zero())); - Ok(()) - } - #[weight = 0] - fn remove_old_members_from_org( - origin, - organization: T::OrgId, - old_members: Vec, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - let authentication: bool = Self::is_sudo_key(&caller) || Self::is_organization_supervisor(organization, &caller); - ensure!(authentication, Error::::NotAuthorizedToChangeMembership); - - Self::batch_remove_members_from_org(organization, old_members)?; - Self::deposit_event(RawEvent::BatchMemberRemovalForOrg(caller, organization, T::Shares::zero())); - Ok(()) - } /// Share Issuance Runtime Methods #[weight = 0] fn issue_shares(origin, organization: T::OrgId, who: T::AccountId, shares: T::Shares) -> DispatchResult { @@ -507,7 +447,11 @@ impl RegisterOrganization fo match src { OrganizationSource::Accounts(accounts) => { // batch_add (flat membership group) - Self::batch_add_members_to_org(org_id, accounts)?; + let weighted_acc = accounts + .into_iter() + .map(|acc| (acc, 1u32.into())) + .collect::>(); + Self::batch_issue(org_id, weighted_acc.into())?; Ok(Organization::new(supervisor, parent_id, value_constitution)) } OrganizationSource::AccountsWeighted(weighted_accounts) => { @@ -574,76 +518,6 @@ impl RemoveOrganization for Module { } } -impl ChangeGroupMembership for Module { - fn add_member_to_org( - org_id: T::OrgId, - new_member: T::AccountId, - batch: bool, - ) -> DispatchResult { - // prevent adding duplicate members - ensure!( - >::get(org_id, &new_member).is_none(), - Error::::CannotAddAnExistingMemberToOrg - ); - if !batch { - let new_organization_size = >::get(org_id) + 1u32; - >::insert(org_id, new_organization_size); - } - // default 0 share profile inserted for this method - >::insert(org_id, new_member, ShareProfile::default()); - Ok(()) - } - fn remove_member_from_org( - org_id: T::OrgId, - old_member: T::AccountId, - batch: bool, - ) -> DispatchResult { - // prevent removal of non-member - let current_profile = >::get(org_id, &old_member) - .ok_or(Error::::CannotRemoveNonMemberFromOrg)?; - // update issuance if it changes due to this removal - if current_profile.total() > T::Shares::zero() { - let new_issuance = >::get(org_id) - .checked_sub(¤t_profile.total()) - .ok_or(Error::::IssuanceGoesNegativeWhileRemovingMember)?; - >::insert(org_id, new_issuance); - } - if !batch { - let new_organization_size = >::get(org_id).saturating_sub(1u32); - >::insert(org_id, new_organization_size); - } - >::remove(org_id, old_member); - Ok(()) - } - // WARNING: the vector fed as inputs to the following methods must have NO duplicates - fn batch_add_members_to_org( - org_id: T::OrgId, - new_members: Vec, - ) -> DispatchResult { - let size_increase: u32 = new_members.len() as u32; - let new_organization_size: u32 = - >::get(org_id).saturating_add(size_increase); - >::insert(org_id, new_organization_size); - new_members - .into_iter() - .map(|member| Self::add_member_to_org(org_id, member, true)) - .collect::() - } - fn batch_remove_members_from_org( - org_id: T::OrgId, - old_members: Vec, - ) -> DispatchResult { - let size_decrease: u32 = old_members.len() as u32; - let new_organization_size: u32 = - >::get(org_id).saturating_sub(size_decrease); - >::insert(org_id, new_organization_size); - old_members - .into_iter() - .map(|member| Self::remove_member_from_org(org_id, member, true)) - .collect::() - } -} - impl GetGroup for Module { fn get_group(organization: T::OrgId) -> Option> { if !Self::id_is_available(organization) { diff --git a/modules/util/src/bank.rs b/modules/util/src/bank.rs index fbad3da..380ef76 100644 --- a/modules/util/src/bank.rs +++ b/modules/util/src/bank.rs @@ -41,64 +41,9 @@ pub enum BankMapID { InternalTransfer, } -#[derive(PartialEq, Eq, Clone, Encode, Decode, sp_runtime::RuntimeDebug)] -/// The simplest `GovernanceConfig` -/// - has no magnitude context and is limited for that reason -/// - future version will use revocable representative governance -pub enum WithdrawalPermissions { - // any of two accounts can reserve free capital for spending - TwoAccounts(AccountId, AccountId), - // withdrawal permissions restricted by weighted membership in organization - // -> sudo might have additional power, depends on impl - JointOrgAccount(Id), -} - -impl + Copy, AccountId> Default - for WithdrawalPermissions -{ - fn default() -> WithdrawalPermissions { - // the chain's dev account? - WithdrawalPermissions::JointOrgAccount(OrgId::zero()) - } -} - -impl + Copy, AccountId> - WithdrawalPermissions -{ - pub fn extract_org_id(&self) -> Option { - match self { - WithdrawalPermissions::JointOrgAccount(org_id) => Some(*org_id), - _ => None, - } - } -} -use crate::bounty::{ReviewBoard, TeamID}; -impl< - OrgId: Codec + PartialEq + Zero + From + Copy, - AccountId: PartialEq, - Hash: Clone, - Threshold: Clone, - > From> - for WithdrawalPermissions -{ - fn from( - other: ReviewBoard, - ) -> WithdrawalPermissions { - WithdrawalPermissions::JointOrgAccount(other.org()) - } -} -impl + Copy, AccountId: Clone + PartialEq> - From> for WithdrawalPermissions -{ - fn from(other: TeamID) -> WithdrawalPermissions { - WithdrawalPermissions::JointOrgAccount(other.org()) - } -} - #[derive(PartialEq, Eq, Clone, Encode, Decode, sp_runtime::RuntimeDebug)] /// This is the state for an OnChainBankId, associated with each bank registered in the runtime -pub struct BankState + Copy, GovernanceConfig, Currency> -{ +pub struct BankState + Copy, Currency> { // Registered organization identifier owners: OrgId, // Free capital, available for spends @@ -107,16 +52,13 @@ pub struct BankState + Copy, Governa reserved: Currency, // Operator of the organization, might be required to be a suborg or `owners` or not // -> power should be restricted by actions made by the `self.owners` - operators: GovernanceConfig, + operators: Option, } -impl< - OrgId: Codec + PartialEq + Zero + From + Copy, - GovernanceConfig: Clone + PartialEq, - Currency: Zero + AtLeast32Bit + Clone, - > BankState +impl + Copy, Currency: Zero + AtLeast32Bit + Clone> + BankState { - pub fn new_from_deposit(owners: OrgId, amount: Currency, operators: GovernanceConfig) -> Self { + pub fn new_from_deposit(owners: OrgId, amount: Currency, operators: Option) -> Self { BankState { owners, free: amount, @@ -133,22 +75,25 @@ impl< pub fn reserved(&self) -> Currency { self.reserved.clone() } - pub fn operators(&self) -> GovernanceConfig { - self.operators.clone() + pub fn operators(&self) -> Option { + self.operators } pub fn is_owners(&self, org: OrgId) -> bool { org == self.owners } - pub fn is_operator(&self, cmp_owner: GovernanceConfig) -> bool { - cmp_owner == self.operators.clone() + pub fn is_operator(&self, org: OrgId) -> bool { + if let Some(op) = self.operators.clone() { + op == org + } else { + false + } } } impl< OrgId: Codec + PartialEq + Zero + From + Copy, - GovernanceConfig: Clone + PartialEq, Currency: Zero + AtLeast32Bit + Clone + sp_std::ops::Add + sp_std::ops::Sub, - > FreeToReserved for BankState + > FreeToReserved for BankState { fn move_from_free_to_reserved(&self, amount: Currency) -> Option { if self.free() >= amount { @@ -170,9 +115,8 @@ impl< impl< OrgId: Codec + PartialEq + Zero + From + Copy, - GovernanceConfig: Clone + PartialEq, Currency: Zero + AtLeast32Bit + Clone + sp_std::ops::Add, - > GetBalance for BankState + > GetBalance for BankState { fn total_free_funds(&self) -> Currency { self.free() @@ -185,11 +129,8 @@ impl< } } -impl< - OrgId: Codec + PartialEq + Zero + From + Copy, - GovernanceConfig: Clone + PartialEq, - Currency: Zero + AtLeast32Bit + Clone, - > DepositSpendOps for BankState +impl + Copy, Currency: Zero + AtLeast32Bit + Clone> + DepositSpendOps for BankState { // infallible fn deposit_into_free(&self, amount: Currency) -> Self { diff --git a/modules/util/src/bounty.rs b/modules/util/src/bounty.rs index a34486d..ada1896 100644 --- a/modules/util/src/bounty.rs +++ b/modules/util/src/bounty.rs @@ -1,5 +1,4 @@ use crate::{ - bank::OnChainTreasuryID, organization::TermsOfAgreement, traits::{ ApproveGrant, ApproveWithoutTransfer, GetTeamOrg, SetMakeTransfer, SpendApprovedGrant, @@ -27,6 +26,7 @@ impl Default for BountyMapID { /// The information most often read after a specific bounty is GOT pub struct BountyInformation< OrgId: Codec + PartialEq + Zero + From + Copy, + TreasuryId, ReservationId, Hash, Currency, @@ -38,7 +38,7 @@ pub struct BountyInformation< // registered organization associated with bounty foundation_id: OrgId, // On chain bank account associated with this bounty - bank_account: OnChainTreasuryID, + bank_account: TreasuryId, // Spend reservation identifier for funds set aside for bounty // TODO: update when funds are spent and a new spend reservation is required (gc) spend_reservation_id: ReservationId, @@ -56,18 +56,19 @@ pub struct BountyInformation< impl< OrgId: Codec + PartialEq + Zero + From + Copy, + TreasuryId: Clone, ReservationId: Codec + PartialEq + Zero + From + Copy, Hash: Parameter, Currency: Parameter, ReviewBoard: Clone, - > BountyInformation + > BountyInformation { // get OrgId for sponsor org basically pub fn foundation(&self) -> OrgId { self.foundation_id } - pub fn bank_account(&self) -> OnChainTreasuryID { - self.bank_account + pub fn bank_account(&self) -> TreasuryId { + self.bank_account.clone() } pub fn spend_reservation(&self) -> ReservationId { self.spend_reservation_id @@ -143,7 +144,7 @@ impl< } #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug)] -pub enum MilestoneStatus { +pub enum MilestoneStatus { SubmittedAwaitingResponse(OrgId), SubmittedReviewStarted(OrgId, VoteId), // if the milestone is approved but the approved application does not @@ -151,7 +152,7 @@ pub enum MilestoneStatus { ApprovedButNotTransferred(OrgId), // wraps Some(transfer_id) (bank_id is proVoteIded for convenient lookup, must equal bounty.bank) // None if the transfer wasn't able to be afforded at the time so it hasn't happened yet - ApprovedAndTransferEnabled(OnChainTreasuryID, TransferId), + ApprovedAndTransferEnabled(TreasuryId, TransferId), } #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] @@ -170,6 +171,7 @@ impl< Currency: Clone, AccountId: Clone, BountyId: Codec + Copy, + TreasuryId: Clone, TransferId: Codec + Copy, OrgId: Codec + Copy, VoteId: Codec + PartialEq + Zero + From + Copy, @@ -179,7 +181,7 @@ impl< Currency, AccountId, BountyId, - MilestoneStatus, + MilestoneStatus, > { pub fn new( @@ -193,7 +195,7 @@ impl< Currency, AccountId, BountyId, - MilestoneStatus, + MilestoneStatus, > { MilestoneSubmission { submitter, @@ -212,8 +214,8 @@ impl< pub fn amount(&self) -> Currency { self.amount.clone() } - pub fn state(&self) -> MilestoneStatus { - self.state + pub fn state(&self) -> MilestoneStatus { + self.state.clone() } pub fn ready_for_review(&self) -> bool { match self.state { @@ -228,6 +230,7 @@ impl< Currency: Clone, AccountId: Clone, BountyId: Codec + Copy, + TreasuryId: Clone, TransferId: Codec + Copy, OrgId: Codec + Copy, VoteId: Codec + PartialEq + Zero + From + Copy, @@ -237,7 +240,7 @@ impl< Currency, AccountId, BountyId, - MilestoneStatus, + MilestoneStatus, > { fn get_team_org(&self) -> Option { @@ -255,6 +258,7 @@ impl< Currency: Clone, AccountId: Clone, BountyId: Codec + Copy, + TreasuryId: Clone, TransferId: Codec + Copy, OrgId: Codec + Copy, VoteId: Codec + PartialEq + Zero + From + Copy, @@ -264,7 +268,7 @@ impl< Currency, AccountId, BountyId, - MilestoneStatus, + MilestoneStatus, > { fn start_review(&self, vote_id: VoteId) -> Option { @@ -292,6 +296,7 @@ impl< Currency: Clone, AccountId: Clone, BountyId: Codec + Copy, + TreasuryId: Clone, TransferId: Codec + Copy, OrgId: Codec + Copy, VoteId: Codec + PartialEq + Zero + From + Copy, @@ -301,7 +306,7 @@ impl< Currency, AccountId, BountyId, - MilestoneStatus, + MilestoneStatus, > { fn approve_without_transfer(&self) -> Option { @@ -323,24 +328,21 @@ impl< Currency: Clone, AccountId: Clone, BountyId: Codec + Copy, + TreasuryId: Clone, TransferId: Codec + Copy, OrgId: Codec + Copy, VoteId: Codec + PartialEq + Zero + From + Copy, - > SetMakeTransfer + > SetMakeTransfer for MilestoneSubmission< Hash, Currency, AccountId, BountyId, - MilestoneStatus, + MilestoneStatus, > { - fn set_make_transfer( - &self, - bank_id: OnChainTreasuryID, - transfer_id: TransferId, - ) -> Option { - match self.state { + fn set_make_transfer(&self, bank_id: TreasuryId, transfer_id: TransferId) -> Option { + match &self.state { MilestoneStatus::SubmittedReviewStarted(_, _) => Some(MilestoneSubmission { submitter: self.submitter.clone(), referenced_application: self.referenced_application, @@ -358,8 +360,8 @@ impl< _ => None, } } - fn get_bank_id(&self) -> Option { - match self.state { + fn get_bank_id(&self) -> Option { + match self.state.clone() { MilestoneStatus::ApprovedAndTransferEnabled(bank_id, _) => Some(bank_id), _ => None, } diff --git a/modules/util/src/lib.rs b/modules/util/src/lib.rs index 5f2f592..bb4a2d0 100644 --- a/modules/util/src/lib.rs +++ b/modules/util/src/lib.rs @@ -14,4 +14,5 @@ pub mod organization; pub mod proposal; pub mod share; pub mod traits; +pub mod uid; pub mod vote; diff --git a/modules/util/src/traits.rs b/modules/util/src/traits.rs index af1e55e..a8ac647 100644 --- a/modules/util/src/traits.rs +++ b/modules/util/src/traits.rs @@ -47,15 +47,6 @@ pub trait GetGroupSize { pub trait GroupMembership: GetGroupSize { fn is_member_of_group(org_id: OrgId, who: &AccountId) -> bool; } - -/// All changes to the organizational membership are infallible -pub trait ChangeGroupMembership: GroupMembership { - fn add_member_to_org(org_id: OrgId, new_member: AccountId, batch: bool) -> DispatchResult; - fn remove_member_from_org(org_id: OrgId, old_member: AccountId, batch: bool) -> DispatchResult; - /// WARNING: the vector fed as inputs to the following methods must have NO duplicates - fn batch_add_members_to_org(org_id: OrgId, new_members: Vec) -> DispatchResult; - fn batch_remove_members_from_org(org_id: OrgId, old_members: Vec) -> DispatchResult; -} pub trait GetGroup { fn get_group(organization: OrgId) -> Option>; } @@ -271,34 +262,35 @@ pub trait VoteOnProposal } // ~~~~~~~~ Bank Module ~~~~~~~~ -use crate::bank::OnChainTreasuryID; use codec::Codec; +use sp_core::TypeId; +use sp_std::fmt::Debug; pub trait OnChainBank { - type TreasuryId: Clone + From; + type TreasuryId: TypeId + Codec + Default + Clone + PartialEq + Debug + Increment; type AssociatedId: Codec + Copy + PartialEq + From + Zero; } -pub trait RegisterAccount: OnChainBank { +pub trait RegisterAccount: OnChainBank { // requires a deposit of some size above the minimum and returns the OnChainTreasuryID fn register_account( owners: OrgId, from: AccountId, amount: Currency, - owner_s: GovernanceConfig, + operators: Option, ) -> Result; fn verify_owner(bank_id: Self::TreasuryId, org: OrgId) -> bool; } // people should be eventually able to solicit loans from others to SEED a bank account but they cede some or all of the control... -pub trait CalculateOwnership: - RegisterAccount +pub trait CalculateOwnership: + RegisterAccount { fn calculate_proportion_ownership_for_account( account: AccountId, - group: GovernanceConfig, + group: OrgId, ) -> Result; fn calculate_proportional_amount_for_account( amount: Currency, account: AccountId, - group: GovernanceConfig, + group: OrgId, ) -> Result; } @@ -345,8 +337,8 @@ pub trait CheckBankBalances: OnChainBank + DepositsAndSpends fn calculate_total_bank_balance_from_balances(bank_id: Self::TreasuryId) -> Option; } -pub trait DepositIntoBank: - RegisterAccount + DepositsAndSpends +pub trait DepositIntoBank: + RegisterAccount + DepositsAndSpends { // get the bank corresponding to bank_id call infallible deposit // - only fails if `from` doesn't have enough Currency @@ -358,14 +350,11 @@ pub trait DepositIntoBank: ) -> Result; // returns DepositId } -pub trait DefaultBankPermissions: +pub trait DefaultBankPermissions: DepositsAndSpends + OnChainBank { fn can_register_account(account: AccountId, on_behalf_of: OrgId) -> bool; - fn withdrawal_permissions_satisfy_org_standards( - org: OrgId, - withdrawal_permissions: WithdrawalPermissions, - ) -> bool; + fn operator_satisfies_requirements(org: OrgId, operator: OrgId) -> bool; fn can_reserve_for_spend( account: AccountId, bank: Self::TreasuryId, @@ -395,7 +384,7 @@ pub trait DefaultBankPermissions: - RegisterAccount +pub trait ReservationMachine: + RegisterAccount { fn reserve_for_spend( bank_id: Self::TreasuryId, reason: Hash, amount: Currency, // acceptance committee for approving set aside spends below the amount - controller: GovernanceConfig, + controller: OrgId, ) -> Result; fn commit_reserved_spend_for_transfer( bank_id: Self::TreasuryId, @@ -438,12 +427,12 @@ pub trait ReservationMachine reservation_id: Self::AssociatedId, amount: Currency, // move control of funds to new outer group which can reserve or withdraw directly - new_controller: GovernanceConfig, + new_controller: OrgId, ) -> Result; // returns transfer_id } -pub trait CommitAndTransfer: - ReservationMachine +pub trait CommitAndTransfer: + ReservationMachine { // in one step fn commit_and_transfer_spending_power( @@ -451,12 +440,12 @@ pub trait CommitAndTransfer: reservation_id: Self::AssociatedId, reason: Hash, amount: Currency, - new_controller: GovernanceConfig, + new_controller: OrgId, ) -> Result; } -pub trait ExecuteSpends: - OnChainBank + ReservationMachine +pub trait ExecuteSpends: + OnChainBank + ReservationMachine { fn spend_from_free( from_bank_id: Self::TreasuryId, diff --git a/modules/util/src/uid.rs b/modules/util/src/uid.rs new file mode 100644 index 0000000..67c1c2f --- /dev/null +++ b/modules/util/src/uid.rs @@ -0,0 +1,6 @@ +use codec::{Codec, Encode, Decode}; + +#[derive(new, PartialEq, Eq, Clone, Encode, Decode, sp_runtime::RuntimeDebug)] +pub struct IdWrapper { + pub id: T, +} \ No newline at end of file diff --git a/modules/vote/src/lib.rs b/modules/vote/src/lib.rs index 477e673..8b79578 100644 --- a/modules/vote/src/lib.rs +++ b/modules/vote/src/lib.rs @@ -160,13 +160,13 @@ decl_module! { origin, topic: Option, organization: T::OrgId, - ends: Option, + duration: Option, ) -> DispatchResult { let vote_creator = ensure_signed(origin)?; // default authentication is organization supervisor or sudo key let authentication: bool = >::is_organization_supervisor(organization, &vote_creator) || >::is_sudo_key(&vote_creator); ensure!(authentication, Error::::NotAuthorizedToCreateVoteForOrganization); - let new_vote_id = Self::open_unanimous_consent(topic, organization, ends)?; + let new_vote_id = Self::open_unanimous_consent(topic, organization, duration)?; // emit event Self::deposit_event(RawEvent::NewVoteStarted(vote_creator, organization, new_vote_id)); Ok(())