profile
viewpoint
Mazdak Farrokhzad Centril @PolymathNetwork Sweden Compiler engineer and former Rust Language and Release team member.

AltSysrq/proptest 686

Hypothesis-like property testing for Rust

Centril/dbg 5

Implementation of https://github.com/rust-lang/rfcs/pull/2173 in stable rust

Centril/firefox-line 4

Rewrite of Mozilla Labs Prospector Oneliner 2 with Addons SDK

Centril/aTetria 2

Initial push of Framework + Model + input processors.

Centril/android-numberpicker 1

A backport of the Android 4.2 NumberPicker

Centril/bin-util 1

small utility scripts, mostly git related.

Centril/consensusbot 1

Next gen rfcbot

Centril/android-project-template 0

template project for android.

Centril/areweasyncyet.rs 0

Are we async yet?

push eventPolymathNetwork/Polymesh

Mudit Gupta

commit sha f22a675a6387b03e7be2918c4cd039e7e925bbac

Benchmarks for identity pallet (#671) * benchmark wasm * wip * Fix schema * Update to develop cryptography and generalise scopes * Fix some tests * Fix build * hardcoded proofs * Fixed scope * wip * added add_member to grouptrait * added more benchmarks * more benchmarks * more benchmarks * WeightInfo setup * fixed tests * Renamed trait * cleanup * minor refactoring * merge fix Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Mazdak Farrokhzad

commit sha e82bd4bff5c358218a4dbe679cafd6df84512854

mesh-1269: implement 'initiate_corporate_action'

view details

Mazdak Farrokhzad

commit sha 84873e154cb4ba09736a8b79c13b0cdb1e379436

mesh-1269: dedup target ids more

view details

Mazdak Farrokhzad

commit sha 5e666d24b5a39f1db99ab3562b7e48daa349b3a0

mesh-1269: enforce no dupe did tax

view details

Mazdak Farrokhzad

commit sha f1c6689163c85818b24fe8df9cb65bb983b520b7

mesh-1269: leave TODO re. scheduling

view details

push time in 3 hours

PullRequestReviewEvent
PullRequestReviewEvent
PullRequestReviewEvent

delete branch PolymathNetwork/Polymesh

delete branch : identity-benchmarks

delete time in 5 hours

PullRequestReviewEvent
PullRequestReviewEvent

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha aaa1d33c8c95b826d0610d356162ced2d608c99a

mesh-1269: enforce no dupe did tax

view details

Mazdak Farrokhzad

commit sha e480e22b8457833668f94c91477f0856f08330d0

mesh-1269: leave TODO re. scheduling

view details

push time in 3 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 0efbdac221f29a5779c25a606efe01e45ab5982c

MESH-1097: Configure Corporate Actions for an asset (#673) * identity: reuse 'ensure_authorization' more * assets: refactor with 'consume_auth_by_owner' * simplify 'find_ceiling' * mesh-1097: implement corporate actions config * mesh-1097: update schema * mesh-1097: extract weights * asset + identity: dedup some code * mesh-1097: polish + add tests * mesh-1097: nix not-holder checks * mesh-1097: address Mudit's review comments * mesh-1097: address Satyam's comments

view details

Satyam Agrawal

commit sha c115b5640badee9ef3878ff18de4d0a75ea8a691

Rename Authorize to affirm (#676) * rename authorize to affirm * minor fixes * fix cli/tests * minor fixes * fix liniting Co-authored-by: satyam <satyam@secureblocks.io> Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Satyam Agrawal

commit sha abc59a7ee4a45c7eae0709d748460951921ec141

MESH-1314/ [Staking] Permissioned entity (#672) * permissioned entity * add runtime upgrade * create migrate_map_keys_and_value * fix chain spec * improve miration function * fix genesis config * rename entity to identity Co-authored-by: satyam <satyam@secureblocks.io> Co-authored-by: Adam Dossa <adam.dossa@gmail.com> Co-authored-by: poly-auto-merge[bot] <65769705+poly-auto-merge[bot]@users.noreply.github.com>

view details

Mazdak Farrokhzad

commit sha be426a09de5494410d3a129a82feee78f29b1506

mesh-1269: implement 'initiate_corporate_action'

view details

Mazdak Farrokhzad

commit sha 95bae69ec0a5ad620447603b54d0cbc97da721f4

mesh-1269: dedup target ids more

view details

Mazdak Farrokhzad

commit sha 17a764bc2cd7d4c9a6dd8d57320bb70e86a0ff12

mesh-1269: enforce no dupe did tax

view details

Mazdak Farrokhzad

commit sha 6a6c029ee7ff689a3b761a946946949816cc3f6f

mesh-1269: leave TODO re. scheduling

view details

push time in 3 days

delete branch PolymathNetwork/Polymesh

delete branch : mesh-1097

delete time in 3 days

Pull request review commentPolymathNetwork/Polymesh

Make PortfolioNumber start from 1 instead of 0

 impl<T: Trait> Module<T> {         <PortfolioAssetBalances<T>>::insert(PortfolioId::default_portfolio(did), ticker, balance);     } -    /// Returns the next portfolio number of a given identity and increments the stored number.-    fn get_next_portfolio_number(did: &IdentityId) -> PortfolioNumber {-        let num = Self::next_portfolio_number(did);-        <NextPortfolioNumber>::insert(did, num + 1);+    /// Returns the next portfolio number of a given identity.+    pub fn get_next_portfolio_number(did: &IdentityId) -> PortfolioNumber {+        Self::portfolio_count(did) + 1+    }++    /// Returns the next portfolio number of a given identity and updates the count in storage+    fn next_portfolio_number(did: &IdentityId) -> PortfolioNumber {+        let num = Self::get_next_portfolio_number(did);+        PortfolioCount::insert(did, num);         num
        PortfolioCount::mutate(did, |num| mem::replace(num, *num + 1))
maxsam4

comment created time in 3 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventPolymathNetwork/Polymesh

Vladimir Komendantskiy

commit sha f4c88a3a4f487f1e8e3f60276ab7c1260d758029

Mesh 1323: use of scheduler pallet in bridge (#669) * import the scheduler pallet * calling the schedule dispatchable * defined the Scheduler type parameter in the bridge * removed MaxScheduledPerBlock * Revert "removed MaxScheduledPerBlock" This reverts commit ad3fa278c130a797b2c53a76e6d4b9c8b8eec3e4. * uncommented MaxScheduledPerBlock * testnet runtime scheduler fixes, bridge runtime upgrade * printing failed to schedule bridge txs * removal of timelocked txs state and functions * bridge test fixes * weight test fix * schedule all timelocked transactions after upgrade * review comments * updated Cargo.lock * simplified schedule_call Co-authored-by: Adam Dossa <adam.dossa@gmail.com> Co-authored-by: poly-auto-merge[bot] <65769705+poly-auto-merge[bot]@users.noreply.github.com>

view details

Joel Moore

commit sha 58f2de2c2435c9af71d51cbfd57167019f0190ac

Mesh 1310/ensure 11_governance_management can be re-run without restarting node (#674) * minor changes * minor change * fixed re-run issue * minor change Co-authored-by: Adam Dossa <adam.dossa@gmail.com> Co-authored-by: poly-auto-merge[bot] <65769705+poly-auto-merge[bot]@users.noreply.github.com>

view details

Satyam Agrawal

commit sha 4a49f3ae2eaabba7dec3d56ddf337e0a1d61f412

MESH-1312/ Simplifies commissions (#670) * simplifies commissions * fix linting * add runtime upgrade * re-add the commission type in schema * minor fixes * remove white trailing spaces * fix error message Co-authored-by: satyam <satyam@secureblocks.io> Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Mudit Gupta

commit sha 77be4d04c6bab390f3e46443ff69915f67f64c08

Simplify compliance_canTransfer RPC (#677) * Fetch PIA directly in compliance_canTransfer * Fixed tests Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Mazdak Farrokhzad

commit sha fde7aba6f7e8f32fc7fbf467a217eb0dd6e654a3

identity: reuse 'ensure_authorization' more

view details

Mazdak Farrokhzad

commit sha ff094c6568de2f48fa8adaeea98c8a3c5ef3c36f

assets: refactor with 'consume_auth_by_owner'

view details

Mazdak Farrokhzad

commit sha ece69c10c3e0296db54d063b83abfcc7edb8b883

simplify 'find_ceiling'

view details

Mazdak Farrokhzad

commit sha 7c4e8de4f51212ee8cc44adbfaab57bf9db3d3e8

mesh-1097: implement corporate actions config

view details

Mazdak Farrokhzad

commit sha efa40a0637916083f5aba0f7f21fdb2d85606c80

mesh-1097: update schema

view details

Mazdak Farrokhzad

commit sha d87dae66ec6b0bbb651976ea73b3b7110af21ae4

mesh-1097: extract weights

view details

Mazdak Farrokhzad

commit sha 9659e27c2fc684554a1da2dd17a8b29e44777427

asset + identity: dedup some code

view details

Mazdak Farrokhzad

commit sha dfb3bb9a9bd0917773c91e566f7d4c56ea2e81c1

mesh-1097: polish + add tests

view details

Mazdak Farrokhzad

commit sha d67799108b823b1a99473ecf1b79dba1915136fe

mesh-1097: nix not-holder checks

view details

Mazdak Farrokhzad

commit sha ec97ffdc46fc80d2ca697d3a9e385db8f881bb8b

mesh-1097: address Mudit's review comments

view details

Mazdak Farrokhzad

commit sha 3c1a9deeb42d9d514f8ca429e965f4605c4c5866

mesh-1097: address Satyam's comments

view details

Mazdak Farrokhzad

commit sha 583e3a352cd20d9e9761b30012d07dfef346035f

mesh-1269: implement 'initiate_corporate_action'

view details

Mazdak Farrokhzad

commit sha fd0e282429aabe718e6077798fc7d78c662359f6

mesh-1269: dedup target ids more

view details

Mazdak Farrokhzad

commit sha 517e2555e726ee7ba1535198fb3825d9e2bcd7f2

mesh-1269: enforce no dupe did tax

view details

Mazdak Farrokhzad

commit sha c846683c8f905030bd65609e08e1da4a2320c46d

mesh-1269: leave TODO re. scheduling

view details

push time in 3 days

push eventPolymathNetwork/Polymesh

Vladimir Komendantskiy

commit sha f4c88a3a4f487f1e8e3f60276ab7c1260d758029

Mesh 1323: use of scheduler pallet in bridge (#669) * import the scheduler pallet * calling the schedule dispatchable * defined the Scheduler type parameter in the bridge * removed MaxScheduledPerBlock * Revert "removed MaxScheduledPerBlock" This reverts commit ad3fa278c130a797b2c53a76e6d4b9c8b8eec3e4. * uncommented MaxScheduledPerBlock * testnet runtime scheduler fixes, bridge runtime upgrade * printing failed to schedule bridge txs * removal of timelocked txs state and functions * bridge test fixes * weight test fix * schedule all timelocked transactions after upgrade * review comments * updated Cargo.lock * simplified schedule_call Co-authored-by: Adam Dossa <adam.dossa@gmail.com> Co-authored-by: poly-auto-merge[bot] <65769705+poly-auto-merge[bot]@users.noreply.github.com>

view details

Joel Moore

commit sha 58f2de2c2435c9af71d51cbfd57167019f0190ac

Mesh 1310/ensure 11_governance_management can be re-run without restarting node (#674) * minor changes * minor change * fixed re-run issue * minor change Co-authored-by: Adam Dossa <adam.dossa@gmail.com> Co-authored-by: poly-auto-merge[bot] <65769705+poly-auto-merge[bot]@users.noreply.github.com>

view details

Satyam Agrawal

commit sha 4a49f3ae2eaabba7dec3d56ddf337e0a1d61f412

MESH-1312/ Simplifies commissions (#670) * simplifies commissions * fix linting * add runtime upgrade * re-add the commission type in schema * minor fixes * remove white trailing spaces * fix error message Co-authored-by: satyam <satyam@secureblocks.io> Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Mudit Gupta

commit sha 77be4d04c6bab390f3e46443ff69915f67f64c08

Simplify compliance_canTransfer RPC (#677) * Fetch PIA directly in compliance_canTransfer * Fixed tests Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Mazdak Farrokhzad

commit sha fde7aba6f7e8f32fc7fbf467a217eb0dd6e654a3

identity: reuse 'ensure_authorization' more

view details

Mazdak Farrokhzad

commit sha ff094c6568de2f48fa8adaeea98c8a3c5ef3c36f

assets: refactor with 'consume_auth_by_owner'

view details

Mazdak Farrokhzad

commit sha ece69c10c3e0296db54d063b83abfcc7edb8b883

simplify 'find_ceiling'

view details

Mazdak Farrokhzad

commit sha 7c4e8de4f51212ee8cc44adbfaab57bf9db3d3e8

mesh-1097: implement corporate actions config

view details

Mazdak Farrokhzad

commit sha efa40a0637916083f5aba0f7f21fdb2d85606c80

mesh-1097: update schema

view details

Mazdak Farrokhzad

commit sha d87dae66ec6b0bbb651976ea73b3b7110af21ae4

mesh-1097: extract weights

view details

Mazdak Farrokhzad

commit sha 9659e27c2fc684554a1da2dd17a8b29e44777427

asset + identity: dedup some code

view details

Mazdak Farrokhzad

commit sha dfb3bb9a9bd0917773c91e566f7d4c56ea2e81c1

mesh-1097: polish + add tests

view details

Mazdak Farrokhzad

commit sha d67799108b823b1a99473ecf1b79dba1915136fe

mesh-1097: nix not-holder checks

view details

Mazdak Farrokhzad

commit sha ec97ffdc46fc80d2ca697d3a9e385db8f881bb8b

mesh-1097: address Mudit's review comments

view details

Mazdak Farrokhzad

commit sha 3c1a9deeb42d9d514f8ca429e965f4605c4c5866

mesh-1097: address Satyam's comments

view details

push time in 3 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1269: Initiating a Corporate Action

 decl_module! {         /// ## Errors         /// - `UnauthorizedAsAgent` if `origin` is not `ticker`'s sole CAA (owner is not necessarily the CAA).         #[weight = <T as Trait>::WeightInfo::set_did_withholding_tax()]-        pub fn set_did_withholding_tax(origin, ticker: Ticker, taxed_did: IdentityId, tax: Option<Permill>) {+        pub fn set_did_withholding_tax(origin, ticker: Ticker, taxed_did: IdentityId, tax: Option<Tax>) {             let caa = Self::ensure_ca_agent(origin, ticker)?;-            DidWitholdingTax::mutate(ticker, taxed_did, |slot| *slot = tax);+            DidWitholdingTax::mutate(ticker, |whts| {+                match (whts.iter().position(|(did, _)| did == &taxed_did), tax) {+                    (None, Some(tax)) => whts.push((taxed_did, tax)),+                    (Some(idx), None) => drop(whts.swap_remove(idx)),+                    (Some(idx), Some(tax)) => whts[idx] = (taxed_did, tax),+                    (None, None) => {}+                }+            });             Self::deposit_event(Event::DidWithholdingTaxChanged(caa, ticker, taxed_did, tax));         }++        /// Initiates a CA for `ticker` of `kind` with `details` and other provided arguments.+        ///+        /// ## Arguments+        /// - `ticker` that the CA is made for.+        /// - `kind` of CA being initiated.+        /// - `record_date`, if any, to calculate the impact of this CA.+        ///    If provided, this results in a scheduled balance snapshot ("checkpoint") at the date.+        /// - `details` of the CA in free-text form, up to a certain number of bytes in length.+        /// - `targets`, if any, which this CA is relevant/irrelevant to.+        ///    Overrides, if provided, the default at the asset level (`set_default_targets`).+        /// - `default_wt`, if any, is the default withholding tax to use for this CA.+        ///    Overrides, if provided, the default at the asset level (`set_default_withholding_tax`).+        /// - `wt`, if any, provides per-DID withholding tax overrides.+        ///    Overrides, if provided, the default at the asset level (`set_did_withholding_tax`).+        ///+        /// # Errors+        /// - `DetailsTooLong` if `details.len()` goes beyond `max_details_length`.+        /// - `UnauthorizedAsAgent` if `origin` is not `ticker`'s sole CAA (owner is not necessarily the CAA).+        /// - `LocalCAIdOverflow` in the unlikely event that so many CAs were created for this `ticker`,+        ///   that integer overflow would have occured if instead allowed.+        #[weight = <T as Trait>::WeightInfo::initiate_corporate_action()]+        pub fn initiate_corporate_action(+            origin,+            ticker: Ticker,+            kind: CAKind,+            record_date: Option<Moment>,+            details: CADetails,+            targets: Option<TargetIdentities>,+            default_wt: Option<Tax>,+            wt: Option<Vec<(IdentityId, Tax)>>,+        ) {+            // Ensure that `details` is short enough.+            details+                .len()+                .try_into()+                .ok()+                .filter(|&len: &u32| len <= Self::max_details_length())+                .ok_or(Error::<T>::DetailsTooLong)?;++            // Ensure that CAA is calling.+            let caa = Self::ensure_ca_agent(origin, ticker)?;++            // Ensure that the next local CA ID doesn't overflow.+            let local_id = CAIdSequence::get(ticker);+            let next_id = local_id.0.checked_add(1).map(LocalCAId).ok_or(Error::<T>::LocalCAIdOverflow)?;+            let id = CAId { ticker, local_id };++            // Create a checkpoint at `record_date`, if any.+            if let Some(record_date) = record_date {+                <Asset<T>>::_create_checkpoint_emit(ticker, record_date, caa)?;

Filed as https://polymath.atlassian.net/browse/MESH-1337.

Centril

comment created time in 3 days

PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

MESH-1097: Configure Corporate Actions for an asset

+// This file is part of the Polymesh distribution (https://github.com/PolymathNetwork/Polymesh).+// Copyright (c) 2020 Polymath++// This program is free software: you can redistribute it and/or modify+// it under the terms of the GNU General Public License as published by+// the Free Software Foundation, version 3.++// This program is distributed in the hope that it will be useful, but+// WITHOUT ANY WARRANTY; without even the implied warranty of+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU+// General Public License for more details.++// You should have received a copy of the GNU General Public License+// along with this program. If not, see <http://www.gnu.org/licenses/>.++//! # Corporate Actions module.+//!+//! TODO

Filed as https://polymath.atlassian.net/browse/MESH-1336.

Centril

comment created time in 3 days

PullRequestReviewEvent

delete branch PolymathNetwork/Polymesh

delete branch : rpc-update

delete time in 3 days

delete branch PolymathNetwork/Polymesh

delete branch : MESH-1264/use-scheduler-pallet-in-bridge

delete time in 4 days

PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

Mesh 1310/ensure 11_governance_management can be re-run without restarting node

 async function main() {   const testEntities = await reqImports.initMain(api);    let alice = testEntities[0];+  let dave = await reqImports.generateRandomEntity(api);   let bob = await reqImports.generateRandomEntity(api);   let govCommittee1 = testEntities[2];   let govCommittee2 = testEntities[3]; -  await reqImports.createIdentities(api, [bob, govCommittee1, govCommittee2], alice);--  // Bob needs some funds to use.-  await reqImports.distributePolyBatch(api, [bob], reqImports.transfer_amount, alice);+  await sendTx(alice, api.tx.sudo.sudo(api.tx.pips.setDefaultEnactmentPeriod(10)));+  await reqImports.createIdentities(api, [bob, dave, govCommittee1, govCommittee2], alice); -  await sendTx(alice, api.tx.staking.bond(bob.publicKey, 20000, "Staked"));+  // Bob and Dave needs some funds to use.+  await reqImports.distributePolyBatch(api, [bob, dave], reqImports.transfer_amount, alice); +  await sendTx(dave, api.tx.staking.bond(bob.publicKey, 20000, "Staked"));   // Create a PIP which is then amended.   const setLimit = api.tx.pips.setActivePipLimit(42);+  // Create a PIP, but first placing the cool-off period.+  await sendTx(alice, api.tx.sudo.sudo(api.tx.pips.setProposalCoolOffPeriod(10)));++  let firstPipCount = await api.query.pips.pipIdSequence();   await sendTx(bob, api.tx.pips.propose(setLimit, 10000000000, "google.com", "first"));-  await sendTx(bob, api.tx.pips.amendProposal(0, "www.facebook.com", null)); +  await sendTx(bob, api.tx.pips.amendProposal(firstPipCount, "www.facebook.com", null));+    // Create a PIP, but first remove the cool-off period.   await sendTx(alice, api.tx.sudo.sudo(api.tx.pips.setProposalCoolOffPeriod(0)));++  let secondPipCount = await api.query.pips.pipIdSequence();   await sendTx(bob, api.tx.pips.propose(setLimit, 10000000000, "google.com", "second"));    // GC needs some funds to use.   await reqImports.distributePolyBatch(api, [govCommittee1, govCommittee2], reqImports.transfer_amount, alice);    // Snapshot and approve second PIP.   await sendTx(govCommittee1, api.tx.pips.snapshot());-  const approvePIP = api.tx.pips.enactSnapshotResults([[1, { "Approve": "" }]]);+  const approvePIP = api.tx.pips.enactSnapshotResults([[secondPipCount, { "Approve": "" }]]);   const voteApprove = api.tx.polymeshCommittee.voteOrPropose(true, approvePIP);   await sendTx(govCommittee1, voteApprove);   await sendTx(govCommittee2, voteApprove); -  // Finally reschedule, demonstrating that it had been scheduled.-  await sendTx(alice, api.tx.pips.rescheduleExecution(1, null));+  // Reject the first PIP+  const rejectPIP = api.tx.pips.rejectProposal(firstPipCount);+  const voteReject = api.tx.polymeshCommittee.voteOrPropose(true, rejectPIP);+  await sendTx(govCommittee1, voteReject);+  await sendTx(govCommittee2, voteReject);

Let's dedup this logic.

JMoore96

comment created time in 4 days

PullRequestReviewEvent

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 59b940b3f0b506db372c7976a545e89515cafbb2

mesh-1269: leave TODO re. scheduling

view details

push time in 4 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 4e59d116d85a38ed9ebc34012c27e2fa7434e5dc

mesh-1269: dedup target ids more

view details

Mazdak Farrokhzad

commit sha ecb8d4ab477fcbf803c67bcf17b02060a53bb430

mesh-1269: enforce no dupe did tax

view details

push time in 4 days

pull request commentPolymathNetwork/Polymesh

[WIP] Fixed the computation of the next checkpoint

Code LGTM; Awaiting tests =P

vkomenda

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+use super::{+    storage::{register_keyring_account, TestStorage},+    ExtBuilder,+};+use pallet_asset::{self as asset, AssetType};+use pallet_compliance_manager as compliance_manager;+use pallet_settlement::{self as settlement, VenueDetails, VenueType};+use pallet_sto::{self as sto, Fundraiser, FundraiserTier, PriceTier};+use polymesh_primitives::{PortfolioId, Ticker};++use frame_support::{assert_noop, assert_ok};+use sp_runtime::DispatchError;+use sp_std::convert::TryFrom;+use test_client::AccountKeyring;++type Origin = <TestStorage as frame_system::Trait>::Origin;+type Asset = asset::Module<TestStorage>;+type STO = sto::Module<TestStorage>;+type Error = sto::Error<TestStorage>;+type PortfolioError = pallet_portfolio::Error<TestStorage>;+type ComplianceManager = compliance_manager::Module<TestStorage>;+type Settlement = settlement::Module<TestStorage>;+type Timestamp = pallet_timestamp::Module<TestStorage>;++#[test]+fn raise_happy_path_ext() {+    ExtBuilder::default()+        .set_max_legs_allowed(2)+        .build()+        .execute_with(raise_happy_path);+}+#[test]+fn raise_unhappy_path_ext() {+    ExtBuilder::default()+        .set_max_legs_allowed(2)+        .build()+        .execute_with(raise_unhappy_path);+}++fn create_asset(origin: Origin, ticker: Ticker, supply: u128) {+    assert_ok!(Asset::create_asset(+        origin,+        vec![0x01].into(),+        ticker,+        supply,+        true,+        AssetType::default(),+        vec![],+        None,+    ));+}++fn empty_compliance(origin: Origin, ticker: Ticker) {+    assert_ok!(ComplianceManager::add_compliance_requirement(+        origin,+        ticker,+        vec![],+        vec![]+    ));+}++fn raise_happy_path() {+    let alice_did = register_keyring_account(AccountKeyring::Alice).unwrap();+    let alice_signed = Origin::signed(AccountKeyring::Alice.public());+    let alice = AccountKeyring::Alice.public();+    let alice_portfolio = PortfolioId::default_portfolio(alice_did);+    let bob_did = register_keyring_account(AccountKeyring::Bob).unwrap();+    let bob_signed = Origin::signed(AccountKeyring::Bob.public());+    let _bob = AccountKeyring::Bob.public();+    let bob_portfolio = PortfolioId::default_portfolio(bob_did);++    // Register tokens+    let offering_ticker = Ticker::try_from(&[0x01][..]).unwrap();+    let raise_ticker = Ticker::try_from(&[0x02][..]).unwrap();+    create_asset(alice_signed.clone(), offering_ticker, 1_000_000);+    create_asset(alice_signed.clone(), raise_ticker, 1_000_000);++    assert_ok!(Asset::unsafe_transfer(+        alice_portfolio,+        bob_portfolio,+        &raise_ticker,+        1_000_000+    ));++    empty_compliance(alice_signed.clone(), offering_ticker);+    empty_compliance(alice_signed.clone(), raise_ticker);++    // Register a venue+    let venue_counter = Settlement::venue_counter();+    assert_ok!(Settlement::create_venue(+        alice_signed.clone(),+        VenueDetails::default(),+        vec![alice],+        VenueType::Sto+    ));++    let amount = 100u128;+    let alice_init_offering = Asset::balance_of(&offering_ticker, alice_did);+    let bob_init_offering = Asset::balance_of(&offering_ticker, bob_did);+    let alice_init_raise = Asset::balance_of(&raise_ticker, alice_did);+    let bob_init_raise = Asset::balance_of(&raise_ticker, bob_did);++    // Alice starts a fundraiser+    let fundraiser_id = STO::fundraiser_count(offering_ticker);+    assert_ok!(STO::create_fundraiser(+        alice_signed.clone(),+        alice_portfolio,+        offering_ticker,+        alice_portfolio,+        raise_ticker,+        vec![PriceTier {+            total: 1_000_000u128,+            price: 1u128+        }],+        venue_counter,+        None,+        None,+    ));+    assert_eq!(+        STO::fundraisers(offering_ticker, fundraiser_id),+        Some(Fundraiser {+            creator: alice_did,+            offering_portfolio: alice_portfolio,+            offering_asset: offering_ticker,+            raising_portfolio: alice_portfolio,+            raising_asset: raise_ticker,+            tiers: vec![FundraiserTier {+                total: 1_000_000u128,+                remaining: 1_000_000u128,+                price: 1u128+            }],+            venue_id: venue_counter,+            start: Timestamp::get(),+            end: None,+            frozen: false+        })+    );++    assert_eq!(+        Asset::balance_of(&offering_ticker, alice_did),+        alice_init_offering+    );+    assert_eq!(+        Asset::balance_of(&offering_ticker, bob_did),+        bob_init_offering+    );+    assert_eq!(+        Asset::balance_of(&raise_ticker, alice_did),+        alice_init_raise+    );+    assert_eq!(Asset::balance_of(&raise_ticker, bob_did), bob_init_raise);++    // Bob invests in Alice's fundraiser+    assert_ok!(STO::invest(+        bob_signed.clone(),+        bob_portfolio,+        bob_portfolio,+        offering_ticker,+        fundraiser_id,+        amount.into(),+        Some(2u128),+        None+    ));++    assert_eq!(+        STO::fundraisers(offering_ticker, 1),+        Some(Fundraiser {+            creator: alice_did,+            offering_portfolio: alice_portfolio,+            offering_asset: offering_ticker,+            raising_portfolio: alice_portfolio,+            raising_asset: raise_ticker,+            tiers: vec![FundraiserTier {+                total: 1_000_000u128,+                remaining: (1_000_000 - amount).into(),+                price: 1u128+            }],+            venue_id: venue_counter,+            start: Timestamp::get(),+            end: None,+            frozen: false+        })+    );

This is almost the same as the assert before, aside from - amount and the id; let's dedup.

CJP10

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+// Copyright (c) 2020 Polymath++//! # Sto Module+//!+//! Sto module creates and manages security token offerings+//!+//! ## Overview+//!+//! Primary issuance agent's can create and manage fundraisers of assets.+//! Fundraisers are of fixed supply, with optional expiry and tiered pricing.+//! Fundraisers allow a single payment asset, known as the raising asset.+//! Investors can invest through on-chain balance or off-chain receipts.+//!+//! ## Dispatchable Functions+//!+//! - `create_fundraiser` - Create a new fundraiser.+//! - `invest` - Invest in a fundraiser.+//! - `freeze_fundraiser` - Freeze a fundraiser.+//! - `unfreeze_fundraiser` - Unfreeze a fundraiser.+//! - `modify_fundraiser_window` - Modify the time window a fundraiser is active.+//! - `stop` - stop a fundraiser.++#![cfg_attr(not(feature = "std"), no_std)]+#![recursion_limit = "256"]++use codec::{Decode, Encode};+use frame_support::{+    decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure,+};+use frame_system::ensure_signed;+use pallet_identity as identity;+use pallet_portfolio::{self as portfolio, PortfolioAssetBalances, Trait as PortfolioTrait};+use pallet_settlement::{+    self as settlement, Leg, ReceiptDetails, SettlementType, Trait as SettlementTrait, VenueInfo,+    VenueType,+};+use pallet_timestamp::{self as timestamp, Trait as TimestampTrait};+use polymesh_common_utilities::{+    portfolio::PortfolioSubTrait,+    traits::{asset::Trait as AssetTrait, identity::Trait as IdentityTrait},+    with_transaction, CommonTrait, Context,+};+use polymesh_primitives::{IdentityId, PortfolioId, Ticker};+use sp_runtime::traits::{CheckedAdd, CheckedMul};+use sp_std::{collections::btree_set::BTreeSet, prelude::*};++type Identity<T> = identity::Module<T>;+type Settlement<T> = settlement::Module<T>;+type Timestamp<T> = timestamp::Module<T>;+type Portfolio<T> = portfolio::Module<T>;++/// Details about the Fundraiser+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct Fundraiser<Balance, Moment> {+    /// The primary issuance agent that created the `Fundraiser`+    pub creator: IdentityId,+    /// Portfolio containing the asset being offered+    pub offering_portfolio: PortfolioId,+    /// Asset being offered+    pub offering_asset: Ticker,+    /// Portfolio receiving funds raised+    pub raising_portfolio: PortfolioId,+    /// Asset to receive payment in+    pub raising_asset: Ticker,+    /// Tiers of the fundraiser.+    /// Each tier has a set amount of tokens available at a fixed price.+    /// The sum of the tiers is the total amount available in this fundraiser.+    pub tiers: Vec<FundraiserTier<Balance>>,+    /// Id of the venue to use for this fundraise+    pub venue_id: u64,+    /// Start of the fundraiser+    pub start: Moment,+    /// End of the fundraiser+    pub end: Option<Moment>,+    /// Fundraiser is frozen+    pub frozen: bool,+}++/// Single tier of a tiered pricing model+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct PriceTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+}++/// Single price tier of a `Fundraiser`.+/// Similar to a `PriceTier` but with an extra field `remaining` for tracking the amount available for purchase in a tier.+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct FundraiserTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+    /// Total amount remaining for sale, set to `total` and decremented until `0`.+    pub remaining: Balance,+}++impl<Balance: Clone> Into<FundraiserTier<Balance>> for PriceTier<Balance> {+    fn into(self) -> FundraiserTier<Balance> {+        FundraiserTier {+            total: self.total.clone(),+            price: self.price.clone(),+            remaining: self.total.clone(),+        }+    }+}++pub trait Trait: frame_system::Trait + IdentityTrait + SettlementTrait + PortfolioTrait {+    /// The overarching event type.+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;+}++decl_event!(+    pub enum Event<T>+    where+        Balance = <T as CommonTrait>::Balance,+        Moment = <T as TimestampTrait>::Moment,+    {+        /// A new fundraiser has been created+        /// (primary issuance agent, fundraiser)+        FundraiserCreated(IdentityId, Fundraiser<Balance, Moment>),+        /// An investor invested in the fundraiser+        /// (offering token, raise token, offering_token_amount, raise_token_amount, fundraiser_id)+        FundsRaised(IdentityId, Ticker, Ticker, Balance, Balance, u64),+    }+);++decl_error! {+    /// Errors for the Settlement module.+    pub enum Error for Module<T: Trait> {+        /// Sender does not have required permissions+        Unauthorized,+        /// An arithmetic operation overflowed+        Overflow,+        /// Not enough tokens left for sale+        InsufficientTokensRemaining,+        /// Fundraiser not found+        FundraiserNotFound,+        /// Fundraiser is frozen+        FundraiserFrozen,+        /// Interacting with a fundraiser past the end `Moment`.+        FundraiserExpired,+        /// Interacting with a fundraiser before the start `Moment`.+        FundraiserNotStarted,+        /// Using an invalid venue+        InvalidVenue,+        /// Using an invalid portfolio+        InvalidPortfolio,+        /// An individual price tier was invalid or a set of price tiers was invalid+        InvalidPriceTiers,+        /// Window (start time, end time) has invalid parameters, e.g start time is after end time.+        InvalidOfferingWindow,+        /// Price of an investment exceeded the max price+        MaxPriceExceeded,+    }+}++decl_storage! {+    trait Store for Module<T: Trait> as StoCapped {+        /// All fundraisers that are currently running. (ticker, fundraiser_id) -> Fundraiser+        Fundraisers get(fn fundraisers): double_map hasher(blake2_128_concat) Ticker, hasher(twox_64_concat) u64 => Option<Fundraiser<T::Balance, T::Moment>>;+        /// Total fundraisers created for a token+        FundraiserCount get(fn fundraiser_count): map hasher(twox_64_concat) Ticker => u64;+    }+}++decl_module! {+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+        type Error = Error<T>;++        fn deposit_event() = default;++        /// Create a new fundraiser.+        ///+        /// * `offering_portfolio` - Portfolio containing the `offering_asset`.+        /// * `offering_asset` - Asset being offered.+        /// * `raising_portfolio` - Portfolio containing the `raising_asset`.+        /// * `raising_asset` - Asset being exchanged for `offering_asset` on investment.+        /// * `tiers` - Price tiers to charge investors on investment.+        /// * `venue_id` - Venue to handle settlement.+        /// * `start` - Fundraiser start time, if `None` the fundraiser will start immediately.+        /// * `end` - Fundraiser end time, if `None` the fundraiser will never expire.+        ///+        /// # Weight+        /// `800_000_000` placeholder+        #[weight = 800_000_000]+        pub fn create_fundraiser(+            origin,+            offering_portfolio: PortfolioId,+            offering_asset: Ticker,+            raising_portfolio: PortfolioId,+            raising_asset: Ticker,+            tiers: Vec<PriceTier<T::Balance>>,+            venue_id: u64,+            start: Option<T::Moment>,+            end: Option<T::Moment>,+        ) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let venue = VenueInfo::get(venue_id).ok_or(Error::<T>::InvalidVenue)?;+            ensure!(+                venue.creator == did && venue.venue_type == VenueType::Sto,+                Error::<T>::InvalidVenue+            );++            Self::ensure_custody_and_asset(did, raising_portfolio, raising_asset)?;+            Self::ensure_custody_and_asset(did, offering_portfolio, offering_asset)?;++            ensure!(+                tiers.len() > 0 && tiers.len() <= 10 && tiers.iter().all(|t| t.total > 0.into()),+                Error::<T>::InvalidPriceTiers+            );++            let offering_amount: T::Balance = tiers+                .iter()+                .map(|t| t.total)+                .fold(0.into(), |total, x| total + x);++            let start = start.unwrap_or_else(Timestamp::<T>::get);+            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Portfolio<T>>::lock_tokens(&offering_portfolio, &offering_asset, &offering_amount)?;++            let fundraiser_id = Self::fundraiser_count(offering_asset);+            let fundraiser = Fundraiser {+                    creator: did,+                    offering_portfolio,+                    offering_asset,+                    raising_portfolio,+                    raising_asset,+                    tiers: tiers.into_iter().map(Into::into).collect(),+                    venue_id,+                    start,+                    end,+                    frozen: false,+            };++            FundraiserCount::insert(offering_asset, fundraiser_id + 1);+            <Fundraisers<T>>::insert(+                offering_asset,+                fundraiser_id,+                fundraiser.clone()+            );++            Self::deposit_event(+                RawEvent::FundraiserCreated(did, fundraiser)+            );++            Ok(())+        }++        /// Invest in a fundraiser.+        ///+        /// * `investment_portfolio` - Portfolio that `offering_asset` will be deposited in.+        /// * `funding_portfolio` - Portfolio that will fund the investment.+        /// * `offering_asset` - Asset to invest in.+        /// * `fundraiser_id` - ID of the fundraiser to invest in.+        /// * `investment_amount` - Amount of `offering_asset` to invest in.+        /// * `max_price` - Maximum price to pay per unit of `offering_asset`, If `None`there are no constraints on price.+        /// * `receipt` - Off-chain receipt to use instead of on-chain balance in `funding_portfolio`.+        ///+        /// # Weight+        /// `2_000_000_000` placeholder+        #[weight = 2_000_000_000]+        pub fn invest(+            origin,+            investment_portfolio: PortfolioId,+            funding_portfolio: PortfolioId,+            offering_asset: Ticker,+            fundraiser_id: u64,+            investment_amount: T::Balance,+            max_price: Option<T::Balance>,+            receipt: Option<ReceiptDetails<T::AccountId, T::OffChainSignature>>+        ) -> DispatchResult {+            let sender = ensure_signed(origin.clone())?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;++            <Portfolio<T>>::ensure_portfolio_custody(investment_portfolio, did)?;+            <Portfolio<T>>::ensure_portfolio_custody(funding_portfolio, did)?;++            let now = Timestamp::<T>::get();+            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id).ok_or(Error::<T>::FundraiserNotFound)?;+            ensure!(!fundraiser.frozen, Error::<T>::FundraiserFrozen);+            ensure!(+                fundraiser.start <= now && fundraiser.end.filter(|e| now >= *e).is_none(),+                Error::<T>::FundraiserExpired+            );++            // Remaining tokens to fulfil the investment amount+            let mut remaining = investment_amount;+            // Total cost to to fulfil the investment amount.+            // Primary use is to calculate the blended price (offering_token_amount / cost).+            // Blended price must be <= to max_price or the investment will fail.+            let mut cost = T::Balance::from(0);+            // Individual purchases from each tier that accumulate to fulfil the investment amount.+            // Tuple of (tier_id, amount to purchase from that tier).+            let mut purchases = Vec::new();++            for (id, tier) in fundraiser.tiers.iter().enumerate().filter(|(_, tier)| tier.remaining > 0.into()) {+                // fulfilled the investment amount+                if remaining == 0.into() {+                    break+                }++                // Check if this tier can fulfil the remaining investment amount.+                // If it can, purchase the remaining amount.+                // If it can't, purchase what's remaining in the tier.+                let purchase_amount = if tier.remaining >= remaining {+                    remaining+                } else {+                    tier.remaining+                };++                remaining -= purchase_amount;+                purchases.push((id, purchase_amount));+                cost = purchase_amount+                    .checked_mul(&tier.price)+                    .and_then(|pa| cost.checked_add(&pa))+                    .ok_or(Error::<T>::Overflow)?;+            }++            ensure!(remaining == 0.into(), Error::<T>::InsufficientTokensRemaining);+            ensure!(+                max_price.map(|max_price| cost <= max_price * investment_amount).unwrap_or(true),+                Error::<T>::MaxPriceExceeded+            );++            let legs = vec![+                Leg {+                    from: fundraiser.offering_portfolio,+                    to: investment_portfolio,+                    asset: fundraiser.offering_asset,+                    amount: investment_amount+                },+                Leg {+                    from: funding_portfolio,+                    to: fundraiser.raising_portfolio,+                    asset: fundraiser.raising_asset,+                    amount: cost+                }+            ];++            with_transaction(|| {+               let instruction_id = Settlement::<T>::base_add_instruction(+                    fundraiser.creator,+                    fundraiser.venue_id,+                    SettlementType::SettleOnAuthorization,+                    None,+                    legs+                )?;++                let portfolios = vec![investment_portfolio, funding_portfolio];++                match receipt {+                    Some(receipt) => Settlement::<T>::authorize_with_receipts(+                        origin,+                        instruction_id,+                        vec![receipt],+                        portfolios+                    ).map_err(|e| e.error)?,+                    None => Settlement::<T>::authorize_instruction(origin, instruction_id, portfolios).map_err(|e| e.error)?,+                };++                let portfolios= vec![fundraiser.offering_portfolio, fundraiser.raising_portfolio].into_iter().collect::<BTreeSet<_>>();+                Settlement::<T>::unsafe_authorize_instruction(fundraiser.creator, instruction_id, portfolios)?;++                <Fundraisers<T>>::mutate(offering_asset, fundraiser_id, |fundraiser| {+                    if let Some(fundraiser) = fundraiser {+                        for (id, amount) in purchases {+                            fundraiser.tiers[id].remaining -= amount;+                        }+                    }+                });++                Ok(())+            })+        }++        /// Freeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to freeze.+        /// * `fundraiser_id` - ID of the fundraiser to freeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn freeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, true)+        }++        /// Unfreeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to unfreeze.+        /// * `fundraiser_id` - ID of the fundraiser to unfreeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn unfreeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, false)+        }++        /// Modify the time window a fundraiser is active+        ///+        /// * `offering_asset` - Asset to modify.+        /// * `fundraiser_id` - ID of the fundraiser to modify.+        /// * `start` - New start of the fundraiser.+        /// * `end` - New end of the fundraiser to modify.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn modify_fundraiser_window(origin, offering_asset: Ticker, fundraiser_id: u64, start: T::Moment, end: Option<T::Moment>) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let now = Timestamp::<T>::get();+            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id)+                .ok_or(Error::<T>::FundraiserNotFound)?;+            if let Some(end) = fundraiser.end {+                ensure!(now < end, Error::<T>::FundraiserExpired);+            };++            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Fundraisers<T>>::mutate(offering_asset, fundraiser_id, |fundraiser| {+                if let Some(fundraiser) = fundraiser {+                    fundraiser.start = start;+                    fundraiser.end = end;+                }+            });+            Ok(())+        }++        /// Stop a fundraiser.+        ///+        /// * `offering_asset` - Asset to stop.+        /// * `fundraiser_id` - ID of the fundraiser to stop.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn stop(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;
            let did = Identity::<T>::ensure_perms(origin)?;
CJP10

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+// Copyright (c) 2020 Polymath++//! # Sto Module+//!+//! Sto module creates and manages security token offerings+//!+//! ## Overview+//!+//! Primary issuance agent's can create and manage fundraisers of assets.+//! Fundraisers are of fixed supply, with optional expiry and tiered pricing.+//! Fundraisers allow a single payment asset, known as the raising asset.+//! Investors can invest through on-chain balance or off-chain receipts.+//!+//! ## Dispatchable Functions+//!+//! - `create_fundraiser` - Create a new fundraiser.+//! - `invest` - Invest in a fundraiser.+//! - `freeze_fundraiser` - Freeze a fundraiser.+//! - `unfreeze_fundraiser` - Unfreeze a fundraiser.+//! - `modify_fundraiser_window` - Modify the time window a fundraiser is active.+//! - `stop` - stop a fundraiser.++#![cfg_attr(not(feature = "std"), no_std)]+#![recursion_limit = "256"]++use codec::{Decode, Encode};+use frame_support::{+    decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure,+};+use frame_system::ensure_signed;+use pallet_identity as identity;+use pallet_portfolio::{self as portfolio, PortfolioAssetBalances, Trait as PortfolioTrait};+use pallet_settlement::{+    self as settlement, Leg, ReceiptDetails, SettlementType, Trait as SettlementTrait, VenueInfo,+    VenueType,+};+use pallet_timestamp::{self as timestamp, Trait as TimestampTrait};+use polymesh_common_utilities::{+    portfolio::PortfolioSubTrait,+    traits::{asset::Trait as AssetTrait, identity::Trait as IdentityTrait},+    with_transaction, CommonTrait, Context,+};+use polymesh_primitives::{IdentityId, PortfolioId, Ticker};+use sp_runtime::traits::{CheckedAdd, CheckedMul};+use sp_std::{collections::btree_set::BTreeSet, prelude::*};++type Identity<T> = identity::Module<T>;+type Settlement<T> = settlement::Module<T>;+type Timestamp<T> = timestamp::Module<T>;+type Portfolio<T> = portfolio::Module<T>;++/// Details about the Fundraiser+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct Fundraiser<Balance, Moment> {+    /// The primary issuance agent that created the `Fundraiser`+    pub creator: IdentityId,+    /// Portfolio containing the asset being offered+    pub offering_portfolio: PortfolioId,+    /// Asset being offered+    pub offering_asset: Ticker,+    /// Portfolio receiving funds raised+    pub raising_portfolio: PortfolioId,+    /// Asset to receive payment in+    pub raising_asset: Ticker,+    /// Tiers of the fundraiser.+    /// Each tier has a set amount of tokens available at a fixed price.+    /// The sum of the tiers is the total amount available in this fundraiser.+    pub tiers: Vec<FundraiserTier<Balance>>,+    /// Id of the venue to use for this fundraise+    pub venue_id: u64,+    /// Start of the fundraiser+    pub start: Moment,+    /// End of the fundraiser+    pub end: Option<Moment>,+    /// Fundraiser is frozen+    pub frozen: bool,+}++/// Single tier of a tiered pricing model+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct PriceTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+}++/// Single price tier of a `Fundraiser`.+/// Similar to a `PriceTier` but with an extra field `remaining` for tracking the amount available for purchase in a tier.+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct FundraiserTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+    /// Total amount remaining for sale, set to `total` and decremented until `0`.+    pub remaining: Balance,+}++impl<Balance: Clone> Into<FundraiserTier<Balance>> for PriceTier<Balance> {+    fn into(self) -> FundraiserTier<Balance> {+        FundraiserTier {+            total: self.total.clone(),+            price: self.price.clone(),+            remaining: self.total.clone(),+        }+    }+}++pub trait Trait: frame_system::Trait + IdentityTrait + SettlementTrait + PortfolioTrait {+    /// The overarching event type.+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;+}++decl_event!(+    pub enum Event<T>+    where+        Balance = <T as CommonTrait>::Balance,+        Moment = <T as TimestampTrait>::Moment,+    {+        /// A new fundraiser has been created+        /// (primary issuance agent, fundraiser)+        FundraiserCreated(IdentityId, Fundraiser<Balance, Moment>),+        /// An investor invested in the fundraiser+        /// (offering token, raise token, offering_token_amount, raise_token_amount, fundraiser_id)+        FundsRaised(IdentityId, Ticker, Ticker, Balance, Balance, u64),+    }+);++decl_error! {+    /// Errors for the Settlement module.+    pub enum Error for Module<T: Trait> {+        /// Sender does not have required permissions+        Unauthorized,+        /// An arithmetic operation overflowed+        Overflow,+        /// Not enough tokens left for sale+        InsufficientTokensRemaining,+        /// Fundraiser not found+        FundraiserNotFound,+        /// Fundraiser is frozen+        FundraiserFrozen,+        /// Interacting with a fundraiser past the end `Moment`.+        FundraiserExpired,+        /// Interacting with a fundraiser before the start `Moment`.+        FundraiserNotStarted,+        /// Using an invalid venue+        InvalidVenue,+        /// Using an invalid portfolio+        InvalidPortfolio,+        /// An individual price tier was invalid or a set of price tiers was invalid+        InvalidPriceTiers,+        /// Window (start time, end time) has invalid parameters, e.g start time is after end time.+        InvalidOfferingWindow,+        /// Price of an investment exceeded the max price+        MaxPriceExceeded,+    }+}++decl_storage! {+    trait Store for Module<T: Trait> as StoCapped {+        /// All fundraisers that are currently running. (ticker, fundraiser_id) -> Fundraiser+        Fundraisers get(fn fundraisers): double_map hasher(blake2_128_concat) Ticker, hasher(twox_64_concat) u64 => Option<Fundraiser<T::Balance, T::Moment>>;+        /// Total fundraisers created for a token+        FundraiserCount get(fn fundraiser_count): map hasher(twox_64_concat) Ticker => u64;+    }+}++decl_module! {+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+        type Error = Error<T>;++        fn deposit_event() = default;++        /// Create a new fundraiser.+        ///+        /// * `offering_portfolio` - Portfolio containing the `offering_asset`.+        /// * `offering_asset` - Asset being offered.+        /// * `raising_portfolio` - Portfolio containing the `raising_asset`.+        /// * `raising_asset` - Asset being exchanged for `offering_asset` on investment.+        /// * `tiers` - Price tiers to charge investors on investment.+        /// * `venue_id` - Venue to handle settlement.+        /// * `start` - Fundraiser start time, if `None` the fundraiser will start immediately.+        /// * `end` - Fundraiser end time, if `None` the fundraiser will never expire.+        ///+        /// # Weight+        /// `800_000_000` placeholder+        #[weight = 800_000_000]+        pub fn create_fundraiser(+            origin,+            offering_portfolio: PortfolioId,+            offering_asset: Ticker,+            raising_portfolio: PortfolioId,+            raising_asset: Ticker,+            tiers: Vec<PriceTier<T::Balance>>,+            venue_id: u64,+            start: Option<T::Moment>,+            end: Option<T::Moment>,+        ) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let venue = VenueInfo::get(venue_id).ok_or(Error::<T>::InvalidVenue)?;+            ensure!(+                venue.creator == did && venue.venue_type == VenueType::Sto,+                Error::<T>::InvalidVenue+            );++            Self::ensure_custody_and_asset(did, raising_portfolio, raising_asset)?;+            Self::ensure_custody_and_asset(did, offering_portfolio, offering_asset)?;++            ensure!(+                tiers.len() > 0 && tiers.len() <= 10 && tiers.iter().all(|t| t.total > 0.into()),+                Error::<T>::InvalidPriceTiers+            );++            let offering_amount: T::Balance = tiers+                .iter()+                .map(|t| t.total)+                .fold(0.into(), |total, x| total + x);++            let start = start.unwrap_or_else(Timestamp::<T>::get);+            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Portfolio<T>>::lock_tokens(&offering_portfolio, &offering_asset, &offering_amount)?;++            let fundraiser_id = Self::fundraiser_count(offering_asset);+            let fundraiser = Fundraiser {+                    creator: did,+                    offering_portfolio,+                    offering_asset,+                    raising_portfolio,+                    raising_asset,+                    tiers: tiers.into_iter().map(Into::into).collect(),+                    venue_id,+                    start,+                    end,+                    frozen: false,+            };++            FundraiserCount::insert(offering_asset, fundraiser_id + 1);+            <Fundraisers<T>>::insert(+                offering_asset,+                fundraiser_id,+                fundraiser.clone()+            );++            Self::deposit_event(+                RawEvent::FundraiserCreated(did, fundraiser)+            );++            Ok(())+        }++        /// Invest in a fundraiser.+        ///+        /// * `investment_portfolio` - Portfolio that `offering_asset` will be deposited in.+        /// * `funding_portfolio` - Portfolio that will fund the investment.+        /// * `offering_asset` - Asset to invest in.+        /// * `fundraiser_id` - ID of the fundraiser to invest in.+        /// * `investment_amount` - Amount of `offering_asset` to invest in.+        /// * `max_price` - Maximum price to pay per unit of `offering_asset`, If `None`there are no constraints on price.+        /// * `receipt` - Off-chain receipt to use instead of on-chain balance in `funding_portfolio`.+        ///+        /// # Weight+        /// `2_000_000_000` placeholder+        #[weight = 2_000_000_000]+        pub fn invest(+            origin,+            investment_portfolio: PortfolioId,+            funding_portfolio: PortfolioId,+            offering_asset: Ticker,+            fundraiser_id: u64,+            investment_amount: T::Balance,+            max_price: Option<T::Balance>,+            receipt: Option<ReceiptDetails<T::AccountId, T::OffChainSignature>>+        ) -> DispatchResult {+            let sender = ensure_signed(origin.clone())?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;++            <Portfolio<T>>::ensure_portfolio_custody(investment_portfolio, did)?;+            <Portfolio<T>>::ensure_portfolio_custody(funding_portfolio, did)?;++            let now = Timestamp::<T>::get();+            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id).ok_or(Error::<T>::FundraiserNotFound)?;+            ensure!(!fundraiser.frozen, Error::<T>::FundraiserFrozen);+            ensure!(+                fundraiser.start <= now && fundraiser.end.filter(|e| now >= *e).is_none(),+                Error::<T>::FundraiserExpired+            );++            // Remaining tokens to fulfil the investment amount+            let mut remaining = investment_amount;+            // Total cost to to fulfil the investment amount.+            // Primary use is to calculate the blended price (offering_token_amount / cost).+            // Blended price must be <= to max_price or the investment will fail.+            let mut cost = T::Balance::from(0);+            // Individual purchases from each tier that accumulate to fulfil the investment amount.+            // Tuple of (tier_id, amount to purchase from that tier).+            let mut purchases = Vec::new();++            for (id, tier) in fundraiser.tiers.iter().enumerate().filter(|(_, tier)| tier.remaining > 0.into()) {+                // fulfilled the investment amount+                if remaining == 0.into() {+                    break+                }++                // Check if this tier can fulfil the remaining investment amount.+                // If it can, purchase the remaining amount.+                // If it can't, purchase what's remaining in the tier.+                let purchase_amount = if tier.remaining >= remaining {+                    remaining+                } else {+                    tier.remaining+                };++                remaining -= purchase_amount;+                purchases.push((id, purchase_amount));+                cost = purchase_amount+                    .checked_mul(&tier.price)+                    .and_then(|pa| cost.checked_add(&pa))+                    .ok_or(Error::<T>::Overflow)?;+            }++            ensure!(remaining == 0.into(), Error::<T>::InsufficientTokensRemaining);+            ensure!(+                max_price.map(|max_price| cost <= max_price * investment_amount).unwrap_or(true),+                Error::<T>::MaxPriceExceeded+            );++            let legs = vec![+                Leg {+                    from: fundraiser.offering_portfolio,+                    to: investment_portfolio,+                    asset: fundraiser.offering_asset,+                    amount: investment_amount+                },+                Leg {+                    from: funding_portfolio,+                    to: fundraiser.raising_portfolio,+                    asset: fundraiser.raising_asset,+                    amount: cost+                }+            ];++            with_transaction(|| {+               let instruction_id = Settlement::<T>::base_add_instruction(+                    fundraiser.creator,+                    fundraiser.venue_id,+                    SettlementType::SettleOnAuthorization,+                    None,+                    legs+                )?;++                let portfolios = vec![investment_portfolio, funding_portfolio];++                match receipt {+                    Some(receipt) => Settlement::<T>::authorize_with_receipts(+                        origin,+                        instruction_id,+                        vec![receipt],+                        portfolios+                    ).map_err(|e| e.error)?,+                    None => Settlement::<T>::authorize_instruction(origin, instruction_id, portfolios).map_err(|e| e.error)?,+                };++                let portfolios= vec![fundraiser.offering_portfolio, fundraiser.raising_portfolio].into_iter().collect::<BTreeSet<_>>();+                Settlement::<T>::unsafe_authorize_instruction(fundraiser.creator, instruction_id, portfolios)?;++                <Fundraisers<T>>::mutate(offering_asset, fundraiser_id, |fundraiser| {+                    if let Some(fundraiser) = fundraiser {+                        for (id, amount) in purchases {+                            fundraiser.tiers[id].remaining -= amount;+                        }+                    }+                });++                Ok(())+            })+        }++        /// Freeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to freeze.+        /// * `fundraiser_id` - ID of the fundraiser to freeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn freeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, true)+        }++        /// Unfreeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to unfreeze.+        /// * `fundraiser_id` - ID of the fundraiser to unfreeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn unfreeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, false)+        }++        /// Modify the time window a fundraiser is active+        ///+        /// * `offering_asset` - Asset to modify.+        /// * `fundraiser_id` - ID of the fundraiser to modify.+        /// * `start` - New start of the fundraiser.+        /// * `end` - New end of the fundraiser to modify.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn modify_fundraiser_window(origin, offering_asset: Ticker, fundraiser_id: u64, start: T::Moment, end: Option<T::Moment>) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);
            let did = Self::ensure_perms_pia(did, &offering_asset)?;
CJP10

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+// Copyright (c) 2020 Polymath++//! # Sto Module+//!+//! Sto module creates and manages security token offerings+//!+//! ## Overview+//!+//! Primary issuance agent's can create and manage fundraisers of assets.+//! Fundraisers are of fixed supply, with optional expiry and tiered pricing.+//! Fundraisers allow a single payment asset, known as the raising asset.+//! Investors can invest through on-chain balance or off-chain receipts.+//!+//! ## Dispatchable Functions+//!+//! - `create_fundraiser` - Create a new fundraiser.+//! - `invest` - Invest in a fundraiser.+//! - `freeze_fundraiser` - Freeze a fundraiser.+//! - `unfreeze_fundraiser` - Unfreeze a fundraiser.+//! - `modify_fundraiser_window` - Modify the time window a fundraiser is active.+//! - `stop` - stop a fundraiser.++#![cfg_attr(not(feature = "std"), no_std)]+#![recursion_limit = "256"]++use codec::{Decode, Encode};+use frame_support::{+    decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure,+};+use frame_system::ensure_signed;+use pallet_identity as identity;+use pallet_portfolio::{self as portfolio, PortfolioAssetBalances, Trait as PortfolioTrait};+use pallet_settlement::{+    self as settlement, Leg, ReceiptDetails, SettlementType, Trait as SettlementTrait, VenueInfo,+    VenueType,+};+use pallet_timestamp::{self as timestamp, Trait as TimestampTrait};+use polymesh_common_utilities::{+    portfolio::PortfolioSubTrait,+    traits::{asset::Trait as AssetTrait, identity::Trait as IdentityTrait},+    with_transaction, CommonTrait, Context,+};+use polymesh_primitives::{IdentityId, PortfolioId, Ticker};+use sp_runtime::traits::{CheckedAdd, CheckedMul};+use sp_std::{collections::btree_set::BTreeSet, prelude::*};++type Identity<T> = identity::Module<T>;+type Settlement<T> = settlement::Module<T>;+type Timestamp<T> = timestamp::Module<T>;+type Portfolio<T> = portfolio::Module<T>;++/// Details about the Fundraiser+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct Fundraiser<Balance, Moment> {+    /// The primary issuance agent that created the `Fundraiser`+    pub creator: IdentityId,+    /// Portfolio containing the asset being offered+    pub offering_portfolio: PortfolioId,+    /// Asset being offered+    pub offering_asset: Ticker,+    /// Portfolio receiving funds raised+    pub raising_portfolio: PortfolioId,+    /// Asset to receive payment in+    pub raising_asset: Ticker,+    /// Tiers of the fundraiser.+    /// Each tier has a set amount of tokens available at a fixed price.+    /// The sum of the tiers is the total amount available in this fundraiser.+    pub tiers: Vec<FundraiserTier<Balance>>,+    /// Id of the venue to use for this fundraise+    pub venue_id: u64,+    /// Start of the fundraiser+    pub start: Moment,+    /// End of the fundraiser+    pub end: Option<Moment>,+    /// Fundraiser is frozen+    pub frozen: bool,+}++/// Single tier of a tiered pricing model+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct PriceTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+}++/// Single price tier of a `Fundraiser`.+/// Similar to a `PriceTier` but with an extra field `remaining` for tracking the amount available for purchase in a tier.+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct FundraiserTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+    /// Total amount remaining for sale, set to `total` and decremented until `0`.+    pub remaining: Balance,+}++impl<Balance: Clone> Into<FundraiserTier<Balance>> for PriceTier<Balance> {+    fn into(self) -> FundraiserTier<Balance> {+        FundraiserTier {+            total: self.total.clone(),+            price: self.price.clone(),+            remaining: self.total.clone(),+        }+    }+}++pub trait Trait: frame_system::Trait + IdentityTrait + SettlementTrait + PortfolioTrait {+    /// The overarching event type.+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;+}++decl_event!(+    pub enum Event<T>+    where+        Balance = <T as CommonTrait>::Balance,+        Moment = <T as TimestampTrait>::Moment,+    {+        /// A new fundraiser has been created+        /// (primary issuance agent, fundraiser)+        FundraiserCreated(IdentityId, Fundraiser<Balance, Moment>),+        /// An investor invested in the fundraiser+        /// (offering token, raise token, offering_token_amount, raise_token_amount, fundraiser_id)+        FundsRaised(IdentityId, Ticker, Ticker, Balance, Balance, u64),+    }+);++decl_error! {+    /// Errors for the Settlement module.+    pub enum Error for Module<T: Trait> {+        /// Sender does not have required permissions+        Unauthorized,+        /// An arithmetic operation overflowed+        Overflow,+        /// Not enough tokens left for sale+        InsufficientTokensRemaining,+        /// Fundraiser not found+        FundraiserNotFound,+        /// Fundraiser is frozen+        FundraiserFrozen,+        /// Interacting with a fundraiser past the end `Moment`.+        FundraiserExpired,+        /// Interacting with a fundraiser before the start `Moment`.+        FundraiserNotStarted,+        /// Using an invalid venue+        InvalidVenue,+        /// Using an invalid portfolio+        InvalidPortfolio,+        /// An individual price tier was invalid or a set of price tiers was invalid+        InvalidPriceTiers,+        /// Window (start time, end time) has invalid parameters, e.g start time is after end time.+        InvalidOfferingWindow,+        /// Price of an investment exceeded the max price+        MaxPriceExceeded,+    }+}++decl_storage! {+    trait Store for Module<T: Trait> as StoCapped {+        /// All fundraisers that are currently running. (ticker, fundraiser_id) -> Fundraiser+        Fundraisers get(fn fundraisers): double_map hasher(blake2_128_concat) Ticker, hasher(twox_64_concat) u64 => Option<Fundraiser<T::Balance, T::Moment>>;+        /// Total fundraisers created for a token+        FundraiserCount get(fn fundraiser_count): map hasher(twox_64_concat) Ticker => u64;+    }+}++decl_module! {+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+        type Error = Error<T>;++        fn deposit_event() = default;++        /// Create a new fundraiser.+        ///+        /// * `offering_portfolio` - Portfolio containing the `offering_asset`.+        /// * `offering_asset` - Asset being offered.+        /// * `raising_portfolio` - Portfolio containing the `raising_asset`.+        /// * `raising_asset` - Asset being exchanged for `offering_asset` on investment.+        /// * `tiers` - Price tiers to charge investors on investment.+        /// * `venue_id` - Venue to handle settlement.+        /// * `start` - Fundraiser start time, if `None` the fundraiser will start immediately.+        /// * `end` - Fundraiser end time, if `None` the fundraiser will never expire.+        ///+        /// # Weight+        /// `800_000_000` placeholder+        #[weight = 800_000_000]+        pub fn create_fundraiser(+            origin,+            offering_portfolio: PortfolioId,+            offering_asset: Ticker,+            raising_portfolio: PortfolioId,+            raising_asset: Ticker,+            tiers: Vec<PriceTier<T::Balance>>,+            venue_id: u64,+            start: Option<T::Moment>,+            end: Option<T::Moment>,+        ) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let venue = VenueInfo::get(venue_id).ok_or(Error::<T>::InvalidVenue)?;+            ensure!(+                venue.creator == did && venue.venue_type == VenueType::Sto,+                Error::<T>::InvalidVenue+            );++            Self::ensure_custody_and_asset(did, raising_portfolio, raising_asset)?;+            Self::ensure_custody_and_asset(did, offering_portfolio, offering_asset)?;++            ensure!(+                tiers.len() > 0 && tiers.len() <= 10 && tiers.iter().all(|t| t.total > 0.into()),+                Error::<T>::InvalidPriceTiers+            );++            let offering_amount: T::Balance = tiers+                .iter()+                .map(|t| t.total)+                .fold(0.into(), |total, x| total + x);++            let start = start.unwrap_or_else(Timestamp::<T>::get);+            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Portfolio<T>>::lock_tokens(&offering_portfolio, &offering_asset, &offering_amount)?;++            let fundraiser_id = Self::fundraiser_count(offering_asset);+            let fundraiser = Fundraiser {+                    creator: did,+                    offering_portfolio,+                    offering_asset,+                    raising_portfolio,+                    raising_asset,+                    tiers: tiers.into_iter().map(Into::into).collect(),+                    venue_id,+                    start,+                    end,+                    frozen: false,+            };++            FundraiserCount::insert(offering_asset, fundraiser_id + 1);+            <Fundraisers<T>>::insert(+                offering_asset,+                fundraiser_id,+                fundraiser.clone()+            );++            Self::deposit_event(+                RawEvent::FundraiserCreated(did, fundraiser)+            );++            Ok(())+        }++        /// Invest in a fundraiser.+        ///+        /// * `investment_portfolio` - Portfolio that `offering_asset` will be deposited in.+        /// * `funding_portfolio` - Portfolio that will fund the investment.+        /// * `offering_asset` - Asset to invest in.+        /// * `fundraiser_id` - ID of the fundraiser to invest in.+        /// * `investment_amount` - Amount of `offering_asset` to invest in.+        /// * `max_price` - Maximum price to pay per unit of `offering_asset`, If `None`there are no constraints on price.+        /// * `receipt` - Off-chain receipt to use instead of on-chain balance in `funding_portfolio`.+        ///+        /// # Weight+        /// `2_000_000_000` placeholder+        #[weight = 2_000_000_000]+        pub fn invest(+            origin,+            investment_portfolio: PortfolioId,+            funding_portfolio: PortfolioId,+            offering_asset: Ticker,+            fundraiser_id: u64,+            investment_amount: T::Balance,+            max_price: Option<T::Balance>,+            receipt: Option<ReceiptDetails<T::AccountId, T::OffChainSignature>>+        ) -> DispatchResult {+            let sender = ensure_signed(origin.clone())?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;++            <Portfolio<T>>::ensure_portfolio_custody(investment_portfolio, did)?;+            <Portfolio<T>>::ensure_portfolio_custody(funding_portfolio, did)?;++            let now = Timestamp::<T>::get();+            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id).ok_or(Error::<T>::FundraiserNotFound)?;+            ensure!(!fundraiser.frozen, Error::<T>::FundraiserFrozen);+            ensure!(+                fundraiser.start <= now && fundraiser.end.filter(|e| now >= *e).is_none(),+                Error::<T>::FundraiserExpired+            );++            // Remaining tokens to fulfil the investment amount+            let mut remaining = investment_amount;+            // Total cost to to fulfil the investment amount.+            // Primary use is to calculate the blended price (offering_token_amount / cost).+            // Blended price must be <= to max_price or the investment will fail.+            let mut cost = T::Balance::from(0);+            // Individual purchases from each tier that accumulate to fulfil the investment amount.+            // Tuple of (tier_id, amount to purchase from that tier).+            let mut purchases = Vec::new();++            for (id, tier) in fundraiser.tiers.iter().enumerate().filter(|(_, tier)| tier.remaining > 0.into()) {+                // fulfilled the investment amount+                if remaining == 0.into() {+                    break+                }++                // Check if this tier can fulfil the remaining investment amount.+                // If it can, purchase the remaining amount.+                // If it can't, purchase what's remaining in the tier.+                let purchase_amount = if tier.remaining >= remaining {+                    remaining+                } else {+                    tier.remaining+                };++                remaining -= purchase_amount;+                purchases.push((id, purchase_amount));+                cost = purchase_amount+                    .checked_mul(&tier.price)+                    .and_then(|pa| cost.checked_add(&pa))+                    .ok_or(Error::<T>::Overflow)?;+            }++            ensure!(remaining == 0.into(), Error::<T>::InsufficientTokensRemaining);+            ensure!(+                max_price.map(|max_price| cost <= max_price * investment_amount).unwrap_or(true),+                Error::<T>::MaxPriceExceeded+            );++            let legs = vec![+                Leg {+                    from: fundraiser.offering_portfolio,+                    to: investment_portfolio,+                    asset: fundraiser.offering_asset,+                    amount: investment_amount+                },+                Leg {+                    from: funding_portfolio,+                    to: fundraiser.raising_portfolio,+                    asset: fundraiser.raising_asset,+                    amount: cost+                }+            ];++            with_transaction(|| {+               let instruction_id = Settlement::<T>::base_add_instruction(+                    fundraiser.creator,+                    fundraiser.venue_id,+                    SettlementType::SettleOnAuthorization,+                    None,+                    legs+                )?;++                let portfolios = vec![investment_portfolio, funding_portfolio];++                match receipt {+                    Some(receipt) => Settlement::<T>::authorize_with_receipts(+                        origin,+                        instruction_id,+                        vec![receipt],+                        portfolios+                    ).map_err(|e| e.error)?,+                    None => Settlement::<T>::authorize_instruction(origin, instruction_id, portfolios).map_err(|e| e.error)?,+                };++                let portfolios= vec![fundraiser.offering_portfolio, fundraiser.raising_portfolio].into_iter().collect::<BTreeSet<_>>();+                Settlement::<T>::unsafe_authorize_instruction(fundraiser.creator, instruction_id, portfolios)?;++                <Fundraisers<T>>::mutate(offering_asset, fundraiser_id, |fundraiser| {+                    if let Some(fundraiser) = fundraiser {+                        for (id, amount) in purchases {+                            fundraiser.tiers[id].remaining -= amount;+                        }+                    }+                });++                Ok(())+            })+        }++        /// Freeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to freeze.+        /// * `fundraiser_id` - ID of the fundraiser to freeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn freeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, true)+        }++        /// Unfreeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to unfreeze.+        /// * `fundraiser_id` - ID of the fundraiser to unfreeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn unfreeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, false)+        }++        /// Modify the time window a fundraiser is active+        ///+        /// * `offering_asset` - Asset to modify.+        /// * `fundraiser_id` - ID of the fundraiser to modify.+        /// * `start` - New start of the fundraiser.+        /// * `end` - New end of the fundraiser to modify.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn modify_fundraiser_window(origin, offering_asset: Ticker, fundraiser_id: u64, start: T::Moment, end: Option<T::Moment>) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let now = Timestamp::<T>::get();+            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id)+                .ok_or(Error::<T>::FundraiserNotFound)?;+            if let Some(end) = fundraiser.end {+                ensure!(now < end, Error::<T>::FundraiserExpired);+            };++            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Fundraisers<T>>::mutate(offering_asset, fundraiser_id, |fundraiser| {+                if let Some(fundraiser) = fundraiser {+                    fundraiser.start = start;+                    fundraiser.end = end;+                }+            });+            Ok(())+        }++        /// Stop a fundraiser.+        ///+        /// * `offering_asset` - Asset to stop.+        /// * `fundraiser_id` - ID of the fundraiser to stop.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn stop(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;++            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id)+                .ok_or(Error::<T>::FundraiserNotFound)?;++            ensure!(+                T::Asset::primary_issuance_agent(&offering_asset) == did ||fundraiser.creator == did,+                Error::<T>::Unauthorized+            );++            let remaining_amount: T::Balance = fundraiser.tiers+                .iter()+                .map(|t| t.remaining)+                .fold(0.into(), |remaining, x| remaining + x);++            <Portfolio<T>>::unlock_tokens(&fundraiser.offering_portfolio, &fundraiser.offering_asset, &remaining_amount)?;+            <Fundraisers<T>>::remove(offering_asset, fundraiser_id);+            Ok(())+        }+    }+}++impl<T: Trait> Module<T> {+    fn set_frozen(+        did: IdentityId,+        offering_asset: Ticker,+        fundraiser_id: u64,+        frozen: bool,+    ) -> DispatchResult {
        origin: T::Origin,
        offering_asset: Ticker,
        fundraiser_id: u64,
        frozen: bool,
    ) -> DispatchResult {
        let did = Identity::<T>::ensure_perms(origin)?;
CJP10

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+// Copyright (c) 2020 Polymath++//! # Sto Module+//!+//! Sto module creates and manages security token offerings+//!+//! ## Overview+//!+//! Primary issuance agent's can create and manage fundraisers of assets.+//! Fundraisers are of fixed supply, with optional expiry and tiered pricing.+//! Fundraisers allow a single payment asset, known as the raising asset.+//! Investors can invest through on-chain balance or off-chain receipts.+//!+//! ## Dispatchable Functions+//!+//! - `create_fundraiser` - Create a new fundraiser.+//! - `invest` - Invest in a fundraiser.+//! - `freeze_fundraiser` - Freeze a fundraiser.+//! - `unfreeze_fundraiser` - Unfreeze a fundraiser.+//! - `modify_fundraiser_window` - Modify the time window a fundraiser is active.+//! - `stop` - stop a fundraiser.++#![cfg_attr(not(feature = "std"), no_std)]+#![recursion_limit = "256"]++use codec::{Decode, Encode};+use frame_support::{+    decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure,+};+use frame_system::ensure_signed;+use pallet_identity as identity;+use pallet_portfolio::{self as portfolio, PortfolioAssetBalances, Trait as PortfolioTrait};+use pallet_settlement::{+    self as settlement, Leg, ReceiptDetails, SettlementType, Trait as SettlementTrait, VenueInfo,+    VenueType,+};+use pallet_timestamp::{self as timestamp, Trait as TimestampTrait};+use polymesh_common_utilities::{+    portfolio::PortfolioSubTrait,+    traits::{asset::Trait as AssetTrait, identity::Trait as IdentityTrait},+    with_transaction, CommonTrait, Context,+};+use polymesh_primitives::{IdentityId, PortfolioId, Ticker};+use sp_runtime::traits::{CheckedAdd, CheckedMul};+use sp_std::{collections::btree_set::BTreeSet, prelude::*};++type Identity<T> = identity::Module<T>;+type Settlement<T> = settlement::Module<T>;+type Timestamp<T> = timestamp::Module<T>;+type Portfolio<T> = portfolio::Module<T>;++/// Details about the Fundraiser+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct Fundraiser<Balance, Moment> {+    /// The primary issuance agent that created the `Fundraiser`+    pub creator: IdentityId,+    /// Portfolio containing the asset being offered+    pub offering_portfolio: PortfolioId,+    /// Asset being offered+    pub offering_asset: Ticker,+    /// Portfolio receiving funds raised+    pub raising_portfolio: PortfolioId,+    /// Asset to receive payment in+    pub raising_asset: Ticker,+    /// Tiers of the fundraiser.+    /// Each tier has a set amount of tokens available at a fixed price.+    /// The sum of the tiers is the total amount available in this fundraiser.+    pub tiers: Vec<FundraiserTier<Balance>>,+    /// Id of the venue to use for this fundraise+    pub venue_id: u64,+    /// Start of the fundraiser+    pub start: Moment,+    /// End of the fundraiser+    pub end: Option<Moment>,+    /// Fundraiser is frozen+    pub frozen: bool,+}++/// Single tier of a tiered pricing model+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct PriceTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+}++/// Single price tier of a `Fundraiser`.+/// Similar to a `PriceTier` but with an extra field `remaining` for tracking the amount available for purchase in a tier.+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct FundraiserTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+    /// Total amount remaining for sale, set to `total` and decremented until `0`.+    pub remaining: Balance,+}++impl<Balance: Clone> Into<FundraiserTier<Balance>> for PriceTier<Balance> {+    fn into(self) -> FundraiserTier<Balance> {+        FundraiserTier {+            total: self.total.clone(),+            price: self.price.clone(),+            remaining: self.total.clone(),+        }+    }+}++pub trait Trait: frame_system::Trait + IdentityTrait + SettlementTrait + PortfolioTrait {+    /// The overarching event type.+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;+}++decl_event!(+    pub enum Event<T>+    where+        Balance = <T as CommonTrait>::Balance,+        Moment = <T as TimestampTrait>::Moment,+    {+        /// A new fundraiser has been created+        /// (primary issuance agent, fundraiser)+        FundraiserCreated(IdentityId, Fundraiser<Balance, Moment>),+        /// An investor invested in the fundraiser+        /// (offering token, raise token, offering_token_amount, raise_token_amount, fundraiser_id)+        FundsRaised(IdentityId, Ticker, Ticker, Balance, Balance, u64),+    }+);++decl_error! {+    /// Errors for the Settlement module.+    pub enum Error for Module<T: Trait> {+        /// Sender does not have required permissions+        Unauthorized,+        /// An arithmetic operation overflowed+        Overflow,+        /// Not enough tokens left for sale+        InsufficientTokensRemaining,+        /// Fundraiser not found+        FundraiserNotFound,+        /// Fundraiser is frozen+        FundraiserFrozen,+        /// Interacting with a fundraiser past the end `Moment`.+        FundraiserExpired,+        /// Interacting with a fundraiser before the start `Moment`.+        FundraiserNotStarted,+        /// Using an invalid venue+        InvalidVenue,+        /// Using an invalid portfolio+        InvalidPortfolio,+        /// An individual price tier was invalid or a set of price tiers was invalid+        InvalidPriceTiers,+        /// Window (start time, end time) has invalid parameters, e.g start time is after end time.+        InvalidOfferingWindow,+        /// Price of an investment exceeded the max price+        MaxPriceExceeded,+    }+}++decl_storage! {+    trait Store for Module<T: Trait> as StoCapped {+        /// All fundraisers that are currently running. (ticker, fundraiser_id) -> Fundraiser+        Fundraisers get(fn fundraisers): double_map hasher(blake2_128_concat) Ticker, hasher(twox_64_concat) u64 => Option<Fundraiser<T::Balance, T::Moment>>;+        /// Total fundraisers created for a token+        FundraiserCount get(fn fundraiser_count): map hasher(twox_64_concat) Ticker => u64;+    }+}++decl_module! {+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+        type Error = Error<T>;++        fn deposit_event() = default;++        /// Create a new fundraiser.+        ///+        /// * `offering_portfolio` - Portfolio containing the `offering_asset`.+        /// * `offering_asset` - Asset being offered.+        /// * `raising_portfolio` - Portfolio containing the `raising_asset`.+        /// * `raising_asset` - Asset being exchanged for `offering_asset` on investment.+        /// * `tiers` - Price tiers to charge investors on investment.+        /// * `venue_id` - Venue to handle settlement.+        /// * `start` - Fundraiser start time, if `None` the fundraiser will start immediately.+        /// * `end` - Fundraiser end time, if `None` the fundraiser will never expire.+        ///+        /// # Weight+        /// `800_000_000` placeholder+        #[weight = 800_000_000]+        pub fn create_fundraiser(+            origin,+            offering_portfolio: PortfolioId,+            offering_asset: Ticker,+            raising_portfolio: PortfolioId,+            raising_asset: Ticker,+            tiers: Vec<PriceTier<T::Balance>>,+            venue_id: u64,+            start: Option<T::Moment>,+            end: Option<T::Moment>,+        ) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let venue = VenueInfo::get(venue_id).ok_or(Error::<T>::InvalidVenue)?;+            ensure!(+                venue.creator == did && venue.venue_type == VenueType::Sto,+                Error::<T>::InvalidVenue+            );++            Self::ensure_custody_and_asset(did, raising_portfolio, raising_asset)?;+            Self::ensure_custody_and_asset(did, offering_portfolio, offering_asset)?;++            ensure!(+                tiers.len() > 0 && tiers.len() <= 10 && tiers.iter().all(|t| t.total > 0.into()),+                Error::<T>::InvalidPriceTiers+            );++            let offering_amount: T::Balance = tiers+                .iter()+                .map(|t| t.total)+                .fold(0.into(), |total, x| total + x);++            let start = start.unwrap_or_else(Timestamp::<T>::get);+            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Portfolio<T>>::lock_tokens(&offering_portfolio, &offering_asset, &offering_amount)?;++            let fundraiser_id = Self::fundraiser_count(offering_asset);+            let fundraiser = Fundraiser {+                    creator: did,+                    offering_portfolio,+                    offering_asset,+                    raising_portfolio,+                    raising_asset,+                    tiers: tiers.into_iter().map(Into::into).collect(),+                    venue_id,+                    start,+                    end,+                    frozen: false,+            };++            FundraiserCount::insert(offering_asset, fundraiser_id + 1);+            <Fundraisers<T>>::insert(+                offering_asset,+                fundraiser_id,+                fundraiser.clone()+            );++            Self::deposit_event(+                RawEvent::FundraiserCreated(did, fundraiser)+            );++            Ok(())+        }++        /// Invest in a fundraiser.+        ///+        /// * `investment_portfolio` - Portfolio that `offering_asset` will be deposited in.+        /// * `funding_portfolio` - Portfolio that will fund the investment.+        /// * `offering_asset` - Asset to invest in.+        /// * `fundraiser_id` - ID of the fundraiser to invest in.+        /// * `investment_amount` - Amount of `offering_asset` to invest in.+        /// * `max_price` - Maximum price to pay per unit of `offering_asset`, If `None`there are no constraints on price.+        /// * `receipt` - Off-chain receipt to use instead of on-chain balance in `funding_portfolio`.+        ///+        /// # Weight+        /// `2_000_000_000` placeholder+        #[weight = 2_000_000_000]+        pub fn invest(+            origin,+            investment_portfolio: PortfolioId,+            funding_portfolio: PortfolioId,+            offering_asset: Ticker,+            fundraiser_id: u64,+            investment_amount: T::Balance,+            max_price: Option<T::Balance>,+            receipt: Option<ReceiptDetails<T::AccountId, T::OffChainSignature>>+        ) -> DispatchResult {+            let sender = ensure_signed(origin.clone())?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;
            let did = Identity::<T>::ensure_perms(origin)?;
CJP10

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+// Copyright (c) 2020 Polymath++//! # Sto Module+//!+//! Sto module creates and manages security token offerings+//!+//! ## Overview+//!+//! Primary issuance agent's can create and manage fundraisers of assets.+//! Fundraisers are of fixed supply, with optional expiry and tiered pricing.+//! Fundraisers allow a single payment asset, known as the raising asset.+//! Investors can invest through on-chain balance or off-chain receipts.+//!+//! ## Dispatchable Functions+//!+//! - `create_fundraiser` - Create a new fundraiser.+//! - `invest` - Invest in a fundraiser.+//! - `freeze_fundraiser` - Freeze a fundraiser.+//! - `unfreeze_fundraiser` - Unfreeze a fundraiser.+//! - `modify_fundraiser_window` - Modify the time window a fundraiser is active.+//! - `stop` - stop a fundraiser.++#![cfg_attr(not(feature = "std"), no_std)]+#![recursion_limit = "256"]++use codec::{Decode, Encode};+use frame_support::{+    decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure,+};+use frame_system::ensure_signed;+use pallet_identity as identity;+use pallet_portfolio::{self as portfolio, PortfolioAssetBalances, Trait as PortfolioTrait};+use pallet_settlement::{+    self as settlement, Leg, ReceiptDetails, SettlementType, Trait as SettlementTrait, VenueInfo,+    VenueType,+};+use pallet_timestamp::{self as timestamp, Trait as TimestampTrait};+use polymesh_common_utilities::{+    portfolio::PortfolioSubTrait,+    traits::{asset::Trait as AssetTrait, identity::Trait as IdentityTrait},+    with_transaction, CommonTrait, Context,+};+use polymesh_primitives::{IdentityId, PortfolioId, Ticker};+use sp_runtime::traits::{CheckedAdd, CheckedMul};+use sp_std::{collections::btree_set::BTreeSet, prelude::*};++type Identity<T> = identity::Module<T>;+type Settlement<T> = settlement::Module<T>;+type Timestamp<T> = timestamp::Module<T>;+type Portfolio<T> = portfolio::Module<T>;++/// Details about the Fundraiser+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct Fundraiser<Balance, Moment> {+    /// The primary issuance agent that created the `Fundraiser`+    pub creator: IdentityId,+    /// Portfolio containing the asset being offered+    pub offering_portfolio: PortfolioId,+    /// Asset being offered+    pub offering_asset: Ticker,+    /// Portfolio receiving funds raised+    pub raising_portfolio: PortfolioId,+    /// Asset to receive payment in+    pub raising_asset: Ticker,+    /// Tiers of the fundraiser.+    /// Each tier has a set amount of tokens available at a fixed price.+    /// The sum of the tiers is the total amount available in this fundraiser.+    pub tiers: Vec<FundraiserTier<Balance>>,+    /// Id of the venue to use for this fundraise+    pub venue_id: u64,+    /// Start of the fundraiser+    pub start: Moment,+    /// End of the fundraiser+    pub end: Option<Moment>,+    /// Fundraiser is frozen+    pub frozen: bool,+}++/// Single tier of a tiered pricing model+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct PriceTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+}++/// Single price tier of a `Fundraiser`.+/// Similar to a `PriceTier` but with an extra field `remaining` for tracking the amount available for purchase in a tier.+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct FundraiserTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+    /// Total amount remaining for sale, set to `total` and decremented until `0`.+    pub remaining: Balance,+}++impl<Balance: Clone> Into<FundraiserTier<Balance>> for PriceTier<Balance> {+    fn into(self) -> FundraiserTier<Balance> {+        FundraiserTier {+            total: self.total.clone(),+            price: self.price.clone(),+            remaining: self.total.clone(),+        }+    }+}++pub trait Trait: frame_system::Trait + IdentityTrait + SettlementTrait + PortfolioTrait {+    /// The overarching event type.+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;+}++decl_event!(+    pub enum Event<T>+    where+        Balance = <T as CommonTrait>::Balance,+        Moment = <T as TimestampTrait>::Moment,+    {+        /// A new fundraiser has been created+        /// (primary issuance agent, fundraiser)+        FundraiserCreated(IdentityId, Fundraiser<Balance, Moment>),+        /// An investor invested in the fundraiser+        /// (offering token, raise token, offering_token_amount, raise_token_amount, fundraiser_id)+        FundsRaised(IdentityId, Ticker, Ticker, Balance, Balance, u64),+    }+);++decl_error! {+    /// Errors for the Settlement module.+    pub enum Error for Module<T: Trait> {+        /// Sender does not have required permissions+        Unauthorized,+        /// An arithmetic operation overflowed+        Overflow,+        /// Not enough tokens left for sale+        InsufficientTokensRemaining,+        /// Fundraiser not found+        FundraiserNotFound,+        /// Fundraiser is frozen+        FundraiserFrozen,+        /// Interacting with a fundraiser past the end `Moment`.+        FundraiserExpired,+        /// Interacting with a fundraiser before the start `Moment`.+        FundraiserNotStarted,+        /// Using an invalid venue+        InvalidVenue,+        /// Using an invalid portfolio+        InvalidPortfolio,+        /// An individual price tier was invalid or a set of price tiers was invalid+        InvalidPriceTiers,+        /// Window (start time, end time) has invalid parameters, e.g start time is after end time.+        InvalidOfferingWindow,+        /// Price of an investment exceeded the max price+        MaxPriceExceeded,+    }+}++decl_storage! {+    trait Store for Module<T: Trait> as StoCapped {+        /// All fundraisers that are currently running. (ticker, fundraiser_id) -> Fundraiser+        Fundraisers get(fn fundraisers): double_map hasher(blake2_128_concat) Ticker, hasher(twox_64_concat) u64 => Option<Fundraiser<T::Balance, T::Moment>>;+        /// Total fundraisers created for a token+        FundraiserCount get(fn fundraiser_count): map hasher(twox_64_concat) Ticker => u64;+    }+}++decl_module! {+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+        type Error = Error<T>;++        fn deposit_event() = default;++        /// Create a new fundraiser.+        ///+        /// * `offering_portfolio` - Portfolio containing the `offering_asset`.+        /// * `offering_asset` - Asset being offered.+        /// * `raising_portfolio` - Portfolio containing the `raising_asset`.+        /// * `raising_asset` - Asset being exchanged for `offering_asset` on investment.+        /// * `tiers` - Price tiers to charge investors on investment.+        /// * `venue_id` - Venue to handle settlement.+        /// * `start` - Fundraiser start time, if `None` the fundraiser will start immediately.+        /// * `end` - Fundraiser end time, if `None` the fundraiser will never expire.+        ///+        /// # Weight+        /// `800_000_000` placeholder+        #[weight = 800_000_000]+        pub fn create_fundraiser(+            origin,+            offering_portfolio: PortfolioId,+            offering_asset: Ticker,+            raising_portfolio: PortfolioId,+            raising_asset: Ticker,+            tiers: Vec<PriceTier<T::Balance>>,+            venue_id: u64,+            start: Option<T::Moment>,+            end: Option<T::Moment>,+        ) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let venue = VenueInfo::get(venue_id).ok_or(Error::<T>::InvalidVenue)?;+            ensure!(+                venue.creator == did && venue.venue_type == VenueType::Sto,+                Error::<T>::InvalidVenue+            );++            Self::ensure_custody_and_asset(did, raising_portfolio, raising_asset)?;+            Self::ensure_custody_and_asset(did, offering_portfolio, offering_asset)?;++            ensure!(+                tiers.len() > 0 && tiers.len() <= 10 && tiers.iter().all(|t| t.total > 0.into()),+                Error::<T>::InvalidPriceTiers+            );++            let offering_amount: T::Balance = tiers+                .iter()+                .map(|t| t.total)+                .fold(0.into(), |total, x| total + x);++            let start = start.unwrap_or_else(Timestamp::<T>::get);+            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Portfolio<T>>::lock_tokens(&offering_portfolio, &offering_asset, &offering_amount)?;++            let fundraiser_id = Self::fundraiser_count(offering_asset);+            let fundraiser = Fundraiser {+                    creator: did,+                    offering_portfolio,+                    offering_asset,+                    raising_portfolio,+                    raising_asset,+                    tiers: tiers.into_iter().map(Into::into).collect(),+                    venue_id,+                    start,+                    end,+                    frozen: false,+            };++            FundraiserCount::insert(offering_asset, fundraiser_id + 1);+            <Fundraisers<T>>::insert(+                offering_asset,+                fundraiser_id,+                fundraiser.clone()+            );++            Self::deposit_event(+                RawEvent::FundraiserCreated(did, fundraiser)+            );++            Ok(())+        }++        /// Invest in a fundraiser.+        ///+        /// * `investment_portfolio` - Portfolio that `offering_asset` will be deposited in.+        /// * `funding_portfolio` - Portfolio that will fund the investment.+        /// * `offering_asset` - Asset to invest in.+        /// * `fundraiser_id` - ID of the fundraiser to invest in.+        /// * `investment_amount` - Amount of `offering_asset` to invest in.+        /// * `max_price` - Maximum price to pay per unit of `offering_asset`, If `None`there are no constraints on price.+        /// * `receipt` - Off-chain receipt to use instead of on-chain balance in `funding_portfolio`.+        ///+        /// # Weight+        /// `2_000_000_000` placeholder+        #[weight = 2_000_000_000]+        pub fn invest(+            origin,+            investment_portfolio: PortfolioId,+            funding_portfolio: PortfolioId,+            offering_asset: Ticker,+            fundraiser_id: u64,+            investment_amount: T::Balance,+            max_price: Option<T::Balance>,+            receipt: Option<ReceiptDetails<T::AccountId, T::OffChainSignature>>+        ) -> DispatchResult {+            let sender = ensure_signed(origin.clone())?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;++            <Portfolio<T>>::ensure_portfolio_custody(investment_portfolio, did)?;+            <Portfolio<T>>::ensure_portfolio_custody(funding_portfolio, did)?;++            let now = Timestamp::<T>::get();+            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id).ok_or(Error::<T>::FundraiserNotFound)?;+            ensure!(!fundraiser.frozen, Error::<T>::FundraiserFrozen);+            ensure!(+                fundraiser.start <= now && fundraiser.end.filter(|e| now >= *e).is_none(),+                Error::<T>::FundraiserExpired+            );++            // Remaining tokens to fulfil the investment amount+            let mut remaining = investment_amount;+            // Total cost to to fulfil the investment amount.+            // Primary use is to calculate the blended price (offering_token_amount / cost).+            // Blended price must be <= to max_price or the investment will fail.+            let mut cost = T::Balance::from(0);+            // Individual purchases from each tier that accumulate to fulfil the investment amount.+            // Tuple of (tier_id, amount to purchase from that tier).+            let mut purchases = Vec::new();++            for (id, tier) in fundraiser.tiers.iter().enumerate().filter(|(_, tier)| tier.remaining > 0.into()) {+                // fulfilled the investment amount+                if remaining == 0.into() {+                    break+                }++                // Check if this tier can fulfil the remaining investment amount.+                // If it can, purchase the remaining amount.+                // If it can't, purchase what's remaining in the tier.+                let purchase_amount = if tier.remaining >= remaining {+                    remaining+                } else {+                    tier.remaining+                };++                remaining -= purchase_amount;+                purchases.push((id, purchase_amount));+                cost = purchase_amount+                    .checked_mul(&tier.price)+                    .and_then(|pa| cost.checked_add(&pa))+                    .ok_or(Error::<T>::Overflow)?;+            }++            ensure!(remaining == 0.into(), Error::<T>::InsufficientTokensRemaining);+            ensure!(+                max_price.map(|max_price| cost <= max_price * investment_amount).unwrap_or(true),+                Error::<T>::MaxPriceExceeded+            );++            let legs = vec![+                Leg {+                    from: fundraiser.offering_portfolio,+                    to: investment_portfolio,+                    asset: fundraiser.offering_asset,+                    amount: investment_amount+                },+                Leg {+                    from: funding_portfolio,+                    to: fundraiser.raising_portfolio,+                    asset: fundraiser.raising_asset,+                    amount: cost+                }+            ];++            with_transaction(|| {+               let instruction_id = Settlement::<T>::base_add_instruction(+                    fundraiser.creator,+                    fundraiser.venue_id,+                    SettlementType::SettleOnAuthorization,+                    None,+                    legs+                )?;++                let portfolios = vec![investment_portfolio, funding_portfolio];++                match receipt {+                    Some(receipt) => Settlement::<T>::authorize_with_receipts(+                        origin,+                        instruction_id,+                        vec![receipt],+                        portfolios+                    ).map_err(|e| e.error)?,+                    None => Settlement::<T>::authorize_instruction(origin, instruction_id, portfolios).map_err(|e| e.error)?,+                };++                let portfolios= vec![fundraiser.offering_portfolio, fundraiser.raising_portfolio].into_iter().collect::<BTreeSet<_>>();+                Settlement::<T>::unsafe_authorize_instruction(fundraiser.creator, instruction_id, portfolios)?;++                <Fundraisers<T>>::mutate(offering_asset, fundraiser_id, |fundraiser| {+                    if let Some(fundraiser) = fundraiser {+                        for (id, amount) in purchases {+                            fundraiser.tiers[id].remaining -= amount;+                        }+                    }+                });++                Ok(())+            })+        }++        /// Freeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to freeze.+        /// * `fundraiser_id` - ID of the fundraiser to freeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn freeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, true)+        }++        /// Unfreeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to unfreeze.+        /// * `fundraiser_id` - ID of the fundraiser to unfreeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn unfreeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, false)
            Self::set_frozen(origin, offering_asset, fundraiser_id, false)
CJP10

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+// Copyright (c) 2020 Polymath++//! # Sto Module+//!+//! Sto module creates and manages security token offerings+//!+//! ## Overview+//!+//! Primary issuance agent's can create and manage fundraisers of assets.+//! Fundraisers are of fixed supply, with optional expiry and tiered pricing.+//! Fundraisers allow a single payment asset, known as the raising asset.+//! Investors can invest through on-chain balance or off-chain receipts.+//!+//! ## Dispatchable Functions+//!+//! - `create_fundraiser` - Create a new fundraiser.+//! - `invest` - Invest in a fundraiser.+//! - `freeze_fundraiser` - Freeze a fundraiser.+//! - `unfreeze_fundraiser` - Unfreeze a fundraiser.+//! - `modify_fundraiser_window` - Modify the time window a fundraiser is active.+//! - `stop` - stop a fundraiser.++#![cfg_attr(not(feature = "std"), no_std)]+#![recursion_limit = "256"]++use codec::{Decode, Encode};+use frame_support::{+    decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure,+};+use frame_system::ensure_signed;+use pallet_identity as identity;+use pallet_portfolio::{self as portfolio, PortfolioAssetBalances, Trait as PortfolioTrait};+use pallet_settlement::{+    self as settlement, Leg, ReceiptDetails, SettlementType, Trait as SettlementTrait, VenueInfo,+    VenueType,+};+use pallet_timestamp::{self as timestamp, Trait as TimestampTrait};+use polymesh_common_utilities::{+    portfolio::PortfolioSubTrait,+    traits::{asset::Trait as AssetTrait, identity::Trait as IdentityTrait},+    with_transaction, CommonTrait, Context,+};+use polymesh_primitives::{IdentityId, PortfolioId, Ticker};+use sp_runtime::traits::{CheckedAdd, CheckedMul};+use sp_std::{collections::btree_set::BTreeSet, prelude::*};++type Identity<T> = identity::Module<T>;+type Settlement<T> = settlement::Module<T>;+type Timestamp<T> = timestamp::Module<T>;+type Portfolio<T> = portfolio::Module<T>;++/// Details about the Fundraiser+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct Fundraiser<Balance, Moment> {+    /// The primary issuance agent that created the `Fundraiser`+    pub creator: IdentityId,+    /// Portfolio containing the asset being offered+    pub offering_portfolio: PortfolioId,+    /// Asset being offered+    pub offering_asset: Ticker,+    /// Portfolio receiving funds raised+    pub raising_portfolio: PortfolioId,+    /// Asset to receive payment in+    pub raising_asset: Ticker,+    /// Tiers of the fundraiser.+    /// Each tier has a set amount of tokens available at a fixed price.+    /// The sum of the tiers is the total amount available in this fundraiser.+    pub tiers: Vec<FundraiserTier<Balance>>,+    /// Id of the venue to use for this fundraise+    pub venue_id: u64,+    /// Start of the fundraiser+    pub start: Moment,+    /// End of the fundraiser+    pub end: Option<Moment>,+    /// Fundraiser is frozen+    pub frozen: bool,+}++/// Single tier of a tiered pricing model+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct PriceTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+}++/// Single price tier of a `Fundraiser`.+/// Similar to a `PriceTier` but with an extra field `remaining` for tracking the amount available for purchase in a tier.+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct FundraiserTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+    /// Total amount remaining for sale, set to `total` and decremented until `0`.+    pub remaining: Balance,+}++impl<Balance: Clone> Into<FundraiserTier<Balance>> for PriceTier<Balance> {+    fn into(self) -> FundraiserTier<Balance> {+        FundraiserTier {+            total: self.total.clone(),+            price: self.price.clone(),+            remaining: self.total.clone(),+        }+    }+}++pub trait Trait: frame_system::Trait + IdentityTrait + SettlementTrait + PortfolioTrait {+    /// The overarching event type.+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;+}++decl_event!(+    pub enum Event<T>+    where+        Balance = <T as CommonTrait>::Balance,+        Moment = <T as TimestampTrait>::Moment,+    {+        /// A new fundraiser has been created+        /// (primary issuance agent, fundraiser)+        FundraiserCreated(IdentityId, Fundraiser<Balance, Moment>),+        /// An investor invested in the fundraiser+        /// (offering token, raise token, offering_token_amount, raise_token_amount, fundraiser_id)+        FundsRaised(IdentityId, Ticker, Ticker, Balance, Balance, u64),+    }+);++decl_error! {+    /// Errors for the Settlement module.+    pub enum Error for Module<T: Trait> {+        /// Sender does not have required permissions+        Unauthorized,+        /// An arithmetic operation overflowed+        Overflow,+        /// Not enough tokens left for sale+        InsufficientTokensRemaining,+        /// Fundraiser not found+        FundraiserNotFound,+        /// Fundraiser is frozen+        FundraiserFrozen,+        /// Interacting with a fundraiser past the end `Moment`.+        FundraiserExpired,+        /// Interacting with a fundraiser before the start `Moment`.+        FundraiserNotStarted,+        /// Using an invalid venue+        InvalidVenue,+        /// Using an invalid portfolio+        InvalidPortfolio,+        /// An individual price tier was invalid or a set of price tiers was invalid+        InvalidPriceTiers,+        /// Window (start time, end time) has invalid parameters, e.g start time is after end time.+        InvalidOfferingWindow,+        /// Price of an investment exceeded the max price+        MaxPriceExceeded,+    }+}++decl_storage! {+    trait Store for Module<T: Trait> as StoCapped {+        /// All fundraisers that are currently running. (ticker, fundraiser_id) -> Fundraiser+        Fundraisers get(fn fundraisers): double_map hasher(blake2_128_concat) Ticker, hasher(twox_64_concat) u64 => Option<Fundraiser<T::Balance, T::Moment>>;+        /// Total fundraisers created for a token+        FundraiserCount get(fn fundraiser_count): map hasher(twox_64_concat) Ticker => u64;+    }+}++decl_module! {+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+        type Error = Error<T>;++        fn deposit_event() = default;++        /// Create a new fundraiser.+        ///+        /// * `offering_portfolio` - Portfolio containing the `offering_asset`.+        /// * `offering_asset` - Asset being offered.+        /// * `raising_portfolio` - Portfolio containing the `raising_asset`.+        /// * `raising_asset` - Asset being exchanged for `offering_asset` on investment.+        /// * `tiers` - Price tiers to charge investors on investment.+        /// * `venue_id` - Venue to handle settlement.+        /// * `start` - Fundraiser start time, if `None` the fundraiser will start immediately.+        /// * `end` - Fundraiser end time, if `None` the fundraiser will never expire.+        ///+        /// # Weight+        /// `800_000_000` placeholder+        #[weight = 800_000_000]+        pub fn create_fundraiser(+            origin,+            offering_portfolio: PortfolioId,+            offering_asset: Ticker,+            raising_portfolio: PortfolioId,+            raising_asset: Ticker,+            tiers: Vec<PriceTier<T::Balance>>,+            venue_id: u64,+            start: Option<T::Moment>,+            end: Option<T::Moment>,+        ) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);++            let venue = VenueInfo::get(venue_id).ok_or(Error::<T>::InvalidVenue)?;+            ensure!(+                venue.creator == did && venue.venue_type == VenueType::Sto,+                Error::<T>::InvalidVenue+            );++            Self::ensure_custody_and_asset(did, raising_portfolio, raising_asset)?;+            Self::ensure_custody_and_asset(did, offering_portfolio, offering_asset)?;++            ensure!(+                tiers.len() > 0 && tiers.len() <= 10 && tiers.iter().all(|t| t.total > 0.into()),+                Error::<T>::InvalidPriceTiers+            );++            let offering_amount: T::Balance = tiers+                .iter()+                .map(|t| t.total)+                .fold(0.into(), |total, x| total + x);++            let start = start.unwrap_or_else(Timestamp::<T>::get);+            if let Some(end) = end {+                ensure!(start < end, Error::<T>::InvalidOfferingWindow);+            }++            <Portfolio<T>>::lock_tokens(&offering_portfolio, &offering_asset, &offering_amount)?;++            let fundraiser_id = Self::fundraiser_count(offering_asset);+            let fundraiser = Fundraiser {+                    creator: did,+                    offering_portfolio,+                    offering_asset,+                    raising_portfolio,+                    raising_asset,+                    tiers: tiers.into_iter().map(Into::into).collect(),+                    venue_id,+                    start,+                    end,+                    frozen: false,+            };++            FundraiserCount::insert(offering_asset, fundraiser_id + 1);+            <Fundraisers<T>>::insert(+                offering_asset,+                fundraiser_id,+                fundraiser.clone()+            );++            Self::deposit_event(+                RawEvent::FundraiserCreated(did, fundraiser)+            );++            Ok(())+        }++        /// Invest in a fundraiser.+        ///+        /// * `investment_portfolio` - Portfolio that `offering_asset` will be deposited in.+        /// * `funding_portfolio` - Portfolio that will fund the investment.+        /// * `offering_asset` - Asset to invest in.+        /// * `fundraiser_id` - ID of the fundraiser to invest in.+        /// * `investment_amount` - Amount of `offering_asset` to invest in.+        /// * `max_price` - Maximum price to pay per unit of `offering_asset`, If `None`there are no constraints on price.+        /// * `receipt` - Off-chain receipt to use instead of on-chain balance in `funding_portfolio`.+        ///+        /// # Weight+        /// `2_000_000_000` placeholder+        #[weight = 2_000_000_000]+        pub fn invest(+            origin,+            investment_portfolio: PortfolioId,+            funding_portfolio: PortfolioId,+            offering_asset: Ticker,+            fundraiser_id: u64,+            investment_amount: T::Balance,+            max_price: Option<T::Balance>,+            receipt: Option<ReceiptDetails<T::AccountId, T::OffChainSignature>>+        ) -> DispatchResult {+            let sender = ensure_signed(origin.clone())?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;++            <Portfolio<T>>::ensure_portfolio_custody(investment_portfolio, did)?;+            <Portfolio<T>>::ensure_portfolio_custody(funding_portfolio, did)?;++            let now = Timestamp::<T>::get();+            let fundraiser = <Fundraisers<T>>::get(offering_asset, fundraiser_id).ok_or(Error::<T>::FundraiserNotFound)?;+            ensure!(!fundraiser.frozen, Error::<T>::FundraiserFrozen);+            ensure!(+                fundraiser.start <= now && fundraiser.end.filter(|e| now >= *e).is_none(),+                Error::<T>::FundraiserExpired+            );++            // Remaining tokens to fulfil the investment amount+            let mut remaining = investment_amount;+            // Total cost to to fulfil the investment amount.+            // Primary use is to calculate the blended price (offering_token_amount / cost).+            // Blended price must be <= to max_price or the investment will fail.+            let mut cost = T::Balance::from(0);+            // Individual purchases from each tier that accumulate to fulfil the investment amount.+            // Tuple of (tier_id, amount to purchase from that tier).+            let mut purchases = Vec::new();++            for (id, tier) in fundraiser.tiers.iter().enumerate().filter(|(_, tier)| tier.remaining > 0.into()) {+                // fulfilled the investment amount+                if remaining == 0.into() {+                    break+                }++                // Check if this tier can fulfil the remaining investment amount.+                // If it can, purchase the remaining amount.+                // If it can't, purchase what's remaining in the tier.+                let purchase_amount = if tier.remaining >= remaining {+                    remaining+                } else {+                    tier.remaining+                };++                remaining -= purchase_amount;+                purchases.push((id, purchase_amount));+                cost = purchase_amount+                    .checked_mul(&tier.price)+                    .and_then(|pa| cost.checked_add(&pa))+                    .ok_or(Error::<T>::Overflow)?;+            }++            ensure!(remaining == 0.into(), Error::<T>::InsufficientTokensRemaining);+            ensure!(+                max_price.map(|max_price| cost <= max_price * investment_amount).unwrap_or(true),+                Error::<T>::MaxPriceExceeded+            );++            let legs = vec![+                Leg {+                    from: fundraiser.offering_portfolio,+                    to: investment_portfolio,+                    asset: fundraiser.offering_asset,+                    amount: investment_amount+                },+                Leg {+                    from: funding_portfolio,+                    to: fundraiser.raising_portfolio,+                    asset: fundraiser.raising_asset,+                    amount: cost+                }+            ];++            with_transaction(|| {+               let instruction_id = Settlement::<T>::base_add_instruction(+                    fundraiser.creator,+                    fundraiser.venue_id,+                    SettlementType::SettleOnAuthorization,+                    None,+                    legs+                )?;++                let portfolios = vec![investment_portfolio, funding_portfolio];++                match receipt {+                    Some(receipt) => Settlement::<T>::authorize_with_receipts(+                        origin,+                        instruction_id,+                        vec![receipt],+                        portfolios+                    ).map_err(|e| e.error)?,+                    None => Settlement::<T>::authorize_instruction(origin, instruction_id, portfolios).map_err(|e| e.error)?,+                };++                let portfolios= vec![fundraiser.offering_portfolio, fundraiser.raising_portfolio].into_iter().collect::<BTreeSet<_>>();+                Settlement::<T>::unsafe_authorize_instruction(fundraiser.creator, instruction_id, portfolios)?;++                <Fundraisers<T>>::mutate(offering_asset, fundraiser_id, |fundraiser| {+                    if let Some(fundraiser) = fundraiser {+                        for (id, amount) in purchases {+                            fundraiser.tiers[id].remaining -= amount;+                        }+                    }+                });++                Ok(())+            })+        }++        /// Freeze a fundraiser.+        ///+        /// * `offering_asset` - Asset to freeze.+        /// * `fundraiser_id` - ID of the fundraiser to freeze.+        ///+        /// # Weight+        /// `1_000` placeholder+        #[weight = 1_000]+        pub fn freeze_fundraiser(origin, offering_asset: Ticker, fundraiser_id: u64) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            Self::set_frozen(did, offering_asset, fundraiser_id, true)
            Self::set_frozen(origin, offering_asset, fundraiser_id, true)
CJP10

comment created time in 4 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1094/Implement a new PrimaryIssuance pallet which wraps up some of the STO functionality

+// Copyright (c) 2020 Polymath++//! # Sto Module+//!+//! Sto module creates and manages security token offerings+//!+//! ## Overview+//!+//! Primary issuance agent's can create and manage fundraisers of assets.+//! Fundraisers are of fixed supply, with optional expiry and tiered pricing.+//! Fundraisers allow a single payment asset, known as the raising asset.+//! Investors can invest through on-chain balance or off-chain receipts.+//!+//! ## Dispatchable Functions+//!+//! - `create_fundraiser` - Create a new fundraiser.+//! - `invest` - Invest in a fundraiser.+//! - `freeze_fundraiser` - Freeze a fundraiser.+//! - `unfreeze_fundraiser` - Unfreeze a fundraiser.+//! - `modify_fundraiser_window` - Modify the time window a fundraiser is active.+//! - `stop` - stop a fundraiser.++#![cfg_attr(not(feature = "std"), no_std)]+#![recursion_limit = "256"]++use codec::{Decode, Encode};+use frame_support::{+    decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure,+};+use frame_system::ensure_signed;+use pallet_identity as identity;+use pallet_portfolio::{self as portfolio, PortfolioAssetBalances, Trait as PortfolioTrait};+use pallet_settlement::{+    self as settlement, Leg, ReceiptDetails, SettlementType, Trait as SettlementTrait, VenueInfo,+    VenueType,+};+use pallet_timestamp::{self as timestamp, Trait as TimestampTrait};+use polymesh_common_utilities::{+    portfolio::PortfolioSubTrait,+    traits::{asset::Trait as AssetTrait, identity::Trait as IdentityTrait},+    with_transaction, CommonTrait, Context,+};+use polymesh_primitives::{IdentityId, PortfolioId, Ticker};+use sp_runtime::traits::{CheckedAdd, CheckedMul};+use sp_std::{collections::btree_set::BTreeSet, prelude::*};++type Identity<T> = identity::Module<T>;+type Settlement<T> = settlement::Module<T>;+type Timestamp<T> = timestamp::Module<T>;+type Portfolio<T> = portfolio::Module<T>;++/// Details about the Fundraiser+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct Fundraiser<Balance, Moment> {+    /// The primary issuance agent that created the `Fundraiser`+    pub creator: IdentityId,+    /// Portfolio containing the asset being offered+    pub offering_portfolio: PortfolioId,+    /// Asset being offered+    pub offering_asset: Ticker,+    /// Portfolio receiving funds raised+    pub raising_portfolio: PortfolioId,+    /// Asset to receive payment in+    pub raising_asset: Ticker,+    /// Tiers of the fundraiser.+    /// Each tier has a set amount of tokens available at a fixed price.+    /// The sum of the tiers is the total amount available in this fundraiser.+    pub tiers: Vec<FundraiserTier<Balance>>,+    /// Id of the venue to use for this fundraise+    pub venue_id: u64,+    /// Start of the fundraiser+    pub start: Moment,+    /// End of the fundraiser+    pub end: Option<Moment>,+    /// Fundraiser is frozen+    pub frozen: bool,+}++/// Single tier of a tiered pricing model+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct PriceTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+}++/// Single price tier of a `Fundraiser`.+/// Similar to a `PriceTier` but with an extra field `remaining` for tracking the amount available for purchase in a tier.+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]+pub struct FundraiserTier<Balance> {+    /// Total amount available+    pub total: Balance,+    /// Price per unit+    pub price: Balance,+    /// Total amount remaining for sale, set to `total` and decremented until `0`.+    pub remaining: Balance,+}++impl<Balance: Clone> Into<FundraiserTier<Balance>> for PriceTier<Balance> {+    fn into(self) -> FundraiserTier<Balance> {+        FundraiserTier {+            total: self.total.clone(),+            price: self.price.clone(),+            remaining: self.total.clone(),+        }+    }+}++pub trait Trait: frame_system::Trait + IdentityTrait + SettlementTrait + PortfolioTrait {+    /// The overarching event type.+    type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;+}++decl_event!(+    pub enum Event<T>+    where+        Balance = <T as CommonTrait>::Balance,+        Moment = <T as TimestampTrait>::Moment,+    {+        /// A new fundraiser has been created+        /// (primary issuance agent, fundraiser)+        FundraiserCreated(IdentityId, Fundraiser<Balance, Moment>),+        /// An investor invested in the fundraiser+        /// (offering token, raise token, offering_token_amount, raise_token_amount, fundraiser_id)+        FundsRaised(IdentityId, Ticker, Ticker, Balance, Balance, u64),+    }+);++decl_error! {+    /// Errors for the Settlement module.+    pub enum Error for Module<T: Trait> {+        /// Sender does not have required permissions+        Unauthorized,+        /// An arithmetic operation overflowed+        Overflow,+        /// Not enough tokens left for sale+        InsufficientTokensRemaining,+        /// Fundraiser not found+        FundraiserNotFound,+        /// Fundraiser is frozen+        FundraiserFrozen,+        /// Interacting with a fundraiser past the end `Moment`.+        FundraiserExpired,+        /// Interacting with a fundraiser before the start `Moment`.+        FundraiserNotStarted,+        /// Using an invalid venue+        InvalidVenue,+        /// Using an invalid portfolio+        InvalidPortfolio,+        /// An individual price tier was invalid or a set of price tiers was invalid+        InvalidPriceTiers,+        /// Window (start time, end time) has invalid parameters, e.g start time is after end time.+        InvalidOfferingWindow,+        /// Price of an investment exceeded the max price+        MaxPriceExceeded,+    }+}++decl_storage! {+    trait Store for Module<T: Trait> as StoCapped {+        /// All fundraisers that are currently running. (ticker, fundraiser_id) -> Fundraiser+        Fundraisers get(fn fundraisers): double_map hasher(blake2_128_concat) Ticker, hasher(twox_64_concat) u64 => Option<Fundraiser<T::Balance, T::Moment>>;+        /// Total fundraisers created for a token+        FundraiserCount get(fn fundraiser_count): map hasher(twox_64_concat) Ticker => u64;+    }+}++decl_module! {+    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+        type Error = Error<T>;++        fn deposit_event() = default;++        /// Create a new fundraiser.+        ///+        /// * `offering_portfolio` - Portfolio containing the `offering_asset`.+        /// * `offering_asset` - Asset being offered.+        /// * `raising_portfolio` - Portfolio containing the `raising_asset`.+        /// * `raising_asset` - Asset being exchanged for `offering_asset` on investment.+        /// * `tiers` - Price tiers to charge investors on investment.+        /// * `venue_id` - Venue to handle settlement.+        /// * `start` - Fundraiser start time, if `None` the fundraiser will start immediately.+        /// * `end` - Fundraiser end time, if `None` the fundraiser will never expire.+        ///+        /// # Weight+        /// `800_000_000` placeholder+        #[weight = 800_000_000]+        pub fn create_fundraiser(+            origin,+            offering_portfolio: PortfolioId,+            offering_asset: Ticker,+            raising_portfolio: PortfolioId,+            raising_asset: Ticker,+            tiers: Vec<PriceTier<T::Balance>>,+            venue_id: u64,+            start: Option<T::Moment>,+            end: Option<T::Moment>,+        ) -> DispatchResult {+            let sender = ensure_signed(origin)?;+            let did = Context::current_identity_or::<Identity<T>>(&sender)?;+            ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);

Relating to https://github.com/PolymathNetwork/Polymesh/pull/628/files?diff=split&w=1#r500842365, it would be good to add:

            let did = Self::ensure_perms_pia(origin)?;

with:

/// Ensure that `origin` is permissioned and the PIA, returning its DID.
fn ensure_perms_pia(origin: T::Origin) -> Result<IdentityId, DispatchError> {
    let did = Identity::<T>::ensure_perms(origin)?;
    ensure!(T::Asset::primary_issuance_agent(&offering_asset) == did, Error::<T>::Unauthorized);
    Ok(did)
}

(ensure_perms is added in https://github.com/PolymathNetwork/Polymesh/pull/673/commits/c50ecad7956da0efaa9ac353595dcbe2e3570fe8)

CJP10

comment created time in 4 days

PullRequestReviewEvent
PullRequestReviewEvent
PullRequestReviewEvent

push eventPolymathNetwork/Polymesh

Adam Dossa

commit sha 10a9c675a7dde8652ed86bca452035d347126b46

Update enum

view details

Adam Dossa

commit sha 45dec199ccaaf0b0ac7bfb5778f71f8b22a038d6

Merge pull request #675 from PolymathNetwork/fix_jurisidiction_enum Update Jurisdiction enum

view details

Mazdak Farrokhzad

commit sha d0b14f71a9c0465d44ad789a0c19d4e2e363c977

MESH-1220: Refactor & Optimize compliance manager (#663) * refactor verify_compliance_complexity uses * add_compliance_requirement: add missing 'dedup' * compliance manager: simplify + optimize code * make 'fetch_claims' lazier * simplify proposition::run * make 'fetch_claim' & co. lazy * proposition: fix test fallout * compliance manager: lazily fetch default trusted issuers & cache them * fetch_context: use 'Either' instead + leave comment * simplify 'ensure_can_modify_rules' Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Mudit Gupta

commit sha 0792a862e4fa6344c0d6aa85ce6d4fcdfcfaa0a8

[MESH-1260] Added a redeem function (#667) * Added reddem function * minor refactoring * Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Apply suggestions from code review Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> build fixes Co-authored-by: Adam Dossa <adam.dossa@gmail.com> Co-authored-by: poly-auto-merge[bot] <65769705+poly-auto-merge[bot]@users.noreply.github.com>

view details

Mazdak Farrokhzad

commit sha 12fc652ff58314034bc8a3b2ae2009f3e83bc42d

identity: reuse 'ensure_authorization' more

view details

Mazdak Farrokhzad

commit sha 79b31e4adfaaef94ba2fbe6f13223c4cbf907649

assets: refactor with 'consume_auth_by_owner'

view details

Mazdak Farrokhzad

commit sha 896a451abc6ce526dd8a1e848199daaca096b810

simplify 'find_ceiling'

view details

Mazdak Farrokhzad

commit sha 44850a5f78545d4ed7a20260ee20b996550a6f9b

mesh-1097: implement corporate actions config

view details

Mazdak Farrokhzad

commit sha 2456e65a3112aa2ae1082f70a72dc74e811d8a1a

mesh-1097: update schema

view details

Mazdak Farrokhzad

commit sha b077f2135a7cdb4ed3118e7113e3a5949058e56b

mesh-1097: extract weights

view details

Mazdak Farrokhzad

commit sha c50ecad7956da0efaa9ac353595dcbe2e3570fe8

asset + identity: dedup some code

view details

Mazdak Farrokhzad

commit sha dfd07110e422346d4b99ce0e7de3a704a9ee5ade

mesh-1097: polish + add tests

view details

Mazdak Farrokhzad

commit sha 47ea7af62488bd479e849f8da0be47a00641cba9

mesh-1097: nix not-holder checks

view details

Mazdak Farrokhzad

commit sha 879d20d54195f774d03f573f7cc199a0189201bc

mesh-1097: address Mudit's review comments

view details

Mazdak Farrokhzad

commit sha c85a5ba7f2f7db89c46b3ff9c3d824c6d35ccbdb

mesh-1097: address Satyam's comments

view details

Mazdak Farrokhzad

commit sha b0b02327ca134c74913040d157d5c9959ca9f720

mesh-1269: implement 'initiate_corporate_action'

view details

push time in 4 days

push eventPolymathNetwork/Polymesh

Adam Dossa

commit sha 10a9c675a7dde8652ed86bca452035d347126b46

Update enum

view details

Adam Dossa

commit sha 45dec199ccaaf0b0ac7bfb5778f71f8b22a038d6

Merge pull request #675 from PolymathNetwork/fix_jurisidiction_enum Update Jurisdiction enum

view details

Mazdak Farrokhzad

commit sha d0b14f71a9c0465d44ad789a0c19d4e2e363c977

MESH-1220: Refactor & Optimize compliance manager (#663) * refactor verify_compliance_complexity uses * add_compliance_requirement: add missing 'dedup' * compliance manager: simplify + optimize code * make 'fetch_claims' lazier * simplify proposition::run * make 'fetch_claim' & co. lazy * proposition: fix test fallout * compliance manager: lazily fetch default trusted issuers & cache them * fetch_context: use 'Either' instead + leave comment * simplify 'ensure_can_modify_rules' Co-authored-by: Adam Dossa <adam.dossa@gmail.com>

view details

Mudit Gupta

commit sha 0792a862e4fa6344c0d6aa85ce6d4fcdfcfaa0a8

[MESH-1260] Added a redeem function (#667) * Added reddem function * minor refactoring * Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Update pallets/asset/src/lib.rs Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> Apply suggestions from code review Co-authored-by: Mazdak Farrokhzad <twingoow@gmail.com> build fixes Co-authored-by: Adam Dossa <adam.dossa@gmail.com> Co-authored-by: poly-auto-merge[bot] <65769705+poly-auto-merge[bot]@users.noreply.github.com>

view details

Mazdak Farrokhzad

commit sha 12fc652ff58314034bc8a3b2ae2009f3e83bc42d

identity: reuse 'ensure_authorization' more

view details

Mazdak Farrokhzad

commit sha 79b31e4adfaaef94ba2fbe6f13223c4cbf907649

assets: refactor with 'consume_auth_by_owner'

view details

Mazdak Farrokhzad

commit sha 896a451abc6ce526dd8a1e848199daaca096b810

simplify 'find_ceiling'

view details

Mazdak Farrokhzad

commit sha 44850a5f78545d4ed7a20260ee20b996550a6f9b

mesh-1097: implement corporate actions config

view details

Mazdak Farrokhzad

commit sha 2456e65a3112aa2ae1082f70a72dc74e811d8a1a

mesh-1097: update schema

view details

Mazdak Farrokhzad

commit sha b077f2135a7cdb4ed3118e7113e3a5949058e56b

mesh-1097: extract weights

view details

Mazdak Farrokhzad

commit sha c50ecad7956da0efaa9ac353595dcbe2e3570fe8

asset + identity: dedup some code

view details

Mazdak Farrokhzad

commit sha dfd07110e422346d4b99ce0e7de3a704a9ee5ade

mesh-1097: polish + add tests

view details

Mazdak Farrokhzad

commit sha 47ea7af62488bd479e849f8da0be47a00641cba9

mesh-1097: nix not-holder checks

view details

Mazdak Farrokhzad

commit sha 879d20d54195f774d03f573f7cc199a0189201bc

mesh-1097: address Mudit's review comments

view details

Mazdak Farrokhzad

commit sha c85a5ba7f2f7db89c46b3ff9c3d824c6d35ccbdb

mesh-1097: address Satyam's comments

view details

push time in 4 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha c9eab7e87b38223c8d24dd12e4829ab5e9dbbc52

mesh-1097: address Satyam's comments

view details

push time in 4 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 1dd3eeaa54b07cb6d2891a8c1f8f1b553451dbeb

mesh-1269: implement 'initiate_corporate_action'

view details

push time in 4 days

PR opened PolymathNetwork/Polymesh

[WIP] MESH-1269: Initiating a Corporate Action

Based atop https://github.com/PolymathNetwork/Polymesh/pull/673.

TODO: rebase this and the other PR.

+1367 -235

0 comment

23 changed files

pr created time in 4 days

create barnchPolymathNetwork/Polymesh

branch : mesh-1269

created branch time in 4 days

delete branch PolymathNetwork/Polymesh

delete branch : MESH-1260/redeem

delete time in 5 days

delete branch PolymathNetwork/Polymesh

delete branch : opt-compliance-manager

delete time in 5 days

delete branch PolymathNetwork/Polymesh

delete branch : fix_jurisidiction_enum

delete time in 5 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 55ffc0591c1522cde48c312f57356a32acdc1fed

mesh-1097: address Mudit's review comments

view details

push time in 5 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 6e3153c2c1439a8ed004b3f7ab33732e183e74b5

mesh-1097: nix not-holder checks

view details

push time in 6 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 472c8af9f5d3305b8f17606bd672c138c3e349fe

mesh-1097: extract weights

view details

Mazdak Farrokhzad

commit sha 6e35eb2751a7afa87284f743c5b1ea179a6868c1

asset + identity: dedup some code

view details

Mazdak Farrokhzad

commit sha 73c42f050567a896eea5ae043b02394fe005de3d

mesh-1097: polish + add tests

view details

push time in 6 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1314/ [Staking] Permissioned entity

 macro_rules! assert_session_era {     }; } +macro_rules! assert_present_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            true+        );+    };+}++macro_rules! assert_absent_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            false+        );+    };+}++macro_rules! assert_add_permissioned_validator {+    ($acc_id:expr) => {+        assert_ok!(Staking::add_permissioned_validator_entity(+            frame_system::RawOrigin::Root.into(),+            Identity::get_identity($acc_id).unwrap()+        ));+    };+}

Well compile time perf; its less code to typecheck and stuff, but also macros give you better type checking errors, etc.

satyamakgec

comment created time in 6 days

PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

MESH-1314/ [Staking] Permissioned entity

 macro_rules! assert_session_era {     }; } +macro_rules! assert_present_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            true+        );+    };+}++macro_rules! assert_absent_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            false+        );+    };+}++macro_rules! assert_add_permissioned_validator {+    ($acc_id:expr) => {+        assert_ok!(Staking::add_permissioned_validator_entity(+            frame_system::RawOrigin::Root.into(),

No, its just shorter; Feel free to move it though.

satyamakgec

comment created time in 6 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

MESH-1314/ [Staking] Permissioned entity

 macro_rules! assert_session_era {     }; } +macro_rules! assert_present_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            true+        );+    };+}++macro_rules! assert_absent_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            false+        );+    };+}++macro_rules! assert_add_permissioned_validator {+    ($acc_id:expr) => {+        assert_ok!(Staking::add_permissioned_validator_entity(+            frame_system::RawOrigin::Root.into(),
            root(),

(use super::committee_test::root)

satyamakgec

comment created time in 6 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1314/ [Staking] Permissioned entity

 macro_rules! assert_session_era {     }; } +macro_rules! assert_present_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            true+        );+    };+}++macro_rules! assert_absent_entity {+    ($acc_id:expr) => {+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity($acc_id).unwrap()),+            false+        );+    };+}++macro_rules! assert_add_permissioned_validator {+    ($acc_id:expr) => {+        assert_ok!(Staking::add_permissioned_validator_entity(+            frame_system::RawOrigin::Root.into(),+            Identity::get_identity($acc_id).unwrap()+        ));+    };+}

