diff --git a/blockchain/modules/asset-registry/src/weights.rs b/blockchain/modules/asset-registry/src/weights.rs
index db889bdb..0147cfe3 100644
--- a/blockchain/modules/asset-registry/src/weights.rs
+++ b/blockchain/modules/asset-registry/src/weights.rs
@@ -25,7 +25,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// --chain=dev
// --steps=50
diff --git a/blockchain/modules/currencies/src/weights.rs b/blockchain/modules/currencies/src/weights.rs
index eb3eab69..64f3c6d6 100644
--- a/blockchain/modules/currencies/src/weights.rs
+++ b/blockchain/modules/currencies/src/weights.rs
@@ -26,7 +26,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// pallet
// --chain=dev
diff --git a/blockchain/modules/ecdp-ussd-treasury/src/weights.rs b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs
index b1884981..d3843115 100644
--- a/blockchain/modules/ecdp-ussd-treasury/src/weights.rs
+++ b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs
@@ -25,7 +25,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// --chain=dev
// --steps=50
@@ -35,8 +35,8 @@
// --execution=wasm
// --wasm-execution=compiled
// --heap-pages=4096
-// --output=./modules/cdp-treasury/src/weights.rs
-// --template=./templates/module-weight-template.hbs
+// --output=./blockchain/modules/ecdp-ussd-treasury/src/weights.rs
+// --template=.maintain/module-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
diff --git a/blockchain/modules/edfis-launchpad/Cargo.toml b/blockchain/modules/edfis-launchpad/Cargo.toml
index 6af98d3f..001b4d09 100644
--- a/blockchain/modules/edfis-launchpad/Cargo.toml
+++ b/blockchain/modules/edfis-launchpad/Cargo.toml
@@ -6,8 +6,6 @@ authors = ["Setheum Labs"]
edition = "2021"
[dependencies]
-scale-info = { workspace = true }
-serde = { workspace = true, optional = true }
parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] }
sp-runtime = { workspace = true }
sp-io = { workspace = true }
@@ -27,8 +25,6 @@ orml-tokens = { workspace = true }
[features]
default = ["std"]
std = [
- "scale-info/std",
- "serde",
"parity-scale-codec/std",
"sp-runtime/std",
"sp-std/std",
diff --git a/blockchain/modules/edfis-launchpad/README.md b/blockchain/modules/edfis-launchpad/README.md
index bf697908..68380c00 100644
--- a/blockchain/modules/edfis-launchpad/README.md
+++ b/blockchain/modules/edfis-launchpad/README.md
@@ -1,5 +1,41 @@
# Edfis Launchpad Module
+Edfis Launchpad is a platform for projects to offer crowdsales (IDO) of their tokens and raise funds on Edfis while listing their liquidity pool on the exchange.
## Overview
-Provides a launchpad crowdsales platform on Edfis.
+This module is used to raise funds on launchpad crowdsales. Teams and projects that are just getting started launching their products would need to raise funds and even sell their tokens to the public. They need community backed by token holders of their token, that is the crowd so that they could have a strong start. By creating a crowdfunding campaign that ends with their project Tokens getting sold to the public, they can raise funds and sell their tokens to the public.
+
+There are four participants in a LaunchPad Crowdsales Protocol, the Campaign Creator, the Campaign Beneficiary, the Crowd/Contributors, and the Governance Council.
+
+* The Campaign Creator is the person who creates the campaign and the project.
+* The Campaign Beneficiary is the person who receives the funds raised.
+* The Crowd/Contributors are the people who contribute to the campaign.
+* The Governance Council is the people who manage the campaign and the protocol.
+
+## How the protocol Works
+
+![Screenshot from 2022-01-23 13-31-41](https://user-images.githubusercontent.com/15086345/150666483-3f9a07b3-2e76-46f9-97f9-729679c03f1c.png)
+The HighEnd LaunchPad Protocol lets teams/projects/campaigns achieve two (2) major goals at once, it raises money, and and sell their tokens to the public.
+The protocol uses `MultiCurrency` to let the Campaign Creator choose which currency to raise/sell their tokens for. Therefore, Campaign Creator can choose to raise funds in any currency available on the chain.
+
+There is a `goal` that is set by the Campaign Creator, the beneficiary of the fund and the Period (campaign period - amount of blocks a campaign should stay active) of the campaign and other information that describes the campaign.
+
+### The Lifecycle of a Campaign
+
+A Launchpad Campaign has three stages in its lifecycle, they are as follows:
+
+1. **Pre-Funding/Proposal Stage**: The Campaign Creator creates the campaign and sets the Period/TimeCap and HardCap. In this stage, the Campaign Creator must submit the proposal to the Governance Council along with a `SubmissionDeposit` required by the protocol.
+
+2. **Waiting Stage**: The Campaign waits for the appropriate time to start the Campaign. The Protocol has a `WaitingPeriod` that is set on runtime, and all campaigns have to wait for that period to start.
+
+3. **Funding/Active Stage**: The Campaign can raise funds and sell their tokens to the public in this stage. If the `goal` is reached before the `period` to end the campaign, the campaign will be ended and the funds will be available for the public to claim and the raised funds for the Campaign Beneficiary or Creator to claim.
+
+### Campaign Ctegories
+
+#### A Successful Launchpad Campaign
+
+A successful Launchpad Campaign is one that has raised the `goal` and has sold their tokens to the public. Once the `goal` is reached, the Campaign is considered successful.
+
+#### A Failed Launchpad Campaign
+
+A failed Launchpad Campaign is one that has not raised the `goal` and has not sold their tokens to the public. Once the `goal` is not reached and the `period` to end the campaign has ended, the Campaign is considered failed and the campaign allocation of tokens is available for the Campaign Creator to claim refund and the raised funds are also available for the Crowd/Contributors/buyers to claim refunds all only before the `RetirementPeriod` of the campaign.
diff --git a/blockchain/modules/edfis-launchpad/src/lib.rs b/blockchain/modules/edfis-launchpad/src/lib.rs
new file mode 100644
index 00000000..430976f8
--- /dev/null
+++ b/blockchain/modules/edfis-launchpad/src/lib.rs
@@ -0,0 +1,915 @@
+// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم
+
+// This file is part of Setheum.
+
+// Copyright (C) 2019-Present Setheum Labs.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// 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, either version 3 of the License, or
+// (at your option) any later version.
+
+// 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 .
+
+//! # Launchpad Crowdsales Pallet
+//!
+//! ## Overview
+//!
+//! Edfis Launchpad is a platform for projects to offer crowdsales (IDO) of their tokens
+//! and raise funds on Edfis while listing their liquidity pool on the exchange.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+// Disable the following two lints since they originate from an external macro (namely decl_storage)
+#![allow(clippy::string_lit_as_bytes)]
+#![allow(clippy::unused_unit)]
+
+use frame_support::{
+ pallet_prelude::*, transactional, PalletId, traits::Get, ensure
+};
+use frame_system::{pallet_prelude::*, ensure_signed};
+
+use orml_traits::{GetByKey, MultiCurrency, MultiLockableCurrency, LockIdentifier};
+use primitives::{Balance, CampaignId, CampaignInfo, CurrencyId};
+use support::{CampaignManager, Proposal};
+
+use sp_std::{
+ vec::Vec,
+};
+use sp_runtime::{traits::{AccountIdConversion, Zero}, DispatchResult};
+
+mod mock;
+mod tests;
+pub mod weights;
+
+pub use module::*;
+pub use weights::WeightInfo;
+
+pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency<::AccountId>>::Balance;
+pub(crate) type CurrencyIdOf =
+ <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId;
+pub(crate) type CampaignInfoOf =
+ CampaignInfo<::AccountId, BalanceOf, ::BlockNumber>;
+
+pub const LAUNCHPAD_LOCK_ID: LockIdentifier = *b"set/lpad";
+
+#[frame_support::pallet]
+pub mod module {
+ use super::*;
+
+ #[pallet::config]
+ pub trait Config: frame_system::Config {
+ type Event: From> + IsType<::Event>;
+
+ /// The Currency for managing assets related to the SERP (Setheum Elastic Reserve Protocol).
+ type MultiCurrency: MultiLockableCurrency;
+
+ #[pallet::constant]
+ /// Native currency_id.
+ ///
+ type GetNativeCurrencyId: Get>;
+
+ /// The Campaign Commission rate taken from successful campaigns
+ /// The Treasury Commission is transferred to the Network's Treasury account.
+ /// The first item of the tuple is the numerator of the commission rate, second
+ /// item is the denominator, fee_rate = numerator / denominator,
+ /// use (u32, u32) over another type to minimize internal division operation.
+ #[pallet::constant]
+ type GetCommission: Get<(u32, u32)>;
+
+ /// The amount to be held on deposit by the owner of a crowdfund
+ /// - in HighEnd LaunchPad (HELP) currency id. (LaunchPad Token)
+ type SubmissionDeposit: Get>;
+
+ /// The minimum amount that must be raised in a crowdsales campaign.
+ /// Campaign Goal must be at least this amount.
+ /// If this amount is not met, the proposal can be updated by the proposer or will be rejected.
+ type MinRaise: GetByKey, BalanceOf>;
+
+ /// The minimum amount that may be contributed into a crowdfund - by currency_id.
+ /// Should almost certainly be at least ExistentialDeposit.
+ type MinContribution: GetByKey, BalanceOf>;
+
+ /// The maximum number of proposals that could be running at any given time.
+ /// If set to 0, proposals are disabled and the Module will panic if a proposal is made.
+ type MaxProposalsCount: Get;
+
+ /// The maximum number of campaigns that could be running at any given time.
+ /// If set to 0, campaigns are disabled and the Module will panic if a campaign is made.
+ type MaxCampaignsCount: Get;
+
+ /// The maximum period of time (in blocks) that a crowdfund campaign clould be active.
+ /// If set to 0, active period is disabled and the Module will panic if a campaign is activated.
+ type MaxActivePeriod: Get;
+
+ /// The period of time (number of blocks) a campaign is delayed after being Approved by governance.
+ type CampaignStartDelay: Get;
+
+ /// The period of time (in blocks) after an unsuccessful crowdfund ending during which
+ /// contributors are able to withdraw their funds. After this period, their funds are lost.
+ type CampaignRetirementPeriod: Get;
+
+ /// The period of time (in blocks) after a rejected crowdfund proposal during which
+ /// proposal creators's locked deposits are unlocked and the proposal is set to `is_rejected`.
+ /// After this period, their proposal is lost.
+ type ProposalRetirementPeriod: Get;
+
+ /// The origin which may update, approve or reject campaign proposals.
+ type UpdateOrigin: EnsureOrigin;
+
+ #[pallet::constant]
+ /// The Airdrop module pallet id, keeps airdrop funds.
+ type PalletId: Get;
+
+ /// Weight information for the extrinsics in this module.
+ type WeightInfo: WeightInfo;
+ }
+
+ #[pallet::error]
+ pub enum Error {
+ /// The campaign funds raised already claimed by campaign creator or beneficiary
+ CampaignAlreadyClaimed,
+ /// The crowdfund's contribution period has ended; no more contributions will be accepted.
+ CampaignEnded,
+ /// Campaign has failed
+ CampaignFailed,
+ /// Campaign is not approved
+ CampaignNotApproved,
+ /// Campaign is not active
+ CampaignNotActive,
+ /// Campaign is not in the list of campaigns.
+ CampaignNotFound,
+ /// Campaign has not started
+ CampaignNotStarted,
+ /// Campaign is still active
+ CampaignStillActive,
+ /// Contributors balance is not enough to contribute
+ ContributionCurrencyNotEnough,
+ /// Contribution failed to transfer
+ ContributionFailedTransfer,
+ /// Contribution is not in the list of contributions.
+ ContributionNotFound,
+ /// Must contribute at least the minimum amount of funds.
+ ContributionTooSmall,
+ /// Contribution has duplicate account
+ DuplicateContribution,
+ /// Must contribute at least the minimum amount of funds.
+ GoalBelowMinimumRaise,
+ /// The Submission Deposit Funds are insufficient
+ InsufficientBalance,
+ /// Wrong Currency Type in use.
+ InvalidCurrencyType,
+ /// The fund index specified does not exist.
+ InvalidIndex,
+ /// The campaign is in waiting period
+ InWaitingPeriod,
+ /// Maximum number of simultaneous campaigns has been reached;
+ /// no more campaigns can be approved until one is closed.
+ MaxCampaignsExceeded,
+ /// Crowdsale period has exceeded the maximum active period.
+ MaxActivePeriodExceeded,
+ /// Maximum number of simultaneous proposals has been exceeded;
+ /// no more proposals can be made until one is approved or rejected.
+ MaxProposalsExceeded,
+ /// You cannot withdraw funds because you have not contributed any.
+ NoContribution,
+ /// Proposal is already approved.
+ ProposalAlreadyApproved,
+ /// Proposal is not in the list of proposals.
+ ProposalNotFound,
+ /// The origin is not correct
+ WrongOrigin,
+ /// Crowdfund period is too short.
+ ZeroPeriod,
+ }
+
+ #[pallet::event]
+ #[pallet::generate_deposit(pub(crate) fn deposit_event)]
+ #[pallet::metadata(T::AccountId = "AccountId", BalanceOf = "Balance", CurrencyIdOf = "CurrencyId")]
+ pub enum Event {
+ /// Created Proposal \[currency_id\]
+ CreatedProposal(CurrencyIdOf, CampaignInfoOf),
+ /// Contributed to a campaign \[contributor, currency_id, amount\]
+ Contributed(T::AccountId, CurrencyIdOf, BalanceOf),
+ /// Claim contribution allocation \[contributor, currency_id, amount\]
+ ClaimedContributionAlloc(T::AccountId, CurrencyIdOf, BalanceOf),
+ /// Claimed Funds Raised \[claimant_account_id, currency_id, amount_claimed\]
+ ClaimedFundraise(T::AccountId, CurrencyIdOf, BalanceOf),
+ /// Rejected Proposal \[currency_id\]
+ RejectedProposal(CurrencyIdOf),
+ /// Approved Proposal \[currency_id\]
+ ApprovedProposal(CurrencyIdOf),
+ /// Activated Campaign \[currency_id\]
+ ActivatedCampaign(CurrencyIdOf),
+ /// Campaign Started \[currency_id\]
+ StartedCampaign(CurrencyIdOf),
+ /// Ended Campaign Successfully \[currency_id, campaign_info\]
+ EndedCampaignSuccessful(CurrencyIdOf),
+ /// Ended Campaign Unsuccessfully \[currency_id, campaign_info\]
+ EndedCampaignUnsuccessful(CurrencyIdOf),
+ /// Contributed to Campaign \[currency_id, contribution_amount\]
+ ContributedToCampaign(CurrencyIdOf, BalanceOf),
+ /// Claimed Contribution Allocation \[claimant_account_id, currency_id, allocation_claimed\]
+ ClaimedAllocation(T::AccountId, CurrencyIdOf, BalanceOf),
+ /// Dissolved Unclaimed Funds \[amount, currency_id, now\]
+ DissolvedFunds(BalanceOf, CurrencyIdOf, ::BlockNumber),
+ /// Dispensed Commissions \[amount, currency_id, now\]
+ DispensedCommissions(BalanceOf, CurrencyIdOf, ::BlockNumber),
+ }
+
+ /// Info on all of the proposed campaigns.
+ ///
+ /// map CurrencyId => CampaignInfo
+ #[pallet::storage]
+ #[pallet::getter(fn proposals)]
+ pub type Proposals = StorageMap<_, Blake2_128Concat, CurrencyIdOf, CampaignInfoOf, OptionQuery>;
+
+ /// Info on all of the approved campaigns.
+ ///
+ /// map CurrencyId => CampaignInfo
+ #[pallet::storage]
+ #[pallet::getter(fn campaigns)]
+ pub type Campaigns = StorageMap<_, Blake2_128Concat, CurrencyIdOf, CampaignInfoOf, OptionQuery>;
+
+ // Track the next campaign id to be used.
+ #[pallet::storage]
+ #[pallet::getter(fn campaign_index)]
+ pub type CampaignsIndex = StorageValue<_, CampaignId, ValueQuery>;
+
+ // Track the number of simultaneous Active Campaigns - ActiveCampaignsCount
+
+ #[pallet::storage]
+ #[pallet::getter(fn active_campaigns_count)]
+ pub type ActiveCampaignsCount = StorageValue<_, u32, ValueQuery>;
+
+ // Track the number of successful campaigns the protocol has achieved.
+ #[pallet::storage]
+ #[pallet::getter(fn successful_campaign_index)]
+ pub type SuccessfulCampaignsCount = StorageValue<_, u32, ValueQuery>;
+
+
+ /// Record of the total amount of funds raised in the protocol
+ /// under a specific currency_id. currency_id => total_raised
+ ///
+ /// TotalAmountRaised: map CurrencyIdOf => BalanceOf
+ #[pallet::storage]
+ #[pallet::getter(fn total_amount_raised)]
+ pub type TotalAmountRaised = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>;
+
+ #[pallet::pallet]
+ pub struct Pallet(PhantomData);
+
+ #[pallet::hooks]
+ impl Hooks for Pallet {
+ // Call at the start of the block to eventuiate on_proposals and on_campaigns
+ // on_initialize is called at the start of the block.
+ fn on_initialize(now: T::BlockNumber) -> Weight {
+
+ // Calls to eventuate proposals and campaigns.
+
+ let mut count: Weight = 0;
+ count += 1;
+
+ // Eventuate proposals and campaigns
+
+ // If there are proposals, check if to remove rejected and retired proposals.
+ // Iterate over the proposals
+ for (id, campaign_info) in Proposals::::iter() {
+ // If the proposal is rejected, check if to remove it
+ if campaign_info.is_rejected && now >= campaign_info.proposal_retirement_period {
+ // Remove the proposal
+ Self::remove_proposal(id).unwrap();
+ count += 1;
+ }
+ break;
+ }
+ // If there are campaigns, check if to start or end them
+ // Iterate over the campaigns
+ for (id, campaign_info) in Campaigns::::iter() {
+ // If the campaign is waiting, check if to start it
+ if campaign_info.is_waiting && campaign_info.campaign_start <= now {
+ // Activate Campaign
+ Self::activate_campaign(id).unwrap();
+ count += 1;
+ }
+ // If the campaign is active, check if to end it
+ if campaign_info.is_active && !campaign_info.is_ended {
+ // If campaign is successfull, call on successful campaign
+ if campaign_info.raised >= campaign_info.goal {
+ Self::on_successful_campaign(now, id).unwrap();
+ count += 1;
+ } else if campaign_info.campaign_end <= now && campaign_info.raised < campaign_info.goal {
+ // If campaign is failed, call on failed campaign
+ Self::on_failed_campaign(now, id).unwrap();
+ count += 1;
+ }
+ }
+ // If the campaign reaches retirement period, call on retirement
+ if campaign_info.is_ended && &campaign_info.campaign_retirement_period <= &now {
+ Self::on_retire(id).unwrap();
+ count += 1;
+ }
+ break;
+ }
+ T::WeightInfo::on_initialize(count as u32)
+ }
+ }
+
+ #[pallet::call]
+ impl Pallet {
+ /// Make a new proposal
+ #[pallet::weight((T::WeightInfo::make_proposal(), DispatchClass::Operational))]
+ #[transactional]
+ pub fn make_proposal(
+ origin: OriginFor,
+ beneficiary: T::AccountId,
+ raise_currency: CurrencyIdOf,
+ sale_token: CurrencyIdOf,
+ token_price: BalanceOf,
+ crowd_allocation: BalanceOf,
+ goal: BalanceOf,
+ period: T::BlockNumber,
+ ) -> DispatchResult {
+ let who = ensure_signed(origin)?;
+
+ // Ensure that the period is not zero
+ ensure!(period > T::BlockNumber::zero(), Error::::ZeroPeriod);
+ // Ensure that the period is not too long
+ ensure!(period <= T::MaxActivePeriod::get(), Error::::MaxActivePeriodExceeded);
+ // Ensure that the goal is not less than the Minimum Raise
+ ensure!(goal > T::MinRaise::get(&raise_currency), Error::::GoalBelowMinimumRaise);
+
+ // Create proposal and add id.
+ Self::new_proposal(
+ who.clone(),
+ beneficiary,
+ raise_currency,
+ sale_token,
+ token_price,
+ crowd_allocation,
+ goal,
+ period,
+ )?;
+ Ok(())
+ }
+
+ // Make a contribution to an active campaign
+ #[pallet::weight((T::WeightInfo::contribute(), DispatchClass::Operational))]
+ #[transactional]
+ pub fn contribute(
+ origin: OriginFor,
+ id: CurrencyIdOf,
+ contribution_amount: BalanceOf,
+ ) -> DispatchResult {
+ let who = ensure_signed(origin)?;
+
+ Self::on_contribution(
+ who.clone(),
+ id,
+ contribution_amount
+ )?;
+ Self::deposit_event(Event::Contributed(who, id, contribution_amount));
+ Ok(())
+ }
+
+ // Claim a contribution allocation
+ #[pallet::weight((T::WeightInfo::claim_contribution_allocation(), DispatchClass::Operational))]
+ #[transactional]
+ pub fn claim_contribution_allocation(
+ origin: OriginFor,
+ id: CurrencyIdOf,
+ ) -> DispatchResult {
+ let who = ensure_signed(origin)?;
+
+ Self::on_claim_allocation(
+ who.clone(),
+ id,
+ )?;
+ Ok(())
+ }
+
+ // Claim a campaign's raised funds
+ #[pallet::weight((T::WeightInfo::claim_campaign_fundraise(), DispatchClass::Operational))]
+ #[transactional]
+ pub fn claim_campaign_fundraise(
+ origin: OriginFor,
+ id: CurrencyIdOf,
+ ) -> DispatchResult {
+ let who = ensure_signed(origin)?;
+
+ Self::on_claim_campaign(
+ who.clone(),
+ id,
+ )?;
+
+ let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+ Self::deposit_event(Event::ClaimedFundraise(who, id, campaign.raised));
+ Ok(())
+ }
+
+ // Approve a proposal - origin must be `UpdateOrigin`
+ #[pallet::weight((T::WeightInfo::approve_proposal(), DispatchClass::Operational))]
+ #[transactional]
+ pub fn approve_proposal(
+ origin: OriginFor,
+ id: CurrencyIdOf,
+ ) -> DispatchResult {
+ T::UpdateOrigin::ensure_origin(origin)?;
+
+ Self::on_approve_proposal(
+ id,
+ )?;
+
+ Self::deposit_event(Event::ApprovedProposal(id));
+ Ok(())
+ }
+
+ // Reject a proposal - origin must be `UpdateOrigin`
+ #[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))]
+ #[transactional]
+ pub fn reject_proposal(
+ origin: OriginFor,
+ id: CurrencyIdOf,
+ ) -> DispatchResult {
+ T::UpdateOrigin::ensure_origin(origin)?;
+
+ Self::on_reject_proposal(
+ id,
+ )?;
+
+ Self::deposit_event(Event::RejectedProposal(id));
+ Ok(())
+ }
+
+ // Activate a Waiting Campaign - origin must be `UpdateOrigin`
+ #[pallet::weight((T::WeightInfo::activate_waiting_campaign(), DispatchClass::Operational))]
+ #[transactional]
+ pub fn activate_waiting_campaign(
+ origin: OriginFor,
+ id: CurrencyIdOf,
+ ) -> DispatchResult {
+ T::UpdateOrigin::ensure_origin(origin)?;
+
+ Self::activate_campaign(
+ id,
+ )?;
+
+ Self::deposit_event(Event::ActivatedCampaign(id));
+ Ok(())
+ }
+ }
+}
+
+
+impl Pallet {
+ /// Get the Launchpad's Treasury Account.
+ pub fn launchpad_treasury() -> T::AccountId {
+ T::PalletId::get().into_account()
+ }
+
+ /// The account ID of the fund pot.
+ ///
+ pub fn campaign_pool(id: CampaignId) -> T::AccountId {
+ T::PalletId::get().into_sub_account(id)
+ }
+}
+
+impl Proposal for Pallet {
+ type CurrencyId = CurrencyId;
+
+ /// The Campaign Proposal info of `id`
+ fn proposal_info(id: Self::CurrencyId) -> Option> {
+ Self::proposals(id)
+ }
+
+ /// Get all proposals
+ fn all_proposals() -> Vec> {
+ let proposals = Proposals::::iter().into_iter();
+ let mut proposals_vec: Vec> = Vec::new();
+ for (_, proposal) in proposals {
+ proposals_vec.push(proposal);
+ }
+ proposals_vec
+ }
+
+ /// Create new Campaign Proposal with specific `CampaignInfo`, return the `id` of the Campaign
+ fn new_proposal(
+ origin: T::AccountId,
+ beneficiary: T::AccountId,
+ raise_currency: Self::CurrencyId,
+ sale_token: Self::CurrencyId,
+ token_price: BalanceOf,
+ crowd_allocation: BalanceOf,
+ goal: BalanceOf,
+ period: T::BlockNumber,
+ ) -> DispatchResult {
+ // Generate pool_id - overflow not managed
+ let pool_id = >::get() + 1;
+ >::put(pool_id);
+
+ // Generate the CampaignInfo structure
+ let proposal = CampaignInfo {
+ id: sale_token,
+ origin: origin.clone(),
+ beneficiary: beneficiary,
+ pool: Self::campaign_pool(pool_id),
+ raise_currency: raise_currency,
+ sale_token: sale_token,
+ token_price: token_price,
+ crowd_allocation: crowd_allocation,
+ goal: goal,
+ raised: Zero::zero(),
+ contributors_count: Zero::zero(),
+ contributions: Vec::new(),
+ period: period,
+ campaign_start: Zero::zero(),
+ campaign_end: Zero::zero(),
+ campaign_retirement_period: Zero::zero(),
+ proposal_retirement_period: Zero::zero(),
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: false,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+
+ // try checks
+ let try_set_lock = T::MultiCurrency::set_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &origin, T::SubmissionDeposit::get()).is_ok();
+ let try_make_transfer = T::MultiCurrency::transfer(sale_token, &origin, &Self::campaign_pool(pool_id), crowd_allocation).is_ok() ;
+
+ if T::MultiCurrency::free_balance(T::GetNativeCurrencyId::get(), &origin) >= T::SubmissionDeposit::get() &&
+ T::MultiCurrency::free_balance(sale_token, &origin) >= crowd_allocation {
+ if try_set_lock && try_make_transfer {
+ // set lock
+ T::MultiCurrency::set_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &origin, T::SubmissionDeposit::get()).unwrap();
+ // make transfer
+ T::MultiCurrency::transfer(sale_token, &origin, &Self::campaign_pool(pool_id), crowd_allocation).unwrap();
+ // insert proposal
+ >::insert(sale_token, proposal.clone());
+ }
+ } else {
+ return Err(Error::::InsufficientBalance.into());
+ }
+
+ Self::deposit_event(Event::CreatedProposal(sale_token, proposal.clone()));
+ Ok(())
+ }
+
+ /// Approve Proposal by `id` at `now`.
+ fn on_approve_proposal(id: Self::CurrencyId)-> sp_std::result::Result<(), DispatchError> {
+ // Tag the proposal and ensure it is not already approved.
+ let mut proposal = Self::proposals(id).ok_or(Error::::ProposalNotFound)?;
+ ensure!(!proposal.is_approved, Error::::ProposalAlreadyApproved);
+
+ // Approve the proposal in CampaignInfo and set it to waiting
+ proposal.is_approved = true;
+ proposal.is_waiting = true;
+
+ // Set campaign start time
+ proposal.campaign_start = >::block_number() + T::CampaignStartDelay::get();
+
+ // Set campaign end time
+ proposal.campaign_end = >::block_number() + T::CampaignStartDelay::get() + proposal.period;
+
+ // Remove from proposals and add to campaigns
+ >::remove(id);
+ >::insert(id, proposal);
+ // Active Campaigns count - overflow not managed
+ >::put(>::get() + 1);
+ Ok(())
+ }
+
+ /// Reject Proposal by `id` and remove from storage.
+ fn on_reject_proposal(id: Self::CurrencyId)-> sp_std::result::Result<(), DispatchError> {
+ // Check that the Proposal exists and tag it
+ let mut proposal = Self::proposals(id).ok_or(Error::::ProposalNotFound)?;
+ // Ensure that the proposal is not already approved
+ ensure!(!proposal.is_approved, Error::::ProposalAlreadyApproved);
+
+ // Set the proposal to rejected
+ proposal.is_rejected = true;
+ proposal.proposal_retirement_period = >::block_number() + T::ProposalRetirementPeriod::get();
+ // Update proposal storage
+ >::insert(id, proposal);
+ Ok(())
+ }
+
+ /// Remove proposal from storage by `id`
+ fn remove_proposal(id: Self::CurrencyId)-> sp_std::result::Result<(), DispatchError> {
+ // Check that the Proposal exists and tag it
+ let proposal = Self::proposals(id).ok_or(Error::::ProposalNotFound)?;
+ // Ensure that the proposal is not already approved
+ ensure!(!proposal.is_approved, Error::::ProposalAlreadyApproved);
+ ensure!(proposal.is_rejected, Error::::ProposalAlreadyApproved);
+
+ let try_remove_lock = T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &proposal.origin).is_ok();
+ let try_refund_transfer = T::MultiCurrency::transfer( proposal.sale_token, &proposal.pool, &proposal.origin, proposal.crowd_allocation).is_ok();
+ // Unlock balances and remove the Proposal from the storage.
+ if try_remove_lock && try_refund_transfer {
+ // remove lock and refund proposal
+ T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &proposal.origin).unwrap();
+ T::MultiCurrency::transfer( proposal.sale_token, &proposal.pool, &proposal.origin, proposal.crowd_allocation).unwrap();
+ // Remove from proposals
+ >::remove(id);
+ };
+ Ok(())
+ }
+}
+
+impl CampaignManager for Pallet {
+ type CurrencyId = CurrencyId;
+
+ /// The Campaign info of `id`
+ fn campaign_info(id: Self::CurrencyId) -> Option> {
+ Self::campaigns(id)
+ }
+
+ /// Get all campaigns
+ fn all_campaigns() -> Vec> {
+ let campaigns = Campaigns::::iter().into_iter();
+ let mut campaigns_vec: Vec> = Vec::new();
+ for (_, proposal) in campaigns {
+ campaigns_vec.push(proposal);
+ }
+ campaigns_vec
+ }
+
+ /// Called when a contribution is received.
+ fn on_contribution(
+ who: T::AccountId,
+ id: Self::CurrencyId,
+ amount: BalanceOf,
+ ) -> DispatchResult {
+ let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+
+ // Ensure campaign is valid
+ ensure!(!campaign.is_failed, Error::::CampaignFailed);
+ ensure!(!campaign.is_ended, Error::::CampaignEnded);
+ ensure!(campaign.is_approved, Error::::CampaignNotApproved);
+ ensure!(campaign.is_active, Error::::CampaignNotActive);
+
+ // Make assurances - minimum contribution & free balance
+ ensure!(amount >= T::MinContribution::get(&campaign.raise_currency), Error::::ContributionTooSmall);
+ ensure!(T::MultiCurrency::free_balance(campaign.raise_currency, &who) >= amount, Error::::ContributionCurrencyNotEnough);
+
+ // Initiate the Contribution
+ let transfer_contribution = T::MultiCurrency::transfer(campaign.raise_currency, &who, &campaign.pool, amount).is_ok();
+ if transfer_contribution {
+ // Transfer contribution and tag allocation
+ T::MultiCurrency::transfer(campaign.raise_currency, &who, &campaign.pool, amount).unwrap();
+ let allocated = amount / campaign.token_price;
+
+ // Check if contributor already exists in contributions list
+ let mut found = false;
+ // if campaign.contributions exists, check for who's contribution
+
+ for (contributor, contribution, allocation, _) in campaign.contributions.iter_mut() {
+ if contributor == &who {
+
+ found = true;
+ *contribution += amount;
+ *allocation += allocated;
+ campaign.raised += amount;
+ }
+ break;
+ }
+ if !found {
+ campaign.contributions.push((who, amount, allocated, false));
+ campaign.raised += amount;
+ }
+
+ // Tag contributors count
+ campaign.contributors_count = campaign.contributions.len() as u32;
+
+ // Put campaign in campaigns storage
+ >::insert(id, campaign);
+ };
+ Ok(())
+ }
+
+ /// Called when a contribution allocation is claimed
+ fn on_claim_allocation(
+ who: T::AccountId,
+ id: Self::CurrencyId,
+ ) -> DispatchResult {
+ let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+ let campaign_p = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+
+ // Check if the contributor exists in the contributions of the campaign, if not return error
+ ensure!(campaign.contributions.iter().any(|(contributor, _, _, _)| *contributor == who), Error::::ContributionNotFound);
+
+ // Ensure campaign is successfully ended
+ Self::ensure_successfully_ended_campaign(id)?;
+
+ // Check if the contributor exists and transfer allocated from pool to contributor
+ for (contributor, _, allocation, claimed) in campaign.contributions.iter_mut() {
+ let transfer_allocation = T::MultiCurrency::transfer(campaign.sale_token, &campaign.pool, &who, *allocation).is_ok();
+
+ if contributor == &who && *claimed == false && transfer_allocation {
+ // set claimed to true - allocation claimed
+ *claimed = true;
+ // complete claim by adding campaign update to storage
+ >::insert(id, campaign);
+
+ for (contributor_p, _, allocation_p, claimed_p) in campaign_p.contributions.iter() {
+ if contributor_p == &who && *claimed_p == false {
+ // transfer allocation
+ T::MultiCurrency::transfer(campaign_p.sale_token, &campaign_p.pool, &who, *allocation_p).unwrap();
+ Self::deposit_event(Event::ClaimedContributionAlloc(who, id, *allocation_p));
+ }
+
+ break;
+ }
+ }
+
+ break;
+ }
+ Ok(())
+ }
+
+ /// Called when a campaign's raised fund is claimed
+ fn on_claim_campaign(
+ who: T::AccountId,
+ id: Self::CurrencyId,
+ ) -> DispatchResult {
+ let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+
+ // Ensure origin is who created campaign proposal or beneficiary and its not claimed
+ ensure!(campaign.origin == who || campaign.beneficiary == who, Error::::WrongOrigin);
+ ensure!(!campaign.is_claimed, Error::::CampaignAlreadyClaimed);
+
+
+ if campaign.is_ended {
+ // Claim the campaign raised funds and transfer to the beneficiary
+ let transfer_claim = T::MultiCurrency::transfer(
+ campaign.raise_currency,
+ &campaign.pool,
+ &campaign.beneficiary,
+ campaign.raised
+ )
+ .is_ok();
+
+ if campaign.is_successful && transfer_claim {
+ T::MultiCurrency::transfer(
+ campaign.raise_currency,
+ &campaign.pool,
+ &campaign.beneficiary,
+ campaign.raised
+ ).unwrap();
+ // Campaign is claimed, update storage
+ campaign.is_claimed = true;
+ >::insert(id, campaign);
+ }
+ }
+ Ok(())
+ }
+
+ /// Called when a failed campaign is claimed by the proposer
+ fn on_claim_failed_campaign(
+ who: T::AccountId,
+ id: Self::CurrencyId,
+ ) -> DispatchResult {
+ let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+
+ // Ensure origin is who created campaign proposal
+ ensure!(campaign.origin == who || campaign.beneficiary == who, Error::::WrongOrigin);
+
+ // Ensure campaign is valid and failed
+ ensure!(campaign.is_failed, Error::::CampaignFailed);
+ ensure!(campaign.is_ended, Error::::CampaignEnded);
+
+ // Get the total amount of sale_token in the pool
+ let total_sale_token = T::MultiCurrency::total_balance(campaign.sale_token, &campaign.pool);
+
+ let remove_lock = T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &campaign.origin).is_ok();
+ let transfer_claim = T::MultiCurrency::transfer( campaign.sale_token, &campaign.pool, &who, total_sale_token).is_ok();
+ // Unlock balances and remove the Proposal from the storage.
+ if remove_lock && transfer_claim {
+ T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &campaign.origin).unwrap();
+ T::MultiCurrency::transfer( campaign.sale_token, &campaign.pool, &who, total_sale_token).unwrap();
+ // Update campaign in campaigns storage
+ >::insert(id, campaign);
+ };
+ Ok(())
+ }
+
+ /// Activate a campaign by `id`
+ fn activate_campaign(id: Self::CurrencyId) -> DispatchResult {
+ // Ensure campaign exists
+ let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+
+ // Set campaign to active
+ campaign.is_waiting = false;
+ campaign.is_active = true;
+ // Update campaign storage
+ >::insert(id, campaign);
+ Ok(())
+ }
+
+ /// Ensure campaign is Valid and Successfully Ended
+ fn ensure_successfully_ended_campaign(id: Self::CurrencyId) -> DispatchResult {
+ let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+ ensure!(!campaign.is_failed, Error::::CampaignFailed);
+ ensure!(campaign.is_successful, Error::::CampaignFailed);
+ ensure!(campaign.is_ended, Error::::CampaignStillActive);
+ ensure!(campaign.is_approved, Error::::CampaignNotApproved);
+
+ // ensure!(campaign.campaign_start <= >::block_number(), Error::::CampaignNotStarted);
+ Ok(())
+ }
+
+ /// Record Successful Campaign by `id`
+ fn on_successful_campaign(now: T::BlockNumber, id: Self::CurrencyId) -> DispatchResult {
+ let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+
+ // Set to successful and ended
+ campaign.is_successful = true;
+ campaign.is_ended = true;
+
+ // Set retirement period
+ campaign.campaign_retirement_period = now + T::CampaignRetirementPeriod::get();
+
+ // Tag contributors count
+ campaign.contributors_count = campaign.contributions.len() as u32;
+
+
+ // Success count - overflow not managed
+ // Add to total successful campaigns
+ let success_count = >::get() + 1;
+ >::put(success_count);
+
+ // Add to `TotalAmountRaised` in protocol
+ >::mutate(campaign.raise_currency, |total| *total += campaign.raised);
+
+ // Update campaign storage
+ >::insert(id, campaign);
+ Ok(())
+ }
+
+ /// Record Failed Campaign by `id`
+ fn on_failed_campaign(now: T::BlockNumber, id: Self::CurrencyId) -> DispatchResult {
+ let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+
+ // Set to failed and ended
+ campaign.is_failed = true;
+ campaign.is_ended = true;
+
+ // Tag contributors count
+ campaign.contributors_count = campaign.contributions.len() as u32;
+
+ // Set retirement period
+ campaign.campaign_retirement_period = now + T::CampaignRetirementPeriod::get();
+
+ // Update campaign storage
+ >::insert(id, campaign);
+ Ok(())
+ }
+
+ /// Called when pool is retired
+ /// Only unsuccessful pools are retired
+ fn on_retire(id: Self::CurrencyId) -> DispatchResult {
+ // Get campaign in tag
+ let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?;
+ // Get accounts in tag
+ let treasury = Self::launchpad_treasury();
+
+ // Get the total amount of raise_currency in the pool
+ let total_raise_currency = T::MultiCurrency::total_balance(campaign.raise_currency, &campaign.pool);
+ // Get the total amount of sale_token in the pool
+ let total_sale_token = T::MultiCurrency::total_balance(campaign.sale_token, &campaign.pool);
+
+ let transfer_allocation = T::MultiCurrency::transfer(campaign.raise_currency, &campaign.pool, &treasury, total_raise_currency).is_ok();
+ let transfer_raise = T::MultiCurrency::transfer(campaign.sale_token, &campaign.pool, &treasury, total_sale_token).is_ok();
+ // Dissolve unclaimed Fundraise
+ if transfer_allocation && transfer_raise {
+ T::MultiCurrency::transfer(campaign.raise_currency, &campaign.pool, &treasury, total_raise_currency).unwrap();
+ T::MultiCurrency::transfer(campaign.sale_token, &campaign.pool, &treasury, total_sale_token).unwrap();
+ // Remove campaign from campaigns storage
+ >::remove(id);
+ }
+ Ok(())
+ }
+
+ /// Get amount of contributors/contributions in a campaign
+ fn get_contributors_count(id: Self::CurrencyId) -> u32 {
+ let campaign = Self::campaigns(id).unwrap();
+ campaign.contributions.len() as u32
+ }
+
+ /// Get the total_amounts_raised for all currencies from `TotalAmountRaised`
+ fn get_total_amounts_raised() -> Vec<(Self::CurrencyId, BalanceOf)> {
+ let total_amounts_raised: Vec<(Self::CurrencyId, BalanceOf)> = >::iter()
+ .into_iter()
+ .collect::)>>();
+ total_amounts_raised
+ }
+}
diff --git a/blockchain/modules/edfis-launchpad/src/mock.rs b/blockchain/modules/edfis-launchpad/src/mock.rs
new file mode 100644
index 00000000..ddbe1879
--- /dev/null
+++ b/blockchain/modules/edfis-launchpad/src/mock.rs
@@ -0,0 +1,223 @@
+// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم
+
+// This file is part of Setheum.
+
+// Copyright (C) 2019-Present Setheum Labs.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// 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, either version 3 of the License, or
+// (at your option) any later version.
+
+// 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 .
+
+//! Mocks for the Edfis Launchpad module.
+
+#![cfg(test)]
+
+use super::*;
+use frame_support::{construct_runtime, ord_parameter_types, parameter_types, PalletId};
+use frame_system::EnsureSignedBy;
+use orml_traits::parameter_type_with_key;
+use sp_core::H256;
+use sp_runtime::{
+ testing::Header,
+ traits::IdentityLookup,
+};
+use primitives::{Amount, Balance, TokenSymbol};
+
+pub type AccountId = u128;
+pub type BlockNumber = u64;
+// The network Treasury account.
+pub const TREASURY: AccountId = 0;
+// Mock accounts.
+pub const ALICE: AccountId = 1;
+pub const BOB: AccountId = 2;
+pub const CHARLIE: AccountId = 3;
+
+pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE);
+pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD);
+pub const TEST: CurrencyId = CurrencyId::Token(TokenSymbol::SETR);
+pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF);
+
+mod launchpad_crowdsales {
+ pub use super::super::*;
+}
+
+parameter_types! {
+ pub const BlockHashCount: u64 = 250;
+}
+
+impl frame_system::Config for Runtime {
+ type Origin = Origin;
+ type Index = u64;
+ type BlockNumber = BlockNumber;
+ type Call = Call;
+ type Hash = H256;
+ type Hashing = ::sp_runtime::traits::BlakeTwo256;
+ type AccountId = AccountId;
+ type Lookup = IdentityLookup;
+ type Header = Header;
+ type Event = Event;
+ type BlockHashCount = BlockHashCount;
+ type BlockWeights = ();
+ type BlockLength = ();
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = ();
+ type OnNewAccount = ();
+ type OnKilledAccount = ();
+ type DbWeight = ();
+ type BaseCallFilter = ();
+ type SystemWeightInfo = ();
+ type SS58Prefix = ();
+ type OnSetCode = ();
+}
+
+parameter_type_with_key! {
+ pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance {
+ Default::default()
+ };
+}
+
+impl orml_tokens::Config for Runtime {
+ type Event = Event;
+ type Balance = Balance;
+ type Amount = Amount;
+ type CurrencyId = CurrencyId;
+ type WeightInfo = ();
+ type ExistentialDeposits = ExistentialDeposits;
+ type OnDust = ();
+ type MaxLocks = ();
+ type DustRemovalWhitelist = ();
+}
+
+parameter_type_with_key! {
+ pub MinRaise: |currency_id: CurrencyId| -> Balance {
+ match currency_id {
+ &USSD => 100,
+ &SEE => 100,
+ &EDF => 100,
+ _ => 0,
+ }
+ };
+}
+
+parameter_type_with_key! {
+ pub MinContribution: |currency_id: CurrencyId| -> Balance {
+ match currency_id {
+ &USSD => 100,
+ &SEE => 100,
+ &EDF => 100,
+ _ => 0,
+ }
+ };
+}
+
+parameter_types! {
+ pub const GetNativeCurrencyId: CurrencyId = SEE; // Setheum native currency ticker is SEE/
+ pub const GetCommission: (u32, u32) = (10, 100); // 10%
+ pub const SubmissionDeposit: Balance = 101;
+ pub const MaxProposalsCount: u32 = 3;
+ pub const MaxCampaignsCount: u32 = 3;
+ pub const MaxActivePeriod: BlockNumber = 20;
+ pub const CampaignStartDelay: BlockNumber = 20;
+ pub const RetirementPeriod: BlockNumber = 20;
+ pub const CrowdsalesPalletId: PalletId = PalletId(*b"set/help");
+}
+
+ord_parameter_types! {
+ pub const TreasuryAccount: AccountId = TREASURY;
+ pub const Eleven: AccountId = 11;
+}
+impl Config for Runtime {
+ type Event = Event;
+ type MultiCurrency = Tokens;
+ type GetNativeCurrencyId = GetNativeCurrencyId;
+ type GetCommission = GetCommission;
+ type SubmissionDeposit = SubmissionDeposit;
+ type MinRaise = MinRaise;
+ type MinContribution = MinContribution;
+ type MaxProposalsCount = MaxProposalsCount;
+ type MaxCampaignsCount = MaxCampaignsCount;
+ type MaxActivePeriod = MaxActivePeriod;
+ type CampaignStartDelay = CampaignStartDelay;
+ type CampaignRetirementPeriod = RetirementPeriod;
+ type ProposalRetirementPeriod = RetirementPeriod;
+ type UpdateOrigin = EnsureSignedBy;
+ type PalletId = CrowdsalesPalletId;
+ type WeightInfo = ();
+}
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic;
+type Block = frame_system::mocking::MockBlock;
+
+construct_runtime!(
+ pub enum Runtime where
+ Block = Block,
+ NodeBlock = Block,
+ UncheckedExtrinsic = UncheckedExtrinsic
+ {
+ System: frame_system::{Pallet, Call, Storage, Config, Event},
+ LaunchPad: launchpad_crowdsales::{Pallet, Storage, Call, Event},
+ Tokens: orml_tokens::{Pallet, Storage, Call, Event},
+ }
+);
+
+pub struct ExtBuilder {
+ balances: Vec<(AccountId, CurrencyId, Balance)>,
+}
+
+impl Default for ExtBuilder {
+ fn default() -> Self {
+ Self { balances: vec![] }
+ }
+}
+
+impl ExtBuilder {
+ pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self {
+ self.balances = balances;
+ self
+ }
+
+ pub fn one_hundred_thousand_for_all(self) -> Self {
+ self.balances(vec![
+ (ALICE, SEE, 100_000),
+ (ALICE, USSD, 100_000),
+ (ALICE, EDF, 100_000),
+ (ALICE, TEST, 100_000),
+ (BOB, SEE, 100_000),
+ (BOB, USSD, 100_000),
+ (BOB, EDF, 100_000),
+ (BOB, TEST, 100_000),
+ (CHARLIE, SEE, 100_000),
+ (CHARLIE, USSD, 100_000),
+ (CHARLIE, EDF, 100_000),
+ (CHARLIE, TEST, 100_000),
+ ])
+ }
+
+ pub fn build(self) -> sp_io::TestExternalities {
+ let mut t = frame_system::GenesisConfig::default()
+ .build_storage::()
+ .unwrap();
+
+ orml_tokens::GenesisConfig:: {
+ balances: self
+ .balances
+ .into_iter()
+ .collect::>(),
+ }
+ .assimilate_storage(&mut t)
+ .unwrap();
+
+ t.into()
+ }
+}
diff --git a/blockchain/modules/edfis-launchpad/src/tests.rs b/blockchain/modules/edfis-launchpad/src/tests.rs
new file mode 100644
index 00000000..881216bf
--- /dev/null
+++ b/blockchain/modules/edfis-launchpad/src/tests.rs
@@ -0,0 +1,1144 @@
+// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم
+
+// This file is part of Setheum.
+
+// Copyright (C) 2019-Present Setheum Labs.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// 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, either version 3 of the License, or
+// (at your option) any later version.
+
+// 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 .
+
+//! Unit tests for the Edfis Launchpad module.
+
+#![cfg(test)]
+
+use super::*;
+use frame_support::{assert_noop, assert_ok};
+use mock::*;
+
+#[test]
+fn proposal_info_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 0,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: false,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_eq!(
+ LaunchPad::proposal_info(TEST),
+ Some(CampaignInfo {
+ id:TEST,
+ origin: ALICE,
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: vec![],
+ period: 20,
+ campaign_start: 0,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: false,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ })
+ )
+ });
+}
+
+#[test]
+fn campaign_info_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 20,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ LaunchPad::on_initialize(23);
+ assert_eq!(
+ LaunchPad::campaign_info(TEST),
+ Some(CampaignInfo {
+ id:TEST,
+ origin: ALICE,
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: vec![],
+ period: 20,
+ campaign_start: 20,
+ campaign_end: 40,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: true,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ })
+ )
+ });
+}
+
+#[test]
+fn make_proposal_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ assert_ok!(LaunchPad::make_proposal(
+ Origin::signed(ALICE),
+ BOB,
+ USSD,
+ TEST,
+ 10,
+ 10_000,
+ 100_000,
+ 20
+ ));
+ });
+}
+
+#[test]
+fn make_proposal_does_not_work() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ assert_noop!(
+ LaunchPad::make_proposal(
+ Origin::signed(ALICE),
+ BOB,
+ USSD,
+ TEST,
+ 10,
+ 10_000,
+ 100_000,
+ 0
+ ),
+ Error::::ZeroPeriod
+ );
+ assert_noop!(
+ LaunchPad::make_proposal(
+ Origin::signed(ALICE),
+ BOB,
+ USSD,
+ TEST,
+ 10,
+ 10_000,
+ 100_000,
+ 21
+ ),
+ Error::::MaxActivePeriodExceeded
+ );
+ assert_noop!(
+ LaunchPad::make_proposal(
+ Origin::signed(ALICE),
+ BOB,
+ USSD,
+ TEST,
+ 10,
+ 10_000,
+ 99,
+ 20
+ ),
+ Error::::GoalBelowMinimumRaise
+ );
+ });
+}
+
+#[test]
+fn contribute_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10_000
+ ));
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 10_000
+ ));
+ assert_noop!(
+ LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10
+ ),
+ Error::::ContributionTooSmall
+ );
+ assert_noop!(
+ LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 100_001
+ ),
+ Error::::ContributionCurrencyNotEnough
+ );
+ });
+}
+
+#[test]
+fn contribute_does_not_work() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 0,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: false,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_noop!(
+ LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10
+ ),
+ Error::::CampaignNotFound
+ );
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+ assert_noop!(
+ LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10_000
+ ),
+ Error::::CampaignNotActive
+ );
+ });
+}
+
+#[test]
+fn claim_contribution_allocation_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 50_000
+ ));
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 50_000
+ ));
+
+ LaunchPad::on_initialize(41);
+ System::set_block_number(41);
+ assert_ok!(LaunchPad::claim_contribution_allocation(
+ Origin::signed(BOB),
+ TEST,
+ ));
+ });
+}
+
+#[test]
+fn claim_contribution_allocation_does_not_work() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 0,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: false,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ LaunchPad::on_initialize(23);
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10_000
+ ));
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 10_000
+ ));
+
+ assert_noop!(
+ LaunchPad::claim_contribution_allocation(
+ Origin::signed(BOB),
+ TEST,
+ ),
+ Error::::CampaignFailed
+ );
+ });
+}
+
+#[test]
+fn claim_campaign_fundraise_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 50_000
+ ));
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 50_000
+ ));
+
+ LaunchPad::on_initialize(41);
+ System::set_block_number(41);
+ assert_ok!(LaunchPad::claim_campaign_fundraise(
+ Origin::signed(ALICE),
+ TEST,
+ ));
+ });
+}
+
+#[test]
+fn claim_campaign_fundraise_does_not_work() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 0,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: false,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ LaunchPad::on_initialize(23);
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10_000
+ ));
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 10_000
+ ));
+
+ LaunchPad::on_initialize(41);
+ System::set_block_number(41);
+
+ assert_ok!(LaunchPad::claim_campaign_fundraise(
+ Origin::signed(ALICE),
+ TEST,
+ ));
+
+ assert_noop!(
+ LaunchPad::claim_campaign_fundraise(
+ Origin::signed(CHARLIE),
+ TEST,
+ ),
+ Error::::WrongOrigin
+ );
+ });
+}
+
+#[test]
+fn claim_campaign_fundraise_does_not_work_already_claimed() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 0,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: false,
+ is_active: false,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: true,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ LaunchPad::on_initialize(23);
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10_000
+ ));
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 10_000
+ ));
+
+ LaunchPad::on_initialize(41);
+ System::set_block_number(41);
+
+ assert_noop!(
+ LaunchPad::claim_campaign_fundraise(
+ Origin::signed(BOB),
+ TEST,
+ ),
+ Error::::CampaignAlreadyClaimed
+ );
+ });
+}
+
+#[test]
+fn approve_proposal_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10_000
+ ));
+ });
+}
+
+#[test]
+fn approve_proposal_does_not_work() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: true,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_noop!(
+ LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ),
+ Error::::ProposalAlreadyApproved
+ );
+ assert_noop!(
+ LaunchPad::approve_proposal(
+ Origin::signed(11),
+ USSD,
+ ),
+ Error::::ProposalNotFound
+ );
+ });
+}
+
+#[test]
+fn reject_proposal_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::reject_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+ });
+}
+
+#[test]
+fn reject_proposal_does_not_work() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: true,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_noop!(
+ LaunchPad::reject_proposal(
+ Origin::signed(11),
+ TEST,
+ ),
+ Error::::ProposalAlreadyApproved
+ );
+ assert_noop!(
+ LaunchPad::reject_proposal(
+ Origin::signed(11),
+ USSD,
+ ),
+ Error::::ProposalNotFound
+ );
+ });
+}
+
+#[test]
+fn get_contributors_count_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ let proposal = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, proposal.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Proposal should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 10_000
+ ));
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(BOB),
+ TEST,
+ 10_000
+ ));
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(CHARLIE),
+ TEST,
+ 10_000
+ ));
+
+ assert_eq!(LaunchPad::get_contributors_count(TEST), 3);
+ });
+}
+
+#[test]
+fn get_total_amounts_raised_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ assert_eq!(
+ LaunchPad::get_total_amounts_raised(),
+ vec![]
+ );
+ let campaign = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, campaign.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Campaign should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 50_000
+ ));
+
+ LaunchPad::on_initialize(40);
+
+ assert_ok!(LaunchPad::on_successful_campaign(>::block_number(), TEST));
+ assert_eq!(
+ LaunchPad::get_total_amounts_raised(),
+ vec![
+ (USSD, 50000),
+ ]
+ );
+ });
+}
+
+#[test]
+fn on_retire_works() {
+ ExtBuilder::default()
+ .one_hundred_thousand_for_all()
+ .build()
+ .execute_with(|| {
+ assert_eq!(
+ LaunchPad::get_total_amounts_raised(),
+ vec![]
+ );
+ let campaign = CampaignInfo {
+ id: TEST,
+ origin: ALICE.clone(),
+ beneficiary: BOB,
+ pool: LaunchPad::campaign_pool(0),
+ raise_currency: USSD,
+ sale_token: TEST,
+ token_price: 10,
+ crowd_allocation: 10_000,
+ goal: 100_000,
+ raised: 0,
+ contributors_count: 0,
+ contributions: Vec::new(),
+ period: 20,
+ campaign_start: 21,
+ campaign_end: 0,
+ campaign_retirement_period: 0,
+ proposal_retirement_period: 0,
+ is_approved: false,
+ is_rejected: false,
+ is_waiting: true,
+ is_active: true,
+ is_successful: false,
+ is_failed: false,
+ is_ended: false,
+ is_claimed: false,
+ };
+ >::insert(TEST, campaign.clone());
+ assert!(
+ >::contains_key(TEST),
+ "Campaign should be in storage"
+ );
+
+ assert_ok!(LaunchPad::approve_proposal(
+ Origin::signed(11),
+ TEST,
+ ));
+
+ LaunchPad::on_initialize(21);
+
+ assert_ok!(LaunchPad::contribute(
+ Origin::signed(ALICE),
+ TEST,
+ 50_000
+ ));
+
+ LaunchPad::on_initialize(60);
+
+ assert_ok!(LaunchPad::on_retire(TEST));
+ });
+}
diff --git a/blockchain/modules/edfis-launchpad/src/weights.rs b/blockchain/modules/edfis-launchpad/src/weights.rs
new file mode 100644
index 00000000..22646f55
--- /dev/null
+++ b/blockchain/modules/edfis-launchpad/src/weights.rs
@@ -0,0 +1,149 @@
+// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم
+
+// This file is part of Setheum.
+
+// Copyright (C) 2019-Present Setheum Labs.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// 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, either version 3 of the License, or
+// (at your option) any later version.
+
+// 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 .
+
+//! Autogenerated weights for module_edfis_launchpad
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-04-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
+
+// Executed Command:
+// target/release/setheum-node
+// benchmark
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=module_edfis_launchpad
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --output=./blockchain/modules/edfis-launchpad/src/weights.rs
+// --template=.maintain/module-weight-template.hbs
+
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for module_edfis_launchpad.
+pub trait WeightInfo {
+ fn on_initialize(n: u32, ) -> Weight;
+ fn make_proposal() -> Weight;
+ fn contribute() -> Weight;
+ fn claim_contribution_allocation() -> Weight;
+ fn claim_campaign_fundraise() -> Weight;
+ fn approve_proposal() -> Weight;
+ fn reject_proposal() -> Weight;
+ fn activate_waiting_campaign() -> Weight;
+}
+
+/// Weights for module_edfis_launchpad using the Setheum node and recommended hardware.
+pub struct SetheumWeight(PhantomData);
+impl WeightInfo for SetheumWeight {
+ fn on_initialize(n: u32, ) -> Weight {
+ Weight::from_parts(16_173_000, 0)
+ // Standard Error: 0
+ .saturating_add((2_000 as u64).saturating_mul(n as u64))
+ .saturating_add(T::DbWeight::get().reads(2 as u64))
+ }
+ fn make_proposal() -> Weight {
+ Weight::from_parts(180_406_000, 0)
+ .saturating_add(T::DbWeight::get().reads(6 as u64))
+ .saturating_add(T::DbWeight::get().writes(7 as u64))
+ }
+ fn contribute() -> Weight {
+ Weight::from_parts(127_732_000, 0)
+ .saturating_add(T::DbWeight::get().reads(4 as u64))
+ .saturating_add(T::DbWeight::get().writes(4 as u64))
+ }
+ fn claim_contribution_allocation() -> Weight {
+ Weight::from_parts(121_893_000, 0)
+ .saturating_add(T::DbWeight::get().reads(4 as u64))
+ .saturating_add(T::DbWeight::get().writes(3 as u64))
+ }
+ fn claim_campaign_fundraise() -> Weight {
+ Weight::from_parts(38_662_000, 0)
+ .saturating_add(T::DbWeight::get().reads(1 as u64))
+ }
+ fn approve_proposal() -> Weight {
+ Weight::from_parts(47_547_000, 0)
+ .saturating_add(T::DbWeight::get().reads(2 as u64))
+ .saturating_add(T::DbWeight::get().writes(3 as u64))
+ }
+ fn reject_proposal() -> Weight {
+ Weight::from_parts(38_272_000, 0)
+ .saturating_add(T::DbWeight::get().reads(1 as u64))
+ .saturating_add(T::DbWeight::get().writes(1 as u64))
+ }
+ fn activate_waiting_campaign() -> Weight {
+ Weight::from_parts(35_868_000, 0)
+ .saturating_add(T::DbWeight::get().reads(1 as u64))
+ .saturating_add(T::DbWeight::get().writes(1 as u64))
+ }
+}
+
+// For backwards compatibility and tests
+impl WeightInfo for () {
+ fn on_initialize(n: u32, ) -> Weight {
+ Weight::from_parts(16_173_000, 0)
+ // Standard Error: 0
+ .saturating_add((2_000 as u64).saturating_mul(n as u64))
+ .saturating_add(RocksDbWeight::get().reads(2 as u64))
+ }
+ fn make_proposal() -> Weight {
+ Weight::from_parts(180_406_000, 0)
+ .saturating_add(RocksDbWeight::get().reads(6 as u64))
+ .saturating_add(RocksDbWeight::get().writes(7 as u64))
+ }
+ fn contribute() -> Weight {
+ Weight::from_parts(127_732_000, 0)
+ .saturating_add(RocksDbWeight::get().reads(4 as u64))
+ .saturating_add(RocksDbWeight::get().writes(4 as u64))
+ }
+ fn claim_contribution_allocation() -> Weight {
+ Weight::from_parts(121_893_000, 0)
+ .saturating_add(RocksDbWeight::get().reads(4 as u64))
+ .saturating_add(RocksDbWeight::get().writes(3 as u64))
+ }
+ fn claim_campaign_fundraise() -> Weight {
+ Weight::from_parts(38_662_000, 0)
+ .saturating_add(RocksDbWeight::get().reads(1 as u64))
+ }
+ fn approve_proposal() -> Weight {
+ Weight::from_parts(47_547_000, 0)
+ .saturating_add(RocksDbWeight::get().reads(2 as u64))
+ .saturating_add(RocksDbWeight::get().writes(3 as u64))
+ }
+ fn reject_proposal() -> Weight {
+ Weight::from_parts(38_272_000, 0)
+ .saturating_add(RocksDbWeight::get().reads(1 as u64))
+ .saturating_add(RocksDbWeight::get().writes(1 as u64))
+ }
+ fn activate_waiting_campaign() -> Weight {
+ Weight::from_parts(35_868_000, 0)
+ .saturating_add(RocksDbWeight::get().reads(1 as u64))
+ .saturating_add(RocksDbWeight::get().writes(1 as u64))
+ }
+}
diff --git a/blockchain/modules/edfis-oracle/src/weights.rs b/blockchain/modules/edfis-oracle/src/weights.rs
index e3d35e02..5651ebb6 100644
--- a/blockchain/modules/edfis-oracle/src/weights.rs
+++ b/blockchain/modules/edfis-oracle/src/weights.rs
@@ -26,7 +26,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// --chain=dev
// --steps=50
diff --git a/blockchain/modules/edfis-swap-legacy/src/weights.rs b/blockchain/modules/edfis-swap-legacy/src/weights.rs
index 75b8bb18..9acf300a 100644
--- a/blockchain/modules/edfis-swap-legacy/src/weights.rs
+++ b/blockchain/modules/edfis-swap-legacy/src/weights.rs
@@ -25,7 +25,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// --chain=dev
// --steps=50
diff --git a/blockchain/modules/edfis-swap/src/weights.rs b/blockchain/modules/edfis-swap/src/weights.rs
index efc167cd..82e2707d 100644
--- a/blockchain/modules/edfis-swap/src/weights.rs
+++ b/blockchain/modules/edfis-swap/src/weights.rs
@@ -25,7 +25,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// --chain=dev
// --steps=50
diff --git a/blockchain/modules/evm/src/weights.rs b/blockchain/modules/evm/src/weights.rs
index fe0e560f..e6699e32 100644
--- a/blockchain/modules/evm/src/weights.rs
+++ b/blockchain/modules/evm/src/weights.rs
@@ -26,7 +26,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// pallet
// --chain=dev
diff --git a/blockchain/modules/nft/src/weights.rs b/blockchain/modules/nft/src/weights.rs
index cb82347f..b04b7238 100644
--- a/blockchain/modules/nft/src/weights.rs
+++ b/blockchain/modules/nft/src/weights.rs
@@ -25,7 +25,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// --chain=dev
// --steps=50
diff --git a/blockchain/modules/prices/src/weights.rs b/blockchain/modules/prices/src/weights.rs
index c02f9f62..a98bec52 100644
--- a/blockchain/modules/prices/src/weights.rs
+++ b/blockchain/modules/prices/src/weights.rs
@@ -26,7 +26,7 @@
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
-// target/release/setheum
+// target/release/setheum-node
// benchmark
// --chain=dev
// --steps=50
diff --git a/blockchain/modules/support/TODO.md b/blockchain/modules/support/TODO.md
index 3e9cd565..9150f0e1 100644
--- a/blockchain/modules/support/TODO.md
+++ b/blockchain/modules/support/TODO.md
@@ -54,3 +54,5 @@ These tasks are just for this file specifically.
- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo.
- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`.
+- [ ] [[src/lib.rs:0] - Use this as reference to upgrade the existing implementation of the Launchpad](src/lib.rs): Use this as reference to upgrade the existing implementation of the Launchpad.
+- [ ] []()
diff --git a/blockchain/modules/support/src/edfis_launchpad.rs b/blockchain/modules/support/src/edfis_launchpad.rs
new file mode 100644
index 00000000..d5bd5a17
--- /dev/null
+++ b/blockchain/modules/support/src/edfis_launchpad.rs
@@ -0,0 +1,101 @@
+// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم
+
+// This file is part of Setheum.
+
+// Copyright (C) 2019-Present Setheum Labs.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// 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, either version 3 of the License, or
+// (at your option) any later version.
+
+// 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 .
+
+//! Traits for the Launchpad Crowdsales Pallet.
+
+use codec::{Decode, Encode};
+use sp_runtime::{
+ DispatchError, DispatchResult,
+};
+use sp_std::{
+ cmp::{Eq, PartialEq},
+};
+
+/// Abstraction over th Launchpad Proposal system.
+pub trait Proposal {
+ type CurrencyId;
+
+ /// The Campaign Proposal info of `id`
+ fn proposal_info(id: Self::CurrencyId) -> Option>;
+ /// Get all proposals
+ fn all_proposals() -> Vec>;
+ /// Create new Campaign Proposal with specific `CampaignInfo`, return the `id` of the Campaign
+ fn new_proposal(
+ origin: AccountId,
+ beneficiary: AccountId,
+ raise_currency: CurrencyId,
+ sale_token: CurrencyId,
+ token_price: Balance,
+ crowd_allocation: Balance,
+ goal: Balance,
+ period: BlockNumber,
+ ) -> DispatchResult;
+ /// Approve Proposal by `id` at `now`.
+ fn on_approve_proposal(id: Self::CurrencyId) -> sp_std::result::Result<(), DispatchError>;
+ /// Reject Proposal by `id` and update storage
+ fn on_reject_proposal(id: Self::CurrencyId) -> sp_std::result::Result<(), DispatchError>;
+ /// Remove Proposal by `id` from storage
+ fn remove_proposal(id: Self::CurrencyId) -> sp_std::result::Result<(), DispatchError>;
+}
+
+/// Abstraction over the Launchpad Campaign system.
+pub trait CampaignManager {
+ type CurrencyId;
+
+ /// The Campaign info of `id`
+ fn campaign_info(id: Self::CurrencyId) -> Option>;
+ /// Get all proposals
+ fn all_campaigns() -> Vec>;
+ /// Called when a contribution is received.
+ fn on_contribution(
+ who: AccountId,
+ id: Self::CurrencyId,
+ amount: Balance,
+ ) -> DispatchResult;
+ /// Called when a contribution allocation is claimed
+ fn on_claim_allocation(
+ who: AccountId,
+ id: Self::CurrencyId,
+ ) -> DispatchResult;
+ /// Called when a campaign's raised fund is claimed
+ fn on_claim_campaign(
+ who: AccountId,
+ id: Self::CurrencyId,
+ ) -> DispatchResult;
+ /// Called when a failed campaign is claimed by the proposer
+ fn on_claim_failed_campaign(
+ who: AccountId,
+ id: Self::CurrencyId,
+ ) -> DispatchResult;
+ /// Activate a campaign by `id`
+ fn activate_campaign(id: Self::CurrencyId) -> DispatchResult;
+ /// Ensure campaign is Valid and Successfully Ended
+ fn ensure_successfully_ended_campaign(id: Self::CurrencyId) -> DispatchResult;
+ /// Record Successful Campaign by `id`
+ fn on_successful_campaign(now: BlockNumber, id: Self::CurrencyId) -> DispatchResult ;
+ /// Record Failed Campaign by `id`
+ fn on_failed_campaign(now: BlockNumber, id: Self::CurrencyId) -> DispatchResult ;
+ /// Called when pool is retired
+ fn on_retire(id: Self::CurrencyId)-> DispatchResult;
+ /// Get amount of contributors in a campaign
+ fn get_contributors_count(id: Self::CurrencyId) -> u32;
+ /// Get the total amounts raised in protocol
+ fn get_total_amounts_raised() -> Vec<(CurrencyId, Balance)>;
+}
diff --git a/blockchain/modules/support/src/edfis.rs b/blockchain/modules/support/src/edfis_swap.rs
similarity index 96%
rename from blockchain/modules/support/src/edfis.rs
rename to blockchain/modules/support/src/edfis_swap.rs
index fc9206e8..525bbd26 100644
--- a/blockchain/modules/support/src/edfis.rs
+++ b/blockchain/modules/support/src/edfis_swap.rs
@@ -1,230 +1,230 @@
-// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم
-
-// This file is part of Setheum.
-
-// Copyright (C) 2019-Present Setheum Labs.
-// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
-
-// 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, either version 3 of the License, or
-// (at your option) any later version.
-
-// 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 .
-
-use frame_support::{ensure, traits::Get};
-use parity_scale_codec::{Decode, Encode};
-use scale_info::TypeInfo;
-#[cfg(feature = "std")]
-use serde::{Deserialize, Serialize};
-use sp_core::H160;
-use sp_runtime::{DispatchError, DispatchResult, RuntimeDebug};
-use sp_std::{cmp::PartialEq, prelude::*, result::Result};
-
-#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)]
-pub enum SwapLimit {
- /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount)
- ExactSupply(Balance, Balance),
- /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount)
- ExactTarget(Balance, Balance),
-}
-
-pub trait SwapManager {
- fn get_liquidity_pool(
- currency_id_a: CurrencyId,
- currency_id_b: CurrencyId
- ) -> (Balance, Balance);
-
- fn get_liquidity_token_address(
- currency_id_a: CurrencyId,
- currency_id_b: CurrencyId
- ) -> Option;
-
- fn get_swap_amount(
- path: &[CurrencyId],
- limit: SwapLimit