diff --git a/Cargo.lock b/Cargo.lock
index 75ba85237..b3ab2d074 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4592,6 +4592,8 @@ dependencies = [
"frame-metadata-hash-extension",
"frame-support",
"frame-system",
+ "hex",
+ "hex-literal",
"itertools 0.11.0",
"macros",
"orml-oracle",
@@ -4906,9 +4908,9 @@ dependencies = [
[[package]]
name = "k256"
-version = "0.13.3"
+version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b"
+checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
dependencies = [
"cfg-if",
"ecdsa",
@@ -7274,7 +7276,10 @@ dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
+ "hex",
+ "hex-literal",
"itertools 0.11.0",
+ "k256",
"log",
"on-slash-vesting",
"pallet-assets",
diff --git a/Cargo.toml b/Cargo.toml
index cb065e758..fc22a3638 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -92,6 +92,7 @@ scale-info = { version = "2.11.3", default-features = false, features = [
] }
jsonrpsee = { version = "0.22.5", features = ["server"] }
hex-literal = "0.4.1"
+hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
serde = { version = "1.0.204", default-features = false }
serde_json = "1.0.120"
smallvec = "1.13.2"
@@ -103,6 +104,7 @@ array-bytes = { version = "6.2.3", default-features = false }
serde-json-core = { version = '0.5.1', default-features = false }
heapless = { version = "0.8", default-features = false }
color-print = "0.3.6"
+k256 = { version = "0.13.4", default-features = false, features = ["ecdsa"] }
# Emulations
xcm-emulator = { version = "0.12.0", default-features = false }
@@ -125,7 +127,7 @@ sp-staking = { version = "33.0.0", default-features = false }
# sp-runtime v38.0.1 takes out sp_std from runtime_string file and calls "alloc" directly which errors out
sp-runtime = { version = "=38.0.0", default-features = false }
sp-arithmetic = { version = "26.0.0", default-features = false }
-sp-core = { version = "34.0.0", default-features = false }
+sp-core = { version = "34.0.0", default-features = false, features = ["serde"] }
sp-io = { version = "37.0.0", default-features = false }
sp-blockchain = { version = "35.0.0", default-features = false }
sp-consensus-aura = { version = "0.39.0", default-features = false }
diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml
index 258f285bb..7856bb96a 100644
--- a/integration-tests/Cargo.toml
+++ b/integration-tests/Cargo.toml
@@ -82,6 +82,8 @@ pallet-session.workspace = true
pallet-proxy-bonding.workspace = true
pallet-skip-feeless-payment.workspace = true
xcm-fee-payment-runtime-api.workspace = true
+hex-literal.workspace = true
+hex.workspace = true
# Runtimes
polkadot-runtime.workspace = true
diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs
index a81cc818e..7f7d5bd59 100644
--- a/integration-tests/src/lib.rs
+++ b/integration-tests/src/lib.rs
@@ -15,12 +15,12 @@
// along with this program. If not, see .
pub mod constants;
-
#[cfg(test)]
mod tests;
pub use constants::{accounts::*, asset_hub, penpal, polimec, polkadot};
pub use frame_support::{assert_noop, assert_ok, pallet_prelude::Weight, parameter_types, traits::Hooks};
+use macros::generate_accounts;
pub use parachains_common::{AccountId, AssetHubPolkadotAuraId, AuraId, Balance, BlockNumber};
pub use sp_core::{sr25519, storage::Storage, Encode, Get};
pub use xcm::prelude::*;
diff --git a/integration-tests/src/tests/credentials.rs b/integration-tests/src/tests/credentials.rs
index 9adcc3c17..921286120 100644
--- a/integration-tests/src/tests/credentials.rs
+++ b/integration-tests/src/tests/credentials.rs
@@ -16,7 +16,6 @@
use crate::*;
use frame_support::{assert_ok, dispatch::GetDispatchInfo, traits::tokens::currency::VestingSchedule};
-use macros::generate_accounts;
use polimec_common::credentials::{Did, InvestorType};
use polimec_common_test_utils::{get_fake_jwt, get_mock_jwt_with_cid, get_test_jwt};
use polimec_runtime::PLMC;
diff --git a/integration-tests/src/tests/defaults.rs b/integration-tests/src/tests/defaults.rs
index 550e805f0..d58df5e42 100644
--- a/integration-tests/src/tests/defaults.rs
+++ b/integration-tests/src/tests/defaults.rs
@@ -17,8 +17,8 @@ use crate::PolimecRuntime;
use frame_support::BoundedVec;
pub use pallet_funding::instantiator::{BidParams, ContributionParams, UserToUSDBalance};
use pallet_funding::{
- AcceptedFundingAsset, BiddingTicketSizes, ContributingTicketSizes, CurrencyMetadata, ParticipationMode,
- PriceProviderOf, ProjectMetadata, ProjectMetadataOf, TicketSize,
+ AcceptedFundingAsset, BiddingTicketSizes, ContributingTicketSizes, CurrencyMetadata, ParticipantsAccountType,
+ ParticipationMode, PriceProviderOf, ProjectMetadata, ProjectMetadataOf, TicketSize,
};
use sp_arithmetic::{FixedPointNumber, Percent};
@@ -88,6 +88,7 @@ pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf Vec> {
diff --git a/integration-tests/src/tests/e2e.rs b/integration-tests/src/tests/e2e.rs
index ebf7a82ca..9b8d6111e 100644
--- a/integration-tests/src/tests/e2e.rs
+++ b/integration-tests/src/tests/e2e.rs
@@ -96,6 +96,7 @@ pub fn project_metadata() -> ProjectMetadataOf {
.unwrap(),
funding_destination_account: ISSUER.into(),
policy_ipfs_cid: Some(metadata_hash),
+ participants_account_type: ParticipantsAccountType::Polkadot,
}
}
diff --git a/integration-tests/src/tests/ethereum_support.rs b/integration-tests/src/tests/ethereum_support.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/integration-tests/src/tests/ethereum_support.rs
@@ -0,0 +1 @@
+
diff --git a/integration-tests/src/tests/mod.rs b/integration-tests/src/tests/mod.rs
index c0e1a51e1..fc2e2050e 100644
--- a/integration-tests/src/tests/mod.rs
+++ b/integration-tests/src/tests/mod.rs
@@ -18,6 +18,7 @@ mod credentials;
mod ct_migration;
mod defaults;
mod e2e;
+mod ethereum_support;
mod evaluator_slash_sideffects;
mod governance;
mod oracle;
diff --git a/pallets/funding/Cargo.toml b/pallets/funding/Cargo.toml
index 01cd1110e..97169261f 100644
--- a/pallets/funding/Cargo.toml
+++ b/pallets/funding/Cargo.toml
@@ -45,6 +45,9 @@ polkadot-parachain-primitives.workspace = true
sp-api.workspace = true
polimec-common-test-utils = { workspace = true, optional = true }
frame-benchmarking = { workspace = true, optional = true }
+hex-literal.workspace = true
+k256.workspace = true
+hex.workspace = true
# Used in the instantiator.
itertools.workspace = true
@@ -59,7 +62,7 @@ xcm-builder.workspace = true
xcm-executor.workspace = true
[features]
-default = [ "std" ]
+default = [ "std", "sp-core/serde" ]
std = [
"frame-benchmarking?/std",
"frame-support/std",
@@ -90,6 +93,8 @@ std = [
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
+ "k256/std",
+ "hex/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs
index fb2c1f34a..38801cbaf 100644
--- a/pallets/funding/src/benchmarking.rs
+++ b/pallets/funding/src/benchmarking.rs
@@ -81,6 +81,7 @@ where
participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(),
funding_destination_account: issuer,
policy_ipfs_cid: Some(metadata_hash.into()),
+ participants_account_type: ParticipantsAccountType::Polkadot,
}
}
@@ -410,6 +411,7 @@ mod benchmarks {
participation_currencies: vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC].try_into().unwrap(),
funding_destination_account: issuer_funding.clone().clone(),
policy_ipfs_cid: Some(BoundedVec::try_from(IPFS_CID.as_bytes().to_vec()).unwrap()),
+ participants_account_type: ParticipantsAccountType::Ethereum,
};
let jwt = get_mock_jwt_with_cid(
diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs
index d577ada0c..e6f02027c 100644
--- a/pallets/funding/src/functions/2_evaluation.rs
+++ b/pallets/funding/src/functions/2_evaluation.rs
@@ -73,6 +73,7 @@ impl Pallet {
usd_amount: Balance,
did: Did,
whitelisted_policy: Cid,
+ receiving_account: Junction,
) -> DispatchResultWithPostInfo {
// * Get variables *
let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?;
@@ -88,6 +89,7 @@ impl Pallet {
let total_evaluations_count = EvaluationCounts::::get(project_id);
let user_evaluations_count = Evaluations::::iter_prefix((project_id, evaluator)).count() as u32;
let project_policy = project_metadata.policy_ipfs_cid.ok_or(Error::::ImpossibleState)?;
+ let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?;
// * Validity Checks *
ensure!(project_policy == whitelisted_policy, Error::::PolicyMismatch);
@@ -100,6 +102,10 @@ impl Pallet {
);
ensure!(total_evaluations_count < T::MaxEvaluationsPerProject::get(), Error::::TooManyProjectParticipations);
ensure!(user_evaluations_count < T::MaxEvaluationsPerUser::get(), Error::::TooManyUserParticipations);
+ ensure!(
+ project_metadata.participants_account_type.junction_is_supported(&receiving_account),
+ Error::::UnsupportedReceiverAccountJunction
+ );
let plmc_bond = plmc_usd_price
.reciprocal()
@@ -129,6 +135,7 @@ impl Pallet {
early_usd_amount,
late_usd_amount,
when: now,
+ receiving_account,
};
T::NativeCurrency::hold(&HoldReason::Evaluation.into(), evaluator, plmc_bond)?;
diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs
index dae7bea08..bf9ea1e2e 100644
--- a/pallets/funding/src/functions/3_auction.rs
+++ b/pallets/funding/src/functions/3_auction.rs
@@ -46,8 +46,17 @@ impl Pallet {
#[transactional]
pub fn do_bid(params: DoBidParams) -> DispatchResultWithPostInfo {
// * Get variables *
- let DoBidParams { bidder, project_id, ct_amount, mode, funding_asset, investor_type, did, whitelisted_policy } =
- params;
+ let DoBidParams {
+ bidder,
+ project_id,
+ ct_amount,
+ mode,
+ funding_asset,
+ investor_type,
+ did,
+ whitelisted_policy,
+ receiving_account,
+ } = params;
let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?;
let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?;
@@ -110,6 +119,10 @@ impl Pallet {
Error::::TooHigh
);
ensure!(existing_bids.len() < T::MaxBidsPerUser::get() as usize, Error::::TooManyUserParticipations);
+ ensure!(
+ project_metadata.participants_account_type.junction_is_supported(&receiving_account),
+ Error::::UnsupportedReceiverAccountJunction
+ );
// While there's a remaining amount to bid for
while !amount_to_bid.is_zero() {
@@ -135,6 +148,7 @@ impl Pallet {
metadata_ticket_size_bounds,
total_bids_by_bidder: existing_bids_amount.saturating_add(perform_bid_calls),
total_bids_for_project: total_bids_for_project.saturating_add(perform_bid_calls),
+ receiving_account,
};
Self::do_perform_bid(perform_params)?;
@@ -169,6 +183,7 @@ impl Pallet {
metadata_ticket_size_bounds,
total_bids_by_bidder,
total_bids_for_project,
+ receiving_account,
} = do_perform_bid_params;
let ticket_size = ct_usd_price.checked_mul_int(ct_amount).ok_or(Error::::BadMath)?;
@@ -200,6 +215,7 @@ impl Pallet {
mode,
plmc_bond,
when: now,
+ receiving_account,
};
Self::bond_plmc_with_mode(&bidder, project_id, plmc_bond, mode, funding_asset)?;
diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs
index 5826fcf91..8b312ca46 100644
--- a/pallets/funding/src/functions/4_contribution.rs
+++ b/pallets/funding/src/functions/4_contribution.rs
@@ -13,6 +13,7 @@ impl Pallet {
investor_type,
did,
whitelisted_policy,
+ receiving_account,
} = params;
let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?;
let did_has_winning_bid = DidWithWinningBids::::get(project_id, did.clone());
@@ -46,6 +47,7 @@ impl Pallet {
investor_type,
did,
whitelisted_policy,
+ receiving_account,
};
Self::do_perform_contribution(perform_params)
@@ -63,6 +65,7 @@ impl Pallet {
investor_type,
did,
whitelisted_policy,
+ receiving_account,
} = params;
let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?;
@@ -107,6 +110,10 @@ impl Pallet {
contributor_ticket_size.usd_ticket_below_maximum_per_did(total_usd_bought_by_did + ticket_size),
Error::::TooHigh
);
+ ensure!(
+ project_metadata.participants_account_type.junction_is_supported(&receiving_account),
+ Error::::UnsupportedReceiverAccountJunction
+ );
let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier)?;
let funding_asset_amount = Self::calculate_funding_asset_amount(ticket_size, funding_asset)?;
@@ -124,6 +131,7 @@ impl Pallet {
funding_asset_amount,
plmc_bond,
when: now,
+ receiving_account,
};
// Try adding the new contribution to the system
diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs
index 32bacb598..9bb399bd9 100644
--- a/pallets/funding/src/functions/6_settlement.rs
+++ b/pallets/funding/src/functions/6_settlement.rs
@@ -17,10 +17,7 @@ use polimec_common::{
migration_types::{MigrationInfo, MigrationOrigin, MigrationStatus, ParticipationType},
ReleaseSchedule,
};
-use sp_runtime::{
- traits::{Convert, Zero},
- Perquintill,
-};
+use sp_runtime::{traits::Zero, Perquintill};
impl Pallet {
#[transactional]
@@ -140,6 +137,7 @@ impl Pallet {
ParticipationType::Evaluation,
ct_rewarded,
duration,
+ evaluation.receiving_account,
)?;
}
Evaluations::::remove((project_id, evaluation.evaluator.clone(), evaluation.id));
@@ -204,6 +202,7 @@ impl Pallet {
ParticipationType::Bid,
final_ct_amount,
ct_vesting_duration,
+ bid.receiving_account,
)?;
Self::release_funding_asset(
@@ -310,6 +309,7 @@ impl Pallet {
ParticipationType::Contribution,
contribution.ct_amount,
ct_vesting_duration,
+ contribution.receiving_account,
)?;
final_ct_amount = contribution.ct_amount;
@@ -484,10 +484,10 @@ impl Pallet {
participation_type: ParticipationType,
ct_amount: Balance,
vesting_time: BlockNumberFor,
+ receiving_account: Junction,
) -> DispatchResult {
UserMigrations::::try_mutate((project_id, origin), |maybe_migrations| -> DispatchResult {
- let location_user =
- Location::new(0, AccountId32 { network: None, id: T::AccountId32Conversion::convert(origin.clone()) });
+ let location_user = Location::new(0, receiving_account);
let migration_origin = MigrationOrigin { user: location_user, id, participation_type };
let vesting_time: u64 = vesting_time.try_into().map_err(|_| Error::::BadMath)?;
let migration_info: MigrationInfo = (ct_amount, vesting_time).into();
diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs
index d9c1eac9f..974e6aa5f 100644
--- a/pallets/funding/src/functions/misc.rs
+++ b/pallets/funding/src/functions/misc.rs
@@ -1,6 +1,14 @@
#[allow(clippy::wildcard_imports)]
use super::*;
+use alloc::string::{String, ToString};
use polimec_common::ProvideAssetPrice;
+use sp_core::{
+ ecdsa::{Public as EcdsaPublic, Signature as EcdsaSignature},
+ keccak_256,
+ sr25519::{Public as SrPublic, Signature as SrSignature},
+ ByteArray,
+};
+use sp_runtime::traits::Verify;
// Helper functions
// ATTENTION: if this is called directly, it will not be transactional
@@ -413,6 +421,74 @@ impl Pallet {
Ok(())
}
+ pub fn get_message_to_sign(polimec_account: AccountIdOf, project_id: ProjectId) -> Option {
+ let mut message = String::new();
+
+ let polimec_account_ss58_string = T::SS58Conversion::convert(polimec_account.clone());
+ let project_id_string = project_id.to_string();
+ let nonce_string = frame_system::Pallet::::account_nonce(polimec_account).to_string();
+
+ use alloc::fmt::Write;
+ write!(
+ &mut message,
+ "polimec account: {} - project id: {} - nonce: {}",
+ polimec_account_ss58_string, project_id_string, nonce_string
+ )
+ .ok()?;
+ Some(message)
+ }
+
+ pub fn verify_receiving_account_signature(
+ polimec_account: &AccountIdOf,
+ project_id: ProjectId,
+ receiver_account: &Junction,
+ mut signature_bytes: [u8; 65],
+ ) -> DispatchResult {
+ let message_to_sign = Self::get_message_to_sign(polimec_account.clone(), project_id)
+ .ok_or(Error::::BadReceiverAccountSignature)?;
+ let message_bytes = message_to_sign.into_bytes();
+ match receiver_account {
+ Junction::AccountId32 { network, id } =>
+ if network.is_none() {
+ let signature = SrSignature::from_slice(&signature_bytes[..64])
+ .map_err(|_| Error::::BadReceiverAccountSignature)?;
+ let public = SrPublic::from_slice(id).map_err(|_| Error::::BadReceiverAccountSignature)?;
+ ensure!(
+ signature.verify(message_bytes.as_slice(), &public),
+ Error::::BadReceiverAccountSignature
+ );
+ },
+
+ Junction::AccountKey20 { network, key } if *network == Some(NetworkId::Ethereum { chain_id: 1 }) => {
+ let message_length = message_bytes.len().to_string().into_bytes();
+ let message_prefix = b"\x19Ethereum Signed Message:\n".to_vec();
+ let full_message = [&message_prefix[..], &message_length[..], &message_bytes[..]].concat();
+ let hashed_message = keccak_256(full_message.as_slice());
+
+ match signature_bytes[64] {
+ 27 => signature_bytes[64] = 0x00,
+ 28 => signature_bytes[64] = 0x01,
+ _v => return Err(Error::::BadReceiverAccountSignature.into()),
+ }
+
+ // If a user specifies an AccountKey20, we assume they used the ECDSA crypto (secp256k1), so the signature is 65 bytes.
+ let signature = EcdsaSignature::from_slice(&signature_bytes)
+ .map_err(|_| Error::::BadReceiverAccountSignature)?;
+ let public_compressed: EcdsaPublic =
+ signature.recover_prehashed(&hashed_message).ok_or(Error::::BadReceiverAccountSignature)?;
+ let public_uncompressed = k256::ecdsa::VerifyingKey::from_sec1_bytes(&public_compressed)
+ .map_err(|_| Error::::BadReceiverAccountSignature)?;
+ let public_uncompressed_point = public_uncompressed.to_encoded_point(false).to_bytes();
+ let derived_ethereum_account: [u8; 20] = keccak_256(&public_uncompressed_point[1..])[12..32]
+ .try_into()
+ .map_err(|_| Error::::BadReceiverAccountSignature)?;
+ ensure!(*key == derived_ethereum_account, Error::::BadReceiverAccountSignature);
+ },
+ _ => return Err(Error::::UnsupportedReceiverAccountJunction.into()),
+ };
+ Ok(())
+ }
+
pub fn get_decimals_aware_funding_asset_price(funding_asset: &AcceptedFundingAsset) -> Option> {
let funding_asset_id = funding_asset.id();
let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id);
diff --git a/pallets/funding/src/functions/mod.rs b/pallets/funding/src/functions/mod.rs
index c7b452d09..a7cd3d7fd 100644
--- a/pallets/funding/src/functions/mod.rs
+++ b/pallets/funding/src/functions/mod.rs
@@ -39,6 +39,6 @@ mod ct_migration;
mod evaluation;
#[path = "5_funding_end.rs"]
mod funding_end;
-mod misc;
+pub mod misc;
#[path = "6_settlement.rs"]
mod settlement;
diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs
index 421b4815f..32a7c3cee 100644
--- a/pallets/funding/src/instantiator/chain_interactions.rs
+++ b/pallets/funding/src/instantiator/chain_interactions.rs
@@ -441,8 +441,9 @@ impl<
&account.clone(),
project_id,
usd_amount,
- generate_did_from_account(account),
+ generate_did_from_account(account.clone()),
project_policy.clone(),
+ Junction::AccountId32 { network: None, id: T::AccountId32Conversion::convert(account) },
)
})?;
}
@@ -456,7 +457,7 @@ impl<
self.execute(|| {
let did = generate_did_from_account(bid.bidder.clone());
let params = DoBidParams:: {
- bidder: bid.bidder,
+ bidder: bid.bidder.clone(),
project_id,
ct_amount: bid.amount,
mode: bid.mode,
@@ -464,6 +465,10 @@ impl<
did,
investor_type: InvestorType::Institutional,
whitelisted_policy: project_policy.clone(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: T::AccountId32Conversion::convert(bid.bidder),
+ },
};
crate::Pallet::::do_bid(params)
})?;
@@ -485,7 +490,7 @@ impl<
// We use institutional to be able to test most multipliers.
let investor_type = InvestorType::Institutional;
let params = DoContributeParams:: {
- contributor: cont.contributor,
+ contributor: cont.contributor.clone(),
project_id,
ct_amount: cont.amount,
mode: cont.mode,
@@ -493,6 +498,10 @@ impl<
did,
investor_type,
whitelisted_policy: project_policy.clone(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: T::AccountId32Conversion::convert(cont.contributor),
+ },
};
self.execute(|| crate::Pallet::::do_contribute(params))?;
},
diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs
index 415ddc197..2a00ca4f6 100644
--- a/pallets/funding/src/instantiator/tests.rs
+++ b/pallets/funding/src/instantiator/tests.rs
@@ -52,6 +52,7 @@ fn dry_run_wap() {
participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(),
funding_destination_account: 0,
policy_ipfs_cid: Some(metadata_hash),
+ participants_account_type: ParticipantsAccountType::Polkadot,
};
// overfund with plmc
@@ -132,6 +133,7 @@ fn find_bucket_for_wap() {
participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(),
funding_destination_account: 0,
policy_ipfs_cid: Some(metadata_hash),
+ participants_account_type: ParticipantsAccountType::Polkadot,
};
// overfund with plmc
diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs
index 630bc7a0d..f96c0bdbe 100644
--- a/pallets/funding/src/lib.rs
+++ b/pallets/funding/src/lib.rs
@@ -70,6 +70,8 @@
#![cfg_attr(not(feature = "std"), no_std)]
// Needed due to empty sections raising the warning
#![allow(unreachable_patterns)]
+// Needed for now beause receiving account extrinsics have too many arguments
+#![allow(clippy::too_many_arguments)]
// This recursion limit is needed because we have too many benchmarks and benchmarking will fail if
// we add more without this limit.
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "512")]
@@ -102,6 +104,7 @@ pub mod storage_migrations;
pub mod traits;
pub mod types;
pub mod weights;
+use alloc::string::String;
#[cfg(test)]
pub mod mock;
@@ -176,7 +179,7 @@ pub mod pallet {
#[pallet::config]
pub trait Config:
- frame_system::Config
+ frame_system::Config
+ pallet_balances::Config
+ pallet_xcm::Config
+ pallet_linear_release::Config>
@@ -193,6 +196,9 @@ pub mod pallet {
/// A way to convert from and to the account type used in CT migrations
type AccountId32Conversion: ConvertBack;
+ /// A way to get the ss58 string representation of an account. Used for linking a polimec account to a receiving account.
+ type SS58Conversion: Convert;
+
/// Type used for testing and benchmarks
#[cfg(any(test, feature = "runtime-benchmarks", feature = "std"))]
type AllPalletsWithoutSystem: OnFinalize>
@@ -568,6 +574,10 @@ pub mod pallet {
ParticipationNotFound,
/// The user investor type is not eligible for the action.
WrongInvestorType,
+ /// Could not verify that the signature provided corresponds to the specified receiver account.
+ BadReceiverAccountSignature,
+ /// Used a Junction variant unsupported to represent a receving account.
+ UnsupportedReceiverAccountJunction,
// * Project Error. Project information not found, or project has an incorrect state. *
/// The project details were not found. Happens when the project with provided ID does
@@ -749,7 +759,28 @@ pub mod pallet {
let (account, did, _investor_type, whitelisted_policy) =
T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
- Self::do_evaluate(&account, project_id, usd_amount, did, whitelisted_policy)
+ let receiving_account =
+ Junction::AccountId32 { network: None, id: T::AccountId32Conversion::convert(account.clone()) };
+
+ Self::do_evaluate(&account, project_id, usd_amount, did, whitelisted_policy, receiving_account)
+ }
+
+ #[pallet::call_index(40)]
+ #[pallet::weight(WeightInfoOf::::evaluate(::MaxEvaluationsPerUser::get()))]
+ pub fn evaluate_with_receiving_account(
+ origin: OriginFor,
+ jwt: UntrustedToken,
+ project_id: ProjectId,
+ #[pallet::compact] usd_amount: Balance,
+ receiving_account: Junction,
+ signature_bytes: [u8; 65],
+ ) -> DispatchResultWithPostInfo {
+ let (account, did, _investor_type, whitelisted_policy) =
+ T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
+
+ Self::verify_receiving_account_signature(&account, project_id, &receiving_account, signature_bytes)?;
+
+ Self::do_evaluate(&account, project_id, usd_amount, did, whitelisted_policy, receiving_account)
}
#[pallet::call_index(5)]
@@ -779,6 +810,10 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
let (bidder, did, investor_type, whitelisted_policy) =
T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
+
+ let receiving_account =
+ Junction::AccountId32 { network: None, id: T::AccountId32Conversion::convert(bidder.clone()) };
+
let params = DoBidParams:: {
bidder,
project_id,
@@ -788,7 +823,48 @@ pub mod pallet {
did,
investor_type,
whitelisted_policy,
+ receiving_account,
};
+
+ Self::do_bid(params)
+ }
+
+ #[pallet::call_index(70)]
+ #[pallet::weight(
+ WeightInfoOf::::bid(
+ ::MaxBidsPerUser::get(),
+ // Assuming the current bucket is full, and has a price higher than the minimum.
+ // This user is buying 100% of the bid allocation.
+ // Since each bucket has 10% of the allocation, one bid can be split into a max of 10
+ 10
+ ))]
+ pub fn bid_with_receiving_account(
+ origin: OriginFor,
+ jwt: UntrustedToken,
+ project_id: ProjectId,
+ #[pallet::compact] ct_amount: Balance,
+ mode: ParticipationMode,
+ funding_asset: AcceptedFundingAsset,
+ receiving_account: Junction,
+ signature_bytes: [u8; 65],
+ ) -> DispatchResultWithPostInfo {
+ let (bidder, did, investor_type, whitelisted_policy) =
+ T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
+
+ Self::verify_receiving_account_signature(&bidder, project_id, &receiving_account, signature_bytes)?;
+
+ let params = DoBidParams:: {
+ bidder,
+ project_id,
+ ct_amount,
+ mode,
+ funding_asset,
+ did,
+ investor_type,
+ whitelisted_policy,
+ receiving_account,
+ };
+
Self::do_bid(params)
}
@@ -825,6 +901,41 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
let (contributor, did, investor_type, whitelisted_policy) =
T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
+ let receiving_account =
+ Junction::AccountId32 { network: None, id: T::AccountId32Conversion::convert(contributor.clone()) };
+ let params = DoContributeParams:: {
+ contributor,
+ project_id,
+ ct_amount,
+ mode,
+ funding_asset,
+ did,
+ investor_type,
+ whitelisted_policy,
+ receiving_account,
+ };
+ Self::do_contribute(params)
+ }
+
+ #[pallet::call_index(90)]
+ #[pallet::weight(
+ WeightInfoOf::::contribute(T::MaxContributionsPerUser::get())
+ )]
+ pub fn contribute_with_receiving_account(
+ origin: OriginFor,
+ jwt: UntrustedToken,
+ project_id: ProjectId,
+ #[pallet::compact] ct_amount: Balance,
+ mode: ParticipationMode,
+ funding_asset: AcceptedFundingAsset,
+ receiving_account: Junction,
+ signature_bytes: [u8; 65],
+ ) -> DispatchResultWithPostInfo {
+ let (contributor, did, investor_type, whitelisted_policy) =
+ T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?;
+
+ Self::verify_receiving_account_signature(&contributor, project_id, &receiving_account, signature_bytes)?;
+
let params = DoContributeParams:: {
contributor,
project_id,
@@ -834,6 +945,7 @@ pub mod pallet {
did,
investor_type,
whitelisted_policy,
+ receiving_account,
};
Self::do_contribute(params)
}
diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs
index fb54f3ee9..41b4a61a0 100644
--- a/pallets/funding/src/mock.rs
+++ b/pallets/funding/src/mock.rs
@@ -23,6 +23,7 @@ use crate as pallet_funding;
use crate::runtime_api::{
ExtrinsicHelpers, Leaderboards, ProjectInformation, ProjectParticipationIds, UserInformation,
};
+use alloc::string::String;
use core::ops::RangeInclusive;
use frame_support::{
construct_runtime, derive_impl,
@@ -37,9 +38,12 @@ use polimec_common::{credentials::EnsureInvestor, ProvideAssetPrice, USD_UNIT};
use polimec_common_test_utils::DummyXcmSender;
use polkadot_parachain_primitives::primitives::Sibling;
use sp_arithmetic::{Perbill, Percent};
-use sp_core::{ConstU8, H256};
+use sp_core::{
+ crypto::{Ss58AddressFormat, Ss58Codec},
+ ConstU8, H256,
+};
use sp_runtime::{
- traits::{BlakeTwo256, ConvertBack, ConvertInto, Get, IdentityLookup, TryConvert},
+ traits::{BlakeTwo256, Convert, ConvertBack, ConvertInto, Get, IdentityLookup, TryConvert},
BuildStorage, Perquintill,
};
use sp_std::collections::btree_map::BTreeMap;
@@ -245,17 +249,8 @@ impl system::Config for TestRuntime {
type Hashing = BlakeTwo256;
type Lookup = IdentityLookup;
type MaxConsumers = frame_support::traits::ConstU32<16>;
- type Nonce = u64;
- type OnKilledAccount = ();
- type OnNewAccount = ();
- type OnSetCode = ();
type PalletInfo = PalletInfo;
- type RuntimeCall = RuntimeCall;
- type RuntimeEvent = RuntimeEvent;
- type RuntimeOrigin = RuntimeOrigin;
- type SS58Prefix = ConstU16<42>;
- type SystemWeightInfo = ();
- type Version = ();
+ type SS58Prefix = ConstU16<41>;
}
parameter_types! {
@@ -380,6 +375,15 @@ impl ProvideAssetPrice for ConstPriceProvider {
}
}
+pub struct SS58Converter;
+impl Convert for SS58Converter {
+ fn convert(account: AccountId) -> String {
+ let account_bytes = DummyConverter::convert(account);
+ let account_id_32 = sp_runtime::AccountId32::new(account_bytes);
+ account_id_32.to_ss58check_with_version(Ss58AddressFormat::from(41u16))
+ }
+}
+
impl ConstPriceProvider {
pub fn set_price(asset_id: AssetId, price: Price) {
PRICE_MAP.with(|price_map| {
@@ -425,6 +429,7 @@ impl Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type RuntimeHoldReason = RuntimeHoldReason;
type RuntimeOrigin = RuntimeOrigin;
+ type SS58Conversion = SS58Converter;
#[cfg(feature = "runtime-benchmarks")]
type SetPrices = ();
type StringLimit = ConstU32<64>;
@@ -591,6 +596,10 @@ sp_api::mock_impl_runtime_apis! {
PolimecFunding::get_funding_asset_min_max_amounts(project_id, did, funding_asset, investor_type)
}
+ fn get_message_to_sign_by_receiving_account(project_id: ProjectId, polimec_account: AccountId) -> Option {
+ PolimecFunding::get_message_to_sign_by_receiving_account(project_id, polimec_account)
+ }
+
}
}
diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs
index bfcc9752d..ffae00213 100644
--- a/pallets/funding/src/runtime_api.rs
+++ b/pallets/funding/src/runtime_api.rs
@@ -1,7 +1,7 @@
use crate::traits::BondingRequirementCalculation;
#[allow(clippy::wildcard_imports)]
use crate::*;
-use alloc::collections::BTreeMap;
+use alloc::{collections::BTreeMap, string::String};
use frame_support::traits::fungibles::{Inspect, InspectEnumerable};
use itertools::Itertools;
use parity_scale_codec::{Decode, Encode};
@@ -9,7 +9,6 @@ use polimec_common::{credentials::InvestorType, ProvideAssetPrice, USD_DECIMALS}
use scale_info::TypeInfo;
use sp_core::Get;
use sp_runtime::traits::Zero;
-
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)]
pub struct ProjectParticipationIds {
account: AccountIdOf,
@@ -55,7 +54,7 @@ sp_api::decl_runtime_apis! {
fn projects_by_did(did: Did) -> Vec;
}
- #[api_version(3)]
+ #[api_version(4)]
pub trait ExtrinsicHelpers {
/// Get the current price of a contribution token (either current bucket in the auction, or WAP in contribution phase),
/// and calculate the amount of tokens that can be bought with the given amount USDT/USDC/DOT.
@@ -74,6 +73,10 @@ sp_api::decl_runtime_apis! {
/// Gets the minimum and maximum amount of FundingAsset a user can input in the UI.
fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)>;
+ /// Gets the hex encoded bytes of the message needed to be signed by the receiving account to participate in the project.
+ /// The message will first be prefixed with a string depending on the blockchain, hashed, and then signed.
+ fn get_message_to_sign_by_receiving_account(project_id: ProjectId, polimec_account: AccountIdOf) -> Option;
+
}
}
@@ -350,6 +353,13 @@ impl Pallet {
Some((funding_asset_min_ticket, funding_asset_max_ticket))
}
+ pub fn get_message_to_sign_by_receiving_account(
+ project_id: ProjectId,
+ polimec_account: AccountIdOf,
+ ) -> Option {
+ Pallet::::get_message_to_sign(polimec_account, project_id)
+ }
+
pub fn all_project_participations_by_did(project_id: ProjectId, did: Did) -> Vec> {
let evaluations = Evaluations::::iter_prefix((project_id,))
.filter(|((_account_id, _evaluation_id), evaluation)| evaluation.did == did)
diff --git a/pallets/funding/src/tests/1_application.rs b/pallets/funding/src/tests/1_application.rs
index 73b36cbc7..ffdc19d7e 100644
--- a/pallets/funding/src/tests/1_application.rs
+++ b/pallets/funding/src/tests/1_application.rs
@@ -932,6 +932,7 @@ mod edit_project_extrinsic {
funding_destination_account: ISSUER_2,
policy_ipfs_cid: Some(new_policy_hash),
+ participants_account_type: ParticipantsAccountType::Polkadot,
};
// No fields changed
diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs
index 8895e4650..444962df4 100644
--- a/pallets/funding/src/tests/2_evaluation.rs
+++ b/pallets/funding/src/tests/2_evaluation.rs
@@ -586,6 +586,10 @@ mod evaluate_extrinsic {
early_usd_amount: evaluation.usd_amount,
late_usd_amount: 0,
when: 1,
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(EVALUATOR_1),
+ },
};
assert_eq!(stored_evaluation, &expected_evaluation_item);
});
@@ -871,6 +875,10 @@ mod evaluate_extrinsic {
500 * USD_UNIT,
generate_did_from_account(ISSUER_1),
project_metadata.clone().policy_ipfs_cid.unwrap(),
+ Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(ISSUER_1 + 1)
+ },
)),
Error::::ParticipationToOwnProject
);
diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs
index 62e4707b4..889e6cb7d 100644
--- a/pallets/funding/src/tests/3_auction.rs
+++ b/pallets/funding/src/tests/3_auction.rs
@@ -1,6 +1,7 @@
use super::*;
use frame_support::traits::{fungible::InspectFreeze, fungibles::metadata::Inspect};
use sp_core::bounded_vec;
+use sp_runtime::traits::Convert;
use std::collections::HashSet;
#[cfg(test)]
@@ -324,6 +325,10 @@ mod round_flow {
did,
investor_type,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(BIDDER_1)
+ },
}),
Error::::IncorrectRound
);
@@ -1291,6 +1296,10 @@ mod bid_extrinsic {
did,
investor_type,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(BIDDER_2)
+ },
}),
Error::::IncorrectRound
);
@@ -1548,6 +1557,10 @@ mod bid_extrinsic {
did: generate_did_from_account(BIDDER_1),
investor_type: InvestorType::Professional,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(BIDDER_1)
+ },
}),
Error::::TooLow
);
@@ -1564,6 +1577,10 @@ mod bid_extrinsic {
did: generate_did_from_account(BIDDER_1),
investor_type: InvestorType::Institutional,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(BIDDER_2)
+ },
}),
Error::::TooLow
);
@@ -1631,6 +1648,10 @@ mod bid_extrinsic {
did: generate_did_from_account(BIDDER_1),
investor_type: InvestorType::Professional,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(BIDDER_2)
+ },
}));
});
let smallest_ct_amount_at_20k_usd = bucket_increase_price
@@ -1650,6 +1671,10 @@ mod bid_extrinsic {
did: generate_did_from_account(BIDDER_1),
investor_type: InvestorType::Institutional,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(BIDDER_3)
+ },
}));
});
}
@@ -1801,6 +1826,10 @@ mod bid_extrinsic {
did: generate_did_from_account(ISSUER_1),
investor_type: InvestorType::Professional,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(ISSUER_1)
+ },
})),
Error::::ParticipationToOwnProject
);
@@ -1832,6 +1861,10 @@ mod bid_extrinsic {
did,
investor_type,
whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(bids[0].bidder),
+ },
})
});
frame_support::assert_err!(outcome, Error::::FundingAssetNotAccepted);
@@ -1951,6 +1984,7 @@ mod end_auction_extrinsic {
participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(),
funding_destination_account: ISSUER_1,
policy_ipfs_cid: Some(metadata_hash),
+ participants_account_type: ParticipantsAccountType::Polkadot,
};
// overfund with plmc
diff --git a/pallets/funding/src/tests/4_contribution.rs b/pallets/funding/src/tests/4_contribution.rs
index dafb7aec3..8c17ba416 100644
--- a/pallets/funding/src/tests/4_contribution.rs
+++ b/pallets/funding/src/tests/4_contribution.rs
@@ -1449,6 +1449,10 @@ mod contribute_extrinsic {
did: generate_did_from_account(ISSUER_1),
investor_type: InvestorType::Institutional,
whitelisted_policy: project_metadata.policy_ipfs_cid.unwrap(),
+ receiving_account: Junction::AccountId32 {
+ network: None,
+ id: ::AccountId32Conversion::convert(ISSUER_1)
+ },
})),
Error::::ParticipationToOwnProject
);
diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs
index 3c3e5622f..6536016bf 100644
--- a/pallets/funding/src/tests/misc.rs
+++ b/pallets/funding/src/tests/misc.rs
@@ -4,6 +4,7 @@ use super::*;
mod helper_functions {
use super::*;
use polimec_common::USD_DECIMALS;
+ use sp_core::{ecdsa, hexdisplay::AsBytesRef, keccak_256, sr25519, Pair};
#[test]
fn test_usd_price_decimal_aware() {
@@ -189,6 +190,7 @@ mod helper_functions {
participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(),
funding_destination_account: ISSUER_1,
policy_ipfs_cid: Some(ipfs_hash()),
+ participants_account_type: ParticipantsAccountType::Polkadot,
};
let project_id = inst.create_community_contributing_project(
diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs
index 8d23cfa2b..ee13c0ff3 100644
--- a/pallets/funding/src/tests/mod.rs
+++ b/pallets/funding/src/tests/mod.rs
@@ -15,7 +15,7 @@ use parachains_common::DAYS;
use polimec_common::{ProvideAssetPrice, ReleaseSchedule, USD_DECIMALS, USD_UNIT};
use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid};
use sp_arithmetic::{traits::Zero, Percent, Perquintill};
-use sp_runtime::TokenError;
+use sp_runtime::{traits::Convert, TokenError};
use sp_std::cell::RefCell;
use std::iter::zip;
use ParticipationMode::{Classic, OTM};
@@ -107,6 +107,7 @@ pub mod defaults {
participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(),
funding_destination_account: issuer,
policy_ipfs_cid: Some(metadata_hash),
+ participants_account_type: ParticipantsAccountType::Polkadot,
}
}
@@ -142,6 +143,7 @@ pub mod defaults {
participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(),
funding_destination_account: ISSUER_1,
policy_ipfs_cid: Some(metadata_hash),
+ participants_account_type: ParticipantsAccountType::Polkadot,
}
}
diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs
index 135683f8b..a65d15972 100644
--- a/pallets/funding/src/tests/runtime_api.rs
+++ b/pallets/funding/src/tests/runtime_api.rs
@@ -2,7 +2,6 @@ use super::*;
use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation};
use frame_support::traits::fungibles::{metadata::Inspect, Mutate};
use sp_runtime::bounded_vec;
-
#[test]
fn top_evaluations() {
let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
@@ -624,6 +623,25 @@ fn funding_asset_to_ct_amount_otm() {
});
}
+#[test]
+fn get_message_to_sign_by_receiving_account() {
+ let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
+ let _project_id_0 = inst.create_new_project(default_project_metadata(ISSUER_1), ISSUER_1, None);
+ let _project_id_1 = inst.create_new_project(default_project_metadata(ISSUER_2), ISSUER_2, None);
+ let project_id_2 = inst.create_new_project(default_project_metadata(ISSUER_3), ISSUER_3, None);
+ let block_hash = inst.execute(|| System::block_hash(System::block_number()));
+ let message = inst.execute(|| {
+ TestRuntime::get_message_to_sign_by_receiving_account(&TestRuntime, block_hash, project_id_2, BUYER_1)
+ .unwrap()
+ .unwrap()
+ });
+
+ const EXPECTED_MESSAGE: &str =
+ "polimec account: 57CoZYedYQwJMnCivQ7FnjCr4dfF912XuvgjUaKUzWSvvEF5 - project id: 2 - nonce: 0";
+
+ assert_eq!(&message, EXPECTED_MESSAGE);
+}
+
#[test]
fn get_next_vesting_schedule_merge_candidates() {
let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext())));
diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs
index ee3573bf7..55976cde2 100644
--- a/pallets/funding/src/types.rs
+++ b/pallets/funding/src/types.rs
@@ -155,6 +155,7 @@ pub mod storage {
#[allow(clippy::wildcard_imports)]
use super::*;
use crate::Balance;
+ use xcm::v4::Junction;
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo, Serialize, Deserialize)]
pub struct ProjectMetadata {
@@ -180,6 +181,7 @@ pub mod storage {
pub funding_destination_account: AccountId,
/// Additional metadata
pub policy_ipfs_cid: Option,
+ pub participants_account_type: ParticipantsAccountType,
}
impl ProjectMetadata {
@@ -342,6 +344,7 @@ pub mod storage {
pub early_usd_amount: Balance,
pub late_usd_amount: Balance,
pub when: BlockNumber,
+ pub receiving_account: Junction,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
@@ -359,6 +362,7 @@ pub mod storage {
pub mode: ParticipationMode,
pub plmc_bond: Balance,
pub when: BlockNumber,
+ pub receiving_account: Junction,
}
impl
BidInfo
@@ -404,6 +408,7 @@ pub mod storage {
pub funding_asset_amount: Balance,
pub plmc_bond: Balance,
pub when: BlockNumber,
+ pub receiving_account: Junction,
}
/// Represents a bucket that holds a specific amount of tokens at a given price.
@@ -484,7 +489,7 @@ pub mod inner {
use super::*;
use crate::Balance;
use variant_count::VariantCount;
- use xcm::v4::QueryId;
+ use xcm::v4::{Junction, NetworkId, QueryId};
pub enum MetadataError {
/// The minimum price per token is too low.
@@ -819,6 +824,26 @@ pub mod inner {
}
}
}
+
+ #[derive(
+ Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, Serialize, Deserialize,
+ )]
+ pub enum ParticipantsAccountType {
+ Polkadot,
+ Ethereum,
+ }
+ impl ParticipantsAccountType {
+ pub fn junction_is_supported(&self, junction: &Junction) -> bool {
+ match self {
+ // This project expects users to submit a 32 byte account, and sign it with SR25519 crypto
+ ParticipantsAccountType::Polkadot =>
+ matches!(junction, Junction::AccountId32 { network, .. } if network.is_none()),
+ // This project expects users to submit a 20 byte account, and sign it with ECDSA secp256k1 crypto
+ ParticipantsAccountType::Ethereum =>
+ matches!(junction, Junction::AccountKey20 { network, .. } if network == &Some(NetworkId::Ethereum {chain_id: 1})),
+ }
+ }
+ }
}
pub mod extrinsic {
@@ -828,6 +853,7 @@ pub mod extrinsic {
};
use frame_system::pallet_prelude::BlockNumberFor;
use polimec_common::credentials::{Cid, Did, InvestorType};
+ use xcm::v4::Junction;
pub struct DoBidParams {
pub bidder: AccountIdOf,
@@ -838,6 +864,7 @@ pub mod extrinsic {
pub did: Did,
pub investor_type: InvestorType,
pub whitelisted_policy: Cid,
+ pub receiving_account: Junction,
}
pub struct DoPerformBidParams {
@@ -853,6 +880,7 @@ pub mod extrinsic {
pub metadata_ticket_size_bounds: TicketSize,
pub total_bids_by_bidder: u32,
pub total_bids_for_project: u32,
+ pub receiving_account: Junction,
}
pub struct DoContributeParams {
@@ -864,6 +892,7 @@ pub mod extrinsic {
pub did: Did,
pub investor_type: InvestorType,
pub whitelisted_policy: Cid,
+ pub receiving_account: Junction,
}
pub struct DoPerformContributionParams<'a, T: Config> {
@@ -876,6 +905,7 @@ pub mod extrinsic {
pub investor_type: InvestorType,
pub did: Did,
pub whitelisted_policy: Cid,
+ pub receiving_account: Junction,
}
pub struct BidRefund {
diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs
index 5ec98261f..b169358d8 100644
--- a/runtimes/polimec/src/lib.rs
+++ b/runtimes/polimec/src/lib.rs
@@ -20,7 +20,7 @@
// Make the WASM binary available.
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
-
+extern crate alloc;
use assets_common::fungible_conversion::{convert, convert_balance};
use core::ops::RangeInclusive;
use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases;
@@ -101,10 +101,11 @@ pub use sp_runtime::{MultiAddress, Perbill, Permill};
#[cfg(feature = "std")]
use sp_version::NativeVersion;
+use alloc::string::String;
+use sp_core::crypto::Ss58Codec;
#[cfg(any(feature = "std", test))]
pub use sp_runtime::BuildStorage;
use xcm::VersionedAssetId;
-
#[cfg(feature = "runtime-benchmarks")]
mod benchmark_helpers;
mod custom_migrations;
@@ -1044,6 +1045,12 @@ impl ConvertBack for ConvertSelf {
bytes.into()
}
}
+pub struct SS58Converter;
+impl Convert for SS58Converter {
+ fn convert(account: AccountId) -> String {
+ account.to_ss58check_with_version(SS58Prefix::get().into())
+ }
+}
impl pallet_funding::Config for Runtime {
type AccountId32Conversion = ConvertSelf;
@@ -1084,6 +1091,7 @@ impl pallet_funding::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeHoldReason = RuntimeHoldReason;
type RuntimeOrigin = RuntimeOrigin;
+ type SS58Conversion = SS58Converter;
#[cfg(feature = "runtime-benchmarks")]
type SetPrices = benchmark_helpers::SetOraclePrices;
type StringLimit = ConstU32<64>;
@@ -1549,6 +1557,9 @@ impl_runtime_apis! {
fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)> {
Funding::get_funding_asset_min_max_amounts(project_id, did, funding_asset, investor_type)
}
+ fn get_message_to_sign_by_receiving_account(project_id: ProjectId, polimec_account: AccountId) -> Option {
+ Funding::get_message_to_sign_by_receiving_account(project_id, polimec_account)
+ }
}
#[cfg(feature = "try-runtime")]