These don't need to be macros; they can be functions using #[track_caller].

satyamakgec

comment created time in 6 days

Pull request review commentPolymathNetwork/Polymesh

MESH-1314/ [Staking] Permissioned entity

 decl_module! {          fn deposit_event() = default; +        fn on_runtime_upgrade() -> Weight {+            use frame_support::storage::migration::{ StorageIterator, put_storage_value };++            if StorageVersion::get() == Releases::V4_0_0 {+                StorageIterator::<()>::new(b"Staking", b"PermissionedValidators")

Let's add a version of migrate_double_map_keys for single maps and use it here.

satyamakgec

comment created time in 6 days

PullRequestReviewEvent
PullRequestReviewEvent
PullRequestReviewEvent

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 6ac52095f186ff4de746e771981c8f7d49f49e9b

identity: reuse 'ensure_authorization' more

view details

Mazdak Farrokhzad

commit sha 56d83a281e80f978a25de5415c17f1f39c34ead7

assets: refactor with 'consume_auth_by_owner'

view details

Mazdak Farrokhzad

commit sha eeeac1e2c124fb83fc4920e9c04d2c6d04af7551

simplify 'find_ceiling'

view details

Mazdak Farrokhzad

commit sha 6085ae159d5a84a5864b5095b05b85fd0316bda6

mesh-1097: implement corporate actions config

view details

Mazdak Farrokhzad

commit sha 14de66c89002d8e71081a272d399544f8663d0a5

mesh-1097: update schema

view details

push time in 6 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 9cb3861820888efa73ed1819c53b31d4660b83b3

mesh-1097: update schema

view details

push time in 7 days

PR opened PolymathNetwork/Polymesh

[WIP] MESH-1097: Configure Corporate Actions for an asset

Also:

  • Simplify find_ceiling + other drive-by refactoring.

Tests are not written yet. Opening to confirm that these are the right semantics.

+498 -166

0 comment

16 changed files

pr created time in 7 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 936149ea88244648379034955784663dee67b1b3

mesh-1097: implement corporate actions config

view details

push time in 7 days

push eventPolymathNetwork/Polymesh

push time in 7 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 5bb45990b0073ba01d128dc2cd177e0fd820dced

identity: reuse 'ensure_authorization' more

view details

Mazdak Farrokhzad

commit sha 4631d4e09f6250a3925615605f1918b9d6f30432

assets: refactor with 'consume_auth_by_owner'

view details

Mazdak Farrokhzad

commit sha deeeec7e56691c0e02751c0e4cff113a3c5e6a85

simplify 'find_ceiling'

view details

Mazdak Farrokhzad

commit sha 1b120a9fda23d2f9076167a0be3a23cdf905a4fb

mesh-1097: add corporate-actions pallet

view details

Mazdak Farrokhzad

commit sha e0eed6217e7bfb60d0c833d7a4b9dedf91f8a344

mesh-1097: implement corporate actions config

view details

Mazdak Farrokhzad

commit sha 3bac6c2041b869de78a7a62dae396069dd6d14e0

wip

view details

push time in 7 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 37f01f88a360f25b318625cafe33898eab674f09

mesh-1097: add corporate-actions pallet

view details

Mazdak Farrokhzad

commit sha 33c3e649814b6970aefbf0bb26299b4bf4c319ec

identity: reuse 'ensure_authorization' more

view details

Mazdak Farrokhzad

commit sha 1bb18ed174b9ec14373eeefc7013be784fe997a0

assets: refactor with 'consume_auth_by_owner'

view details

Mazdak Farrokhzad

commit sha 91692d745d8cf49540499d631777497da0befa7d

simplify 'find_ceiling'

view details

Mazdak Farrokhzad

commit sha 57aa300adfc19bf2b72d1cea652c9ee6a6ca0183

mesh-1097: implement corporate actions config

view details

Mazdak Farrokhzad

commit sha d5a89cfb411a711a72253eb17efffb9b1a7959cb

wip

view details

push time in 7 days

push eventPolymathNetwork/Polymesh

Mazdak Farrokhzad

commit sha 09cbb7c2231edc2dc0256417305925ba63784628

wip

view details

push time in 7 days

create barnchPolymathNetwork/Polymesh

branch : mesh-1097

created branch time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 fn voting_for_pip_overlays_with_staking() {         assert_noop!(alice_proposal(1), Error::InsufficientDeposit);     }); }++#[test]+fn test_with_multiple_validators_from_entity() {+    ExtBuilder::default()+        .validator_count(5)+        .minimum_validator_count(5)+        .build()+        .execute_with(|| {+            start_era(1);++            // add new validator+            bond_validator(50, 51, 500000);++            // Add other stash and cntrl to the same did.

Probably "controller" 😜

satyamakgec

comment created time in 7 days

PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

Benchmarks for identity pallet

 decl_module! {             Ok(())         } -        /// Sets a new primary key for a DID.-        ///-        /// # Failure-        /// Only called by primary key owner.-        #[weight = 800_000_000]-        fn set_primary_key(origin, new_key: T::AccountId) -> DispatchResult {

Ah; so its a drive-by change? Possibly extract to a different PR or add to PR description?

maxsam4

comment created time in 7 days

PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 fn voting_for_pip_overlays_with_staking() {         assert_noop!(alice_proposal(1), Error::InsufficientDeposit);     }); }++#[test]+fn test_with_multiple_validators_from_entity() {+    ExtBuilder::default()+        .validator_count(5)+        .minimum_validator_count(5)+        .build()+        .execute_with(|| {+            start_era(1);++            // add new validator+            bond_validator(50, 51, 500000);++            // Add other stash and cntrl to the same did.

cntrl?

satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 decl_error! {         /// Updates with same value.         NoChange,         /// Updates with same value.-        InvalidCommission+        InvalidCommission,+        /// Given potential entity has invalid identity.

Not sure what this means; can you rephrase?

satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 decl_error! {         /// Updates with same value.         NoChange,         /// Updates with same value.-        InvalidCommission+        InvalidCommission,+        /// Given potential entity has invalid identity.+        InvalidEntityIdentity,+        /// Stash is not a part of any allowed entities.+        StashNotAllowed,+        /// Stash doesn't have a identityId.
        /// Stash doesn't have a DID.
satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 impl<T: Trait> Module<T> {         false     } -    /// Is the stash account one of the permissioned validators?-    pub fn is_validator_or_nominator_compliant(stash: &T::AccountId) -> bool {-        if let Some(validator_identity) = <identity::Module<T>>::get_identity(&stash) {-            return <identity::Module<T>>::has_valid_cdd(validator_identity);-        }-        false+    /// Is nominator's stash account is compliant or not.
    /// Is nominator's `stash` account compliant?
satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 impl<T: Trait> Module<T> {         false     } -    /// Is the stash account one of the permissioned validators?-    pub fn is_validator_or_nominator_compliant(stash: &T::AccountId) -> bool {-        if let Some(validator_identity) = <identity::Module<T>>::get_identity(&stash) {-            return <identity::Module<T>>::has_valid_cdd(validator_identity);-        }-        false+    /// Is nominator's stash account is compliant or not.+    pub fn is_nominator_compliant(stash: &T::AccountId) -> bool {+        <Identity<T>>::get_identity(&stash).map_or(false, |id| <Identity<T>>::has_valid_cdd(id))+    }++    /// Is validator's stash account is compliant or not.
    /// Is validator's `stash` account compliant?
satyamakgec

comment created time in 7 days

PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 fn should_add_permissioned_validators() {         let acc_10 = 10;         let acc_20 = 20; -        assert_ok!(Staking::add_permissioned_validator(+        provide_did_to_user(10);+        provide_did_to_user(20);++        assert_ok!(Staking::add_permissioned_validator_entity(             frame_system::RawOrigin::Root.into(),-            acc_10.clone()+            Identity::get_identity(&acc_10).unwrap()         ));-        assert_ok!(Staking::add_permissioned_validator(+        assert_ok!(Staking::add_permissioned_validator_entity(             frame_system::RawOrigin::Root.into(),-            acc_20.clone()+            Identity::get_identity(&acc_20).unwrap()         ));-        assert_eq!(Staking::permissioned_validators(acc_10), true);-        assert_eq!(Staking::permissioned_validators(acc_20), true);+        assert_eq!(+            Staking::permissioned_entities(Identity::get_identity(&acc_10).unwrap()),+            true+        );

Let's make a function out of this, taking the AccountId + the true/false RHS of assert_eq and refactor below.

satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 fn staking_should_work() {                 1500,                 RewardDestination::Controller             ));-            assert_ok!(Staking::add_permissioned_validator(+            assert_ok!(Staking::add_permissioned_validator_entity(                 frame_system::RawOrigin::Root.into(),-                3+                Identity::get_identity(&3).unwrap()             ));

Could refactor logic from here with a function as well.

satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 decl_storage! {         /// forcing into account.         pub IsCurrentSessionFinal get(fn is_current_session_final): bool = false; -        /// The map from (wannabe) validators to the status of compliance.-        pub PermissionedValidators get(fn permissioned_validators):-            map hasher(twox_64_concat) T::AccountId => bool;+        /// Allowed entities who shows interests to run operators/validator node.
        /// Allowed entities who shows interests in running operators/validator node.
satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 decl_module! {             ValidatorCount::mutate(|n| *n += factor * *n);         } -        /// Governance committee on 2/3 rds majority can introduce a new potential validator-        /// to the pool of validators. Staking module uses `PermissionedValidators` to ensure-        /// validators have completed KYB compliance and considers them for validation.+        /// Governance committee on 2/3 rds majority can introduce a new potential entity+        /// to the pool of complianced entities who can run validators. Staking module uses `PermissionedEntities`+        /// to ensure validators have completed KYB compliance and considers them for validation.         ///         /// # Arguments         /// * origin Required origin for adding a potential validator.-        /// * validator Stash AccountId of the validator.+        /// * entity IdentityId of an entity who shows interests to run validators.         #[weight = 750_000_000]-        pub fn add_permissioned_validator(origin, validator: T::AccountId) {+        pub fn add_permissioned_validator_entity(origin, entity: IdentityId) {             T::RequiredAddOrigin::ensure_origin(origin)?;-            ensure!(!Self::permissioned_validators(&validator), Error::<T>::AlreadyExists);-            // Change validator status to be Permissioned-            <PermissionedValidators<T>>::insert(&validator, true);-            let validator_id = <identity::Module<T>>::get_identity(&validator);-            Self::deposit_event(RawEvent::PermissionedValidatorAdded(validator_id, validator));+            ensure!(!Self::permissioned_entities(&entity), Error::<T>::AlreadyExists);+            // Validate the cdd status of the entity.+            ensure!(<Identity<T>>::has_valid_cdd(entity), Error::<T>::InvalidEntityIdentity);+            // Change entity status to be Permissioned+            <PermissionedEntities>::insert(&entity, true);+            Self::deposit_event(RawEvent::PermissionedEntityAdded(GC_DID, entity));         } -        /// Remove a validator from the pool of validators. Effects are known in the next session.-        /// Staking module checks `PermissionedValidators` to ensure validators have+        /// Remove an entity from the pool of (wannable) entity validators. Effects are known in the next session.+        /// Staking module checks `PermissionedEntities` to ensure validators have         /// completed KYB compliance         ///         /// # Arguments         /// * origin Required origin for removing a potential validator.-        /// * validator Stash AccountId of the validator.+        /// * entity IdentityId of an entity who shows interests to run validators.         #[weight = 750_000_000]-        pub fn remove_permissioned_validator(origin, validator: T::AccountId) {-            T::RequiredRemoveOrigin::ensure_origin(origin.clone())?;-            let caller = ensure_signed(origin)?;-            let caller_id = Context::current_identity_or::<T::Identity>(&caller).ok();-            ensure!(Self::permissioned_validators(&validator), Error::<T>::NotExists);-            // Change validator status to be Non-Permissioned-            <PermissionedValidators<T>>::insert(&validator, false);+        pub fn remove_permissioned_validator_entity(origin, entity: IdentityId) {+            T::RequiredRemoveOrigin::ensure_origin(origin)?;+            ensure!(Self::permissioned_entities(&entity), Error::<T>::NotExists);+            // Change entity status to be Non-Permissioned+            <PermissionedEntities>::insert(&entity, false);
            PermissionedEntities::insert(&entity, false);
satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 decl_module! {             ValidatorCount::mutate(|n| *n += factor * *n);         } -        /// Governance committee on 2/3 rds majority can introduce a new potential validator-        /// to the pool of validators. Staking module uses `PermissionedValidators` to ensure-        /// validators have completed KYB compliance and considers them for validation.+        /// Governance committee on 2/3 rds majority can introduce a new potential entity+        /// to the pool of complianced entities who can run validators. Staking module uses `PermissionedEntities`+        /// to ensure validators have completed KYB compliance and considers them for validation.         ///         /// # Arguments         /// * origin Required origin for adding a potential validator.-        /// * validator Stash AccountId of the validator.+        /// * entity IdentityId of an entity who shows interests to run validators.         #[weight = 750_000_000]-        pub fn add_permissioned_validator(origin, validator: T::AccountId) {+        pub fn add_permissioned_validator_entity(origin, entity: IdentityId) {             T::RequiredAddOrigin::ensure_origin(origin)?;-            ensure!(!Self::permissioned_validators(&validator), Error::<T>::AlreadyExists);-            // Change validator status to be Permissioned-            <PermissionedValidators<T>>::insert(&validator, true);-            let validator_id = <identity::Module<T>>::get_identity(&validator);-            Self::deposit_event(RawEvent::PermissionedValidatorAdded(validator_id, validator));+            ensure!(!Self::permissioned_entities(&entity), Error::<T>::AlreadyExists);+            // Validate the cdd status of the entity.+            ensure!(<Identity<T>>::has_valid_cdd(entity), Error::<T>::InvalidEntityIdentity);+            // Change entity status to be Permissioned+            <PermissionedEntities>::insert(&entity, true);
            PermissionedEntities::insert(&entity, true);
satyamakgec

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

[WIP]: MESH-1314/ [Staking] Permissioned entity

 impl<T: Trait> Module<T> {         false     } -    /// Is the stash account one of the permissioned validators?-    pub fn is_validator_or_nominator_compliant(stash: &T::AccountId) -> bool {-        if let Some(validator_identity) = <identity::Module<T>>::get_identity(&stash) {-            return <identity::Module<T>>::has_valid_cdd(validator_identity);-        }-        false+    /// Is nominator's stash account is compliant or not.+    pub fn is_nominator_compliant(stash: &T::AccountId) -> bool {+        <Identity<T>>::get_identity(&stash).map_or(false, |id| <Identity<T>>::has_valid_cdd(id))
        <Identity<T>>::get_identity(&stash).map_or(false, <Identity<T>>::has_valid_cdd)
satyamakgec

comment created time in 7 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

Benchmarks for identity pallet

 impl Ticker {     pub fn iter(&self) -> sp_std::slice::Iter<'_, u8> {         self.0.iter()     }++    /// Returns a ticker that may not be in upper case+    #[inline]+    pub fn unsafe_ticker(raw: [u8; TICKER_LEN]) -> Ticker {+        Ticker(raw)
    pub fn unsafe_ticker(raw: [u8; TICKER_LEN]) -> Self {
        Self(raw)
maxsam4

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

Benchmarks for identity pallet

 decl_module! {             Ok(())         } -        /// Sets a new primary key for a DID.-        ///-        /// # Failure-        /// Only called by primary key owner.-        #[weight = 800_000_000]-        fn set_primary_key(origin, new_key: T::AccountId) -> DispatchResult {

What's up with removing this?

maxsam4

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

Benchmarks for identity pallet

 fn make_account<T: Trait>(     (account, origin, did) } +fn make_cdd_account<T: Trait>(u: u32) -> (T::AccountId, RawOrigin<T::AccountId>, IdentityId) {+    let (cdd_account, cdd_origin, cdd_did) = make_account::<T>("cdd", u);+    T::CddServiceProviders::add_member(cdd_did).unwrap();+    (cdd_account, cdd_origin, cdd_did)+}++fn setup_investor_uniqueness_claim<T: Trait>(+    name: &'static str,+) -> (+    T::AccountId,+    RawOrigin<T::AccountId>,+    IdentityId,+    Claim,+    InvestorZKProofData,+) {+    let (account, origin) = make_account_without_did::<T>(name, SEED);++    let did = IdentityId::from([+        152u8, 25, 31, 70, 229, 131, 2, 22, 68, 84, 54, 151, 136, 3, 105, 122, 94, 58, 182, 27, 30,+        137, 81, 212, 254, 154, 230, 123, 171, 97, 74, 95,+    ]);+    Module::<T>::link_did(account.clone(), did);++    let cdd_id = CddId::from([+        102u8, 210, 32, 212, 213, 80, 255, 99, 142, 30, 202, 20, 220, 131, 109, 106, 137, 12, 137,+        191, 123, 156, 212, 20, 215, 87, 23, 42, 84, 181, 128, 73,+    ]);+    let cdd_claim = Claim::CustomerDueDiligence(cdd_id);+    Module::<T>::base_add_claim(did, cdd_claim, did, Some(666.into()));++    let scope = Scope::Custom([228u8, 152, 116, 104, 5, 8, 30, 188, 143, 185, 10, 208].to_vec());+    let scope_did = IdentityId::from([+        2u8, 72, 20, 154, 7, 96, 116, 105, 155, 74, 227, 252, 172, 18, 200, 203, 137, 107, 200,+        210, 194, 71, 250, 41, 108, 172, 100, 107, 223, 114, 182, 101,+    ]);+    let conf_scope_claim = Claim::InvestorUniqueness(scope, scope_did, cdd_id);++    let inv_proof = InvestorZKProofData(+        Signature::from_bytes(&[+            216u8, 224, 57, 254, 200, 45, 150, 202, 12, 108, 226, 233, 148, 213, 237, 7, 35, 150,+            142, 18, 127, 146, 162, 19, 161, 164, 95, 67, 181, 100, 156, 25, 201, 210, 209, 165,+            182, 74, 184, 145, 230, 255, 215, 144, 223, 100, 100, 147, 226, 58, 142, 92, 103, 153,+            153, 204, 123, 120, 133, 113, 218, 51, 208, 132,+        ])+        .unwrap(),+    );++    (account, origin, did, conf_scope_claim, inv_proof)+}+ benchmarks! {-    _ {-        // User account seed.-        let u in 1 .. MAX_USER_INDEX => ();-    }--    // register_did {-    //     let u in ...;-    //     // Number of secondary items.-    //     let i in 0 .. 50;-    //     let origin = make_account_without_did::<T>(NAME, u).1;-    //     let uid = uid_from_name_and_idx(NAME, u);-    //     let secondary_keys: Vec<SecondaryKey<T::AccountId>> = iter::repeat(Default::default())-    //         .take(i as usize)-    //         .collect();-    // }: _(origin, uid, secondary_keys)--    // add_claim {-    //     let u in ...;-    //     let (_, origin, origin_did) = make_account::<T>("caller", u);-    //     let uid = uid_from_name_and_idx("caller", u);-    //     let ticker = Ticker::try_from(vec![b'T'; 12].as_slice()).unwrap();-    //     let st_scope = Scope::Identity(IdentityId::try_from(ticker.as_slice()).unwrap());-    //     let scope_claim = InvestorZKProofData::make_scope_claim(&ticker, &uid);-    //     let scope_id = compute_scope_id(&scope_claim).compress().to_bytes().into();-    //     let inv_proof = InvestorZKProofData::new(&origin_did, &uid, &ticker);-    //     let cdd_claim = InvestorZKProofData::make_cdd_claim(&origin_did, &uid);-    //     let cdd_id = compute_cdd_id(&cdd_claim).compress().to_bytes().into();-    //     let conf_scope_claim = Claim::InvestorZKProof(st_scope, scope_id, cdd_id, inv_proof);-    // }: _(origin, origin_did, conf_scope_claim, Some(666.into()))+    _ {}++    register_did {+        // Number of secondary items.+        let i in 0 .. 50;+        make_cdd_account::<T>(SEED);+        let (_, origin) = make_account_without_did::<T>("caller", SEED);+        let uid = uid_from_name_and_idx("caller", SEED);++        let mut secondary_keys: Vec<secondary_key::api::SecondaryKey<T::AccountId>> = Vec::with_capacity(i as usize);+        for x in 0..i {+            secondary_keys.push(secondary_key::api::SecondaryKey {+                signer: Signatory::Account(account("key", x, SEED)),+                ..Default::default()+            });+        }+    }: _(origin, uid, secondary_keys)++    cdd_register_did {+        // Number of secondary items.+        let i in 0 .. 50;++        let (_, origin, origin_did) = make_cdd_account::<T>(SEED);++        let target: T::AccountId = account("target", SEED, SEED);++        let mut secondary_keys: Vec<secondary_key::api::SecondaryKey<T::AccountId>> = Vec::with_capacity(i as usize);+        for x in 0..i {+            secondary_keys.push(secondary_key::api::SecondaryKey {+                signer: Signatory::Account(account("key", x, SEED)),+                ..Default::default()+            });+        }

Dedup with above?

maxsam4

comment created time in 7 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentPolymathNetwork/Polymesh

Mesh 1323: use of scheduler pallet in bridge

 decl_event! { }  decl_module! {-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+    pub struct Module<T: Trait> for enum Call where origin: <T as frame_system::Trait>::Origin {         type Error = Error<T>; -        const MaxTimelockedTxsPerBlock: u32 = T::MaxTimelockedTxsPerBlock::get();-         fn deposit_event() = default; -        /// Issues tokens in timelocked transactions.-        fn on_initialize(block_number: T::BlockNumber) -> Weight {-            Self::handle_timelocked_txs(block_number)+        fn on_runtime_upgrade() -> Weight {+            use frame_support::{migration::StorageKeyIterator, Twox64Concat};++            let now = frame_system::Module::<T>::block_number();++            // Migrate timelocked transactions.+            StorageKeyIterator::<T::BlockNumber, Vec::<BridgeTx<T::AccountId, T::Balance>>, Twox64Concat>::new(b"Bridge", b"TimelockedTxs")+                .drain()+                .filter_map(|(block_number, txs)| {

This seems to be infallible, so .map(...) is sufficient?

vkomenda

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

Mesh 1323: use of scheduler pallet in bridge

 decl_event! { }  decl_module! {-    pub struct Module<T: Trait> for enum Call where origin: T::Origin {+    pub struct Module<T: Trait> for enum Call where origin: <T as frame_system::Trait>::Origin {         type Error = Error<T>; -        const MaxTimelockedTxsPerBlock: u32 = T::MaxTimelockedTxsPerBlock::get();-         fn deposit_event() = default; -        /// Issues tokens in timelocked transactions.-        fn on_initialize(block_number: T::BlockNumber) -> Weight {-            Self::handle_timelocked_txs(block_number)+        fn on_runtime_upgrade() -> Weight {+            use frame_support::{migration::StorageKeyIterator, Twox64Concat};++            let now = frame_system::Module::<T>::block_number();++            // Migrate timelocked transactions.+            StorageKeyIterator::<T::BlockNumber, Vec::<BridgeTx<T::AccountId, T::Balance>>, Twox64Concat>::new(b"Bridge", b"TimelockedTxs")+                .drain()+                .filter_map(|(block_number, txs)| {+                    // Schedule only for future blocks.+                    let block_number = T::BlockNumber::max(block_number, now + One::one());+                    let calls: Vec<_> = txs+                        .into_iter()+                        .map(|tx| {+                            Call::<T>::handle_scheduled_bridge_tx(tx).into()+                        })+                        .collect();

Although this is a one-time-code thing, consider a .flat_map(...) instead for efficiency (don't allocate into a Vec<_> that is consumed immediately).

vkomenda

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

Mesh 1323: use of scheduler pallet in bridge

 impl<T: Trait> Module<T> {         }          let current_block_number = <system::Module<T>>::block_number();-        let mut unlock_block_number =+        let unlock_block_number =             current_block_number + timelock + T::BlockNumber::from(2u32.pow(already_tried.into()));-        let max_timelocked_txs_per_block = T::MaxTimelockedTxsPerBlock::get() as usize;-        let old_unlock_count_number = unlock_block_number;-        while Self::timelocked_txs(unlock_block_number).len() >= max_timelocked_txs_per_block {-            unlock_block_number += One::one();-        }-        let calculated_weight = weight_for::handle_bridge_tx_later::<T>(-            (unlock_block_number - old_unlock_count_number).saturated_into::<u64>(),-        );         tx_details.execution_block = unlock_block_number;         <BridgeTxDetails<T>>::insert(&bridge_tx.recipient, &bridge_tx.nonce, tx_details);-        <TimelockedTxs<T>>::mutate(&unlock_block_number, |txs| {-            txs.push(bridge_tx.clone());-        });-        let current_did = Context::current_identity::<Identity<T>>().unwrap_or_else(|| GC_DID);-        Self::deposit_event(RawEvent::BridgeTxScheduled(-            current_did,-            bridge_tx,-            unlock_block_number,-        ));--        Ok(calculated_weight)-    } -    /// Handles the timelocked transactions that are set to unlock at the given block number.-    fn handle_timelocked_txs(block_number: T::BlockNumber) -> Weight {-        let mut weight = 0;-        let txs = <TimelockedTxs<T>>::take(block_number);-        for tx in txs {-            weight += match Self::handle_bridge_tx_now(tx, false) {-                Ok(weight) => weight,-                Err(e) => {-                    sp_runtime::print(e);-                    Zero::zero()-                }-            };+        // Schedule the transaction as a dispatchable call.+        let call = Call::<T>::handle_scheduled_bridge_tx(bridge_tx.clone()).into();+        // FIXME: handle errors.+        if let Err(e) = T::Scheduler::schedule(+            DispatchTime::At(unlock_block_number),+            None,+            LOWEST_PRIORITY,+            RawOrigin::Root.into(),+            call,+        ) {

Call seems substantially similar to the one above; perhaps refactor?

vkomenda

comment created time in 7 days

Pull request review commentPolymathNetwork/Polymesh

Mesh 1323: use of scheduler pallet in bridge

 dependencies = [  [[package]] name = "aes"-version = "0.5.0"+version = "0.4.0"

Hmm; why is everything being downgraded in the lockfile?

vkomenda

comment created time in 7 days

PullRequestReviewEvent

delete branch PolymathNetwork/Polymesh

delete branch : MESH-1306-fix-put-code

delete time in 10 days

PullRequestReviewEvent
more