Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ece085b
Require a VenueId when validating receipts.
Neopallium May 23, 2025
b32fdc1
Fix offchain receipts in STO pallet.
Neopallium May 23, 2025
6db45fb
Add missing event.
Neopallium May 26, 2025
fbe735a
Fix issue with portfolio affirmations.
Neopallium May 26, 2025
5cba2cb
Add benchmarks.
Neopallium May 26, 2025
fa4d3c1
Add missing AssetId to event and storage.
Neopallium May 28, 2025
1bbf7d2
Change invest_with_receipt to invest_v2.
Neopallium May 28, 2025
23c255a
Break old STO API.
Neopallium May 28, 2025
d16eea3
Move AssetHelper into shared crate code.
Neopallium May 28, 2025
6496ae8
Add STO test for OnChain funding method.
Neopallium May 28, 2025
bce0739
Add STO test for OffChain funding method.
Neopallium May 28, 2025
56a31c7
Fix integration tests for previous release.
Neopallium May 28, 2025
33f8e98
Fix ci pipeline chain upgrade build.
Neopallium May 28, 2025
ee36c3b
Use more sensible values in the STO testing.
Neopallium May 28, 2025
7cc20ac
Merge branch 'develop' into fix_sto_receipts
Neopallium May 29, 2025
f3140b3
Merge branch 'develop' into fix_sto_receipts
Neopallium May 30, 2025
c236cff
Merge branch 'develop' into fix_sto_receipts
Neopallium Jun 2, 2025
58055ff
Update v7.3 metadata snapshots.
Neopallium May 30, 2025
e890aa3
Fix comment.
Neopallium Jun 2, 2025
bf63337
Remove debug logging.
Neopallium Jun 2, 2025
dd2b8a1
Improve STO events and docs.
Neopallium Jun 3, 2025
3d9fd11
Update v7.3.0 metadata.
Neopallium Jun 3, 2025
84462be
cargo fmt.
Neopallium Jun 3, 2025
a4ff1a2
Fix event in STO integration tests.
Neopallium Jun 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions integration/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ sp-core = "36.1"
sp-runtime = "41.1"
sp-keyring = "41.0"
sp-weights = "31.1"
codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive", "max-encoded-len"] }

polymesh-api = { version = "3.11.0", features = ["download_metadata"] }
polymesh-api-client-extras = { version = "3.6.0" }
Expand Down
218 changes: 218 additions & 0 deletions integration/src/asset_helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use anyhow::Result;
use std::collections::BTreeSet;

use polymesh_api::types::polymesh_primitives::{
asset::{AssetName, AssetType},
identity_id::{PortfolioId, PortfolioKind},
settlement::{Leg, SettlementType, VenueDetails, VenueId, VenueType},
};

use crate::*;

/// Asset Helper.
pub struct AssetHelper {
pub api: Api,
pub asset_id: AssetId,
pub issuer: User,
pub issuer_venue_id: VenueId,
pub issuer_did: IdentityId,
}

impl AssetHelper {
/// Create a new asset, mint some tokens, and pause compliance rules.
pub async fn new(
api: &Api,
issuer: &mut User,
name: &str,
mint: u128,
signers: Vec<AccountId>,
) -> Result<Self> {
// Create a new venue.
let mut venue_res = api
.call()
.settlement()
.create_venue(
VenueDetails(format!("Venue for {name}").into()),
signers,
VenueType::Other,
)?
.submit_and_watch(issuer)
.await?;

// Create a new asset.
let mut asset_res = api
.call()
.asset()
.create_asset(
AssetName(name.into()),
true, // Divisible token.
AssetType::EquityCommon,
vec![],
None,
)?
.submit_and_watch(issuer)
.await?;

// Get the asset ID from the response.
let asset_id = get_asset_id(&mut asset_res)
.await?
.expect("Asset ID not found");

// Mint some tokens.
let mut mint_res = api
.call()
.asset()
.issue(asset_id, mint, PortfolioKind::Default)?
.submit_and_watch(issuer)
.await?;

// Pause compliance rules to allow transfers.
let mut pause_res = api
.call()
.compliance_manager()
.pause_asset_compliance(asset_id)?
.submit_and_watch(issuer)
.await?;

// Wait for mint and pause to complete.
mint_res.ok().await?;
pause_res.ok().await?;

// Get the venue ID from the response.
let issuer_venue_id = get_venue_id(&mut venue_res)
.await?
.expect("Venue ID not found");

Ok(Self {
api: api.clone(),
asset_id,
issuer: issuer.clone(),
issuer_venue_id,
issuer_did: issuer.did.expect("Issuer DID"),
})
}

/// Mint some more tokens.
pub async fn mint(&mut self, amount: u128) -> Result<()> {
// Mint some tokens.
let mut mint_res = self
.api
.call()
.asset()
.issue(self.asset_id, amount, PortfolioKind::Default)?
.submit_and_watch(&mut self.issuer)
.await?;

// Wait for mint to complete.
mint_res.ok().await?;

Ok(())
}

/// Fund the investors portfolio with some tokens.
pub async fn fund_investors(
&mut self,
investors: &mut [&mut User],
amount: u128,
) -> Result<()> {
// Make sure the asset issuer has enough tokens.
let total = amount * investors.len() as u128;
self.mint(total).await?;

// Issuer portfolios.
let issuer_portfolio = PortfolioId {
did: self.issuer_did,
kind: PortfolioKind::Default,
};
let issuer_portfolios = [issuer_portfolio].into_iter().collect::<BTreeSet<_>>();

let mut pending_settlements = Vec::new();
for batch in investors.chunks_mut(10) {
let mut legs = Vec::new();
for investor in batch.iter() {
// Get the investor DID.
let investor_did = investor.did.expect("Investor DID");

// User portfolios.
let investor_portfolio = PortfolioId {
did: investor_did,
kind: PortfolioKind::Default,
};

// Create a simple Settlement to transfer tokens from the issuer to the investor.
legs.push(Leg::Fungible {
sender: issuer_portfolio,
receiver: investor_portfolio,
asset_id: self.asset_id,
amount,
});
}
let leg_count = legs.len() as u32;

// Create a simple Settlement to transfer tokens from the issuer to the investors.
let mut settlement_res = self
.api
.call()
.settlement()
.add_and_affirm_instruction(
Some(self.issuer_venue_id),
SettlementType::SettleManual(0),
None,
None,
legs.clone(),
issuer_portfolios.clone(),
None,
)?
.submit_and_watch(&mut self.issuer)
.await?;

// Get the settlement ID from the response.
let settlement_id = get_instruction_id(&mut settlement_res)
.await?
.expect("Settlement ID not found");

// The investors need to affirm the settlement.
let mut pending_affirms = Vec::new();
for investor in batch.iter_mut() {
let affirm_res = self
.api
.call()
.settlement()
.affirm_instruction(
settlement_id,
vec![PortfolioId {
did: investor.did.expect("Investor DID"),
kind: PortfolioKind::Default,
}]
.into_iter()
.collect(),
)?
.submit_and_watch(*investor)
.await?;
pending_affirms.push(affirm_res);
}

// Wait for investors affirmations to complete.
for mut affirm_res in pending_affirms {
affirm_res.ok().await?;
}

// Execute the settlement
let execute_res = self
.api
.call()
.settlement()
.execute_manual_instruction(settlement_id, None, leg_count, 0, 0, None)?
.submit_and_watch(&mut self.issuer)
.await?;
pending_settlements.push(execute_res);
}

// Wait for the settlements to complete.
for mut execute_res in pending_settlements {
execute_res.ok().await?;
}

Ok(())
}
}
21 changes: 21 additions & 0 deletions integration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ use std::collections::{BTreeMap, BTreeSet};
use polymesh_api::types::polymesh_primitives::{
identity_id::PortfolioId,
secondary_key::{ExtrinsicPermissions, PalletPermissions},
sto::FundraiserId,
subset::SubsetRestriction,
ExtrinsicName, PalletName,
};
use polymesh_api::*;

use anyhow::{anyhow, Result};

mod asset_helper;
pub use asset_helper::*;

pub async fn get_batch_results(res: &mut TransactionResults) -> Result<Vec<bool>> {
let events = res
.events()
Expand Down Expand Up @@ -271,6 +275,23 @@ pub async fn get_auth_id(res: &mut TransactionResults) -> Result<Option<u64>> {
Ok(None)
}

/// Get Fundraiser ID from the transaction results.
pub async fn get_fundraiser_id(
res: &mut TransactionResults,
) -> Result<Option<(AssetId, FundraiserId)>> {
if let Some(events) = res.events().await? {
for rec in &events.0 {
match &rec.event {
RuntimeEvent::Sto(StoEvent::FundraiserCreated(_, asset, fundraiser, ..)) => {
return Ok(Some((*asset, fundraiser.clone())));
}
_ => (),
}
}
}
Ok(None)
}

pub async fn get_contract_address(res: &mut TransactionResults) -> Result<Option<AccountId>> {
if let Some(events) = res.events().await? {
for rec in &events.0 {
Expand Down
Loading