mirror of
https://gitlab.com/pezkuwichain/pezkuwi-sdk.git
synced 2026-10-20 18:56:23 +00:00
sync: mirror deployed github repo (chains + sanitized bridge), keep GitLab CI
Make this monorepo's tracked tree identical to the deployed github repo (github.com/pezkuwichain/pezkuwi-DKS): all RC/AH/People runtimes and framework carry the mainnet fixes (incl. U128CurrencyToVote everywhere, so the staking election stall cannot recur), and tools/usdt-bridge matches the current, public-safe custody tooling (real config/deploy units gitignored, secret-file locations and server IPs stripped, sanitized *.example templates committed). Only exception to the mirror: GitLab-native CI is preserved (.gitlab-ci.yml and .gitlab/), to be rebuilt to match the github CI pipeline in a follow-up.
This commit is contained in:
1 parent
82543bdfab
commit
d285d59108
736 files changed
+15082
-16359
No files matched your search
@@ -77,7 +77,7 @@
|
||||
"name": "Pezkuwichain Asset Hub",
|
||||
"properties": {
|
||||
"tokenDecimals": 12,
|
||||
"tokenSymbol": "TYR"
|
||||
"tokenSymbol": "HEZ"
|
||||
},
|
||||
"protocolId": null,
|
||||
"telemetryEndpoints": null
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"properties": {
|
||||
"ss58Format": 42,
|
||||
"tokenDecimals": 12,
|
||||
"tokenSymbol": "TYR"
|
||||
"tokenSymbol": "HEZ"
|
||||
},
|
||||
"protocolId": null,
|
||||
"telemetryEndpoints": null
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"properties": {
|
||||
"ss58Format": 42,
|
||||
"tokenDecimals": 12,
|
||||
"tokenSymbol": "TYR"
|
||||
"tokenSymbol": "HEZ"
|
||||
},
|
||||
"protocolId": null,
|
||||
"telemetryEndpoints": null
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"properties": {
|
||||
"ss58Format": 42,
|
||||
"tokenDecimals": 12,
|
||||
"tokenSymbol": "TYR"
|
||||
"tokenSymbol": "HEZ"
|
||||
},
|
||||
"protocolId": null,
|
||||
"telemetryEndpoints": null
|
||||
|
||||
+2
-2
@@ -28,11 +28,11 @@ use pezkuwi_primitives::{AssignmentId, ValidatorId};
|
||||
use emulated_integration_tests_common::{
|
||||
accounts, build_genesis_storage, get_host_config, validators,
|
||||
};
|
||||
use pezkuwichain_runtime_constants::currency::UNITS as TYR;
|
||||
use pezkuwichain_runtime_constants::currency::UNITS as HEZ;
|
||||
use teyrchains_common::Balance;
|
||||
|
||||
pub const ED: Balance = pezkuwichain_runtime_constants::currency::EXISTENTIAL_DEPOSIT;
|
||||
const ENDOWMENT: u128 = 1_000_000 * TYR;
|
||||
const ENDOWMENT: u128 = 1_000_000 * HEZ;
|
||||
|
||||
fn session_keys(
|
||||
babe: BabeId,
|
||||
|
||||
+3
-3
@@ -21,10 +21,10 @@ use pezframe_support::{
|
||||
use pezkuwi_runtime_common::impls::VersionedLocatableAsset;
|
||||
use pezkuwichain_runtime_constants::currency::GRAND;
|
||||
|
||||
// Fund Treasury account on Asset Hub from Treasury account on Relay Chain with TYRs.
|
||||
// Fund Treasury account on Asset Hub from Treasury account on Relay Chain with HEZ.
|
||||
#[test]
|
||||
fn spend_roc_on_asset_hub() {
|
||||
// initial treasury balance on Asset Hub in TYRs.
|
||||
// initial treasury balance on Asset Hub in HEZ.
|
||||
let treasury_balance = 9_000 * GRAND;
|
||||
// the balance spend on Asset Hub.
|
||||
let treasury_spend_balance = 1_000 * GRAND;
|
||||
@@ -42,7 +42,7 @@ fn spend_roc_on_asset_hub() {
|
||||
type Balances = <Pezkuwichain as PezkuwichainPallet>::Balances;
|
||||
type Treasury = <Pezkuwichain as PezkuwichainPallet>::Treasury;
|
||||
|
||||
// Fund Treasury account on Asset Hub with TYRs.
|
||||
// Fund Treasury account on Asset Hub with HEZ.
|
||||
|
||||
let root = <Pezkuwichain as Chain>::RuntimeOrigin::root();
|
||||
let treasury_account = Treasury::account_id();
|
||||
|
||||
+6
-6
@@ -208,14 +208,14 @@ fn transfer_foreign_assets_from_asset_hub_to_para() {
|
||||
let native_asset_location = RelayLocation::get();
|
||||
let receiver = PenpalAReceiver::get();
|
||||
let assets_owner = PenpalAssetOwner::get();
|
||||
// Foreign asset used: bridged TYR
|
||||
// Foreign asset used: bridged HEZ
|
||||
let foreign_amount_to_send = ASSET_HUB_ZAGROS_ED * 10_000_000;
|
||||
let roc_at_zagros_teyrchains = Location::new(
|
||||
2,
|
||||
[Junction::GlobalConsensus(NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH))],
|
||||
);
|
||||
|
||||
// Configure destination chain to trust AH as reserve of TYR
|
||||
// Configure destination chain to trust AH as reserve of HEZ
|
||||
PenpalA::execute_with(|| {
|
||||
assert_ok!(<PenpalA as Chain>::System::set_storage(
|
||||
<PenpalA as Chain>::RuntimeOrigin::root(),
|
||||
@@ -339,14 +339,14 @@ fn transfer_foreign_assets_from_para_to_asset_hub() {
|
||||
let native_asset_location = RelayLocation::get();
|
||||
let assets_owner = PenpalAssetOwner::get();
|
||||
|
||||
// Foreign asset used: bridged TYR
|
||||
// Foreign asset used: bridged HEZ
|
||||
let foreign_amount_to_send = ASSET_HUB_ZAGROS_ED * 10_000_000;
|
||||
let roc_at_zagros_teyrchains = Location::new(
|
||||
2,
|
||||
[Junction::GlobalConsensus(NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH))],
|
||||
);
|
||||
|
||||
// Configure destination chain to trust AH as reserve of TYR
|
||||
// Configure destination chain to trust AH as reserve of HEZ
|
||||
PenpalA::execute_with(|| {
|
||||
assert_ok!(<PenpalA as Chain>::System::set_storage(
|
||||
<PenpalA as Chain>::RuntimeOrigin::root(),
|
||||
@@ -499,7 +499,7 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() {
|
||||
let sov_of_receiver_on_ah = AssetHubZagros::sovereign_account_id_of(receiver_as_seen_by_ah);
|
||||
let roc_to_send = ASSET_HUB_ZAGROS_ED * 10_000_000;
|
||||
|
||||
// Configure source and destination chains to trust AH as reserve of TYR
|
||||
// Configure source and destination chains to trust AH as reserve of HEZ
|
||||
PenpalA::execute_with(|| {
|
||||
assert_ok!(<PenpalA as Chain>::System::set_storage(
|
||||
<PenpalA as Chain>::RuntimeOrigin::root(),
|
||||
@@ -519,7 +519,7 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() {
|
||||
));
|
||||
});
|
||||
|
||||
// Register TYR as foreign asset and transfer it around the Zagros ecosystem
|
||||
// Register HEZ as foreign asset and transfer it around the Zagros ecosystem
|
||||
let roc_at_zagros_teyrchains = Location::new(
|
||||
2,
|
||||
[Junction::GlobalConsensus(NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH))],
|
||||
|
||||
+12
-12
@@ -109,7 +109,7 @@ fn send_assets_from_penpal_pezkuwichain_through_pezkuwichain_ah_to_zagros_ah(
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Test transfer of TYR from AssetHub Pezkuwichain to AssetHub Zagros.
|
||||
/// Test transfer of HEZ from AssetHub Pezkuwichain to AssetHub Zagros.
|
||||
fn send_roc_from_asset_hub_pezkuwichain_to_asset_hub_zagros() {
|
||||
let amount = ASSET_HUB_PEZKUWICHAIN_ED * 1_000_000;
|
||||
let sender = AssetHubPezkuwichainSender::get();
|
||||
@@ -131,7 +131,7 @@ fn send_roc_from_asset_hub_pezkuwichain_to_asset_hub_zagros() {
|
||||
let receiver_rocs_before =
|
||||
foreign_balance_on_ah_zagros(bridged_roc_at_asset_hub_zagros.clone(), &receiver);
|
||||
|
||||
// send TYRs, use them for fees
|
||||
// send HEZ, use them for fees
|
||||
send_assets_over_bridge(|| {
|
||||
let destination = asset_hub_zagros_location();
|
||||
let assets: Assets = (roc_at_asset_hub_pezkuwichain.clone(), amount).into();
|
||||
@@ -152,7 +152,7 @@ fn send_roc_from_asset_hub_pezkuwichain_to_asset_hub_zagros() {
|
||||
assert_expected_events!(
|
||||
AssetHubZagros,
|
||||
vec![
|
||||
// issue TYRs on AHW
|
||||
// issue HEZ on AHW
|
||||
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
|
||||
asset_id: *asset_id == roc,
|
||||
owner: owner == &receiver,
|
||||
@@ -171,11 +171,11 @@ fn send_roc_from_asset_hub_pezkuwichain_to_asset_hub_zagros() {
|
||||
let rocs_in_reserve_on_ahr_after =
|
||||
<AssetHubPezkuwichain as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
|
||||
|
||||
// Sender's TYR balance is reduced
|
||||
// Sender's HEZ balance is reduced
|
||||
assert!(sender_rocs_before > sender_rocs_after);
|
||||
// Receiver's TYR balance is increased
|
||||
// Receiver's HEZ balance is increased
|
||||
assert!(receiver_rocs_after > receiver_rocs_before);
|
||||
// Reserve TYR balance is increased by sent amount
|
||||
// Reserve HEZ balance is increased by sent amount
|
||||
assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount);
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ fn send_roc_from_asset_hub_pezkuwichain_to_asset_hub_zagros() {
|
||||
/// Send bridged assets "back" from AssetHub Pezkuwichain to AssetHub Zagros.
|
||||
///
|
||||
/// This mix of assets should cover the whole range:
|
||||
/// - bridged native assets: TYR,
|
||||
/// - bridged native assets: HEZ,
|
||||
/// - bridged trust-based assets: USDT (exists only on Zagros, Pezkuwichain gets it from Zagros over
|
||||
/// bridge),
|
||||
/// - bridged foreign asset / double-bridged asset (other bridge / Snowfork): wETH (bridged from
|
||||
@@ -412,7 +412,7 @@ fn send_rocs_from_penpal_pezkuwichain_through_asset_hub_pezkuwichain_to_asset_hu
|
||||
let receiver_rocs_before =
|
||||
foreign_balance_on_ah_zagros(roc_at_asset_hub_zagros.clone(), &receiver);
|
||||
|
||||
// Send TYRs over bridge
|
||||
// Send HEZ over bridge
|
||||
{
|
||||
let destination = asset_hub_zagros_location();
|
||||
let assets: Assets = (roc_at_pezkuwichain_teyrchains.clone(), amount).into();
|
||||
@@ -440,7 +440,7 @@ fn send_rocs_from_penpal_pezkuwichain_through_asset_hub_pezkuwichain_to_asset_hu
|
||||
assert_expected_events!(
|
||||
AssetHubZagros,
|
||||
vec![
|
||||
// issue TYRs on AHW
|
||||
// issue HEZ on AHW
|
||||
RuntimeEvent::ForeignAssets(pezpallet_assets::Event::Issued { asset_id, owner, .. }) => {
|
||||
asset_id: *asset_id == roc,
|
||||
owner: owner == &receiver,
|
||||
@@ -477,7 +477,7 @@ fn send_back_wnds_from_penpal_pezkuwichain_through_asset_hub_pezkuwichain_to_ass
|
||||
let sender = PenpalASender::get();
|
||||
let receiver = AssetHubZagrosReceiver::get();
|
||||
|
||||
// set up TYRs for transfer
|
||||
// set up HEZ for transfer
|
||||
let (roc_at_pezkuwichain_teyrchains, _) =
|
||||
set_up_rocs_for_penpal_pezkuwichain_through_ahr_to_ahw(&sender, amount);
|
||||
|
||||
@@ -528,7 +528,7 @@ fn send_back_wnds_from_penpal_pezkuwichain_through_asset_hub_pezkuwichain_to_ass
|
||||
});
|
||||
let receiver_wnds_before = <AssetHubZagros as Chain>::account_data_of(receiver.clone()).free;
|
||||
|
||||
// send ZGRs over the bridge, TYRs only used to pay fees on local AH, pay with ZGR on remote AH
|
||||
// send ZGRs over the bridge, HEZ only used to pay fees on local AH, pay with ZGR on remote AH
|
||||
{
|
||||
let final_destination = asset_hub_zagros_location();
|
||||
let intermediary_hop = PenpalA::sibling_location_of(AssetHubPezkuwichain::para_id());
|
||||
@@ -584,7 +584,7 @@ fn send_back_wnds_from_penpal_pezkuwichain_through_asset_hub_pezkuwichain_to_ass
|
||||
assert_expected_events!(
|
||||
AssetHubZagros,
|
||||
vec![
|
||||
// issue TYRs on AHW
|
||||
// issue HEZ on AHW
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Issued { .. }) => {},
|
||||
// message processed successfully
|
||||
RuntimeEvent::MessageQueue(
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ pub(crate) fn bridge_hub_zagros_location() -> Location {
|
||||
)
|
||||
}
|
||||
|
||||
// TYR and wTYR
|
||||
// HEZ and wTYR
|
||||
pub(crate) fn roc_at_ah_pezkuwichain() -> Location {
|
||||
Parent.into()
|
||||
}
|
||||
|
||||
+26
-26
@@ -288,7 +288,7 @@ fn send_wnds_usdt_and_weth_from_asset_hub_zagros_to_asset_hub_pezkuwichain() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Send bridged TYRs "back" from AssetHub Zagros to AssetHub Pezkuwichain.
|
||||
/// Send bridged HEZ "back" from AssetHub Zagros to AssetHub Pezkuwichain.
|
||||
fn send_back_rocs_from_asset_hub_zagros_to_asset_hub_pezkuwichain() {
|
||||
let prefund_amount = 10_000_000_000_000u128;
|
||||
let amount_to_send = ASSET_HUB_PEZKUWICHAIN_ED * 1_000;
|
||||
@@ -304,7 +304,7 @@ fn send_back_rocs_from_asset_hub_zagros_to_asset_hub_pezkuwichain() {
|
||||
prefund_accounts,
|
||||
);
|
||||
|
||||
// fund the AHW's SA on AHR with the TYR tokens held in reserve
|
||||
// fund the AHW's SA on AHR with the HEZ tokens held in reserve
|
||||
let sov_ahw_on_ahr =
|
||||
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
|
||||
ByGenesis(ZAGROS_GENESIS_HASH),
|
||||
@@ -322,7 +322,7 @@ fn send_back_rocs_from_asset_hub_zagros_to_asset_hub_pezkuwichain() {
|
||||
let receiver_rocs_before =
|
||||
<AssetHubPezkuwichain as Chain>::account_data_of(receiver.clone()).free;
|
||||
|
||||
// send back TYRs, use them for fees
|
||||
// send back HEZ, use them for fees
|
||||
send_assets_over_bridge(|| {
|
||||
let destination = asset_hub_pezkuwichain_location();
|
||||
let assets: Assets = (bridged_roc_at_asset_hub_zagros.clone(), amount_to_send).into();
|
||||
@@ -336,14 +336,14 @@ fn send_back_rocs_from_asset_hub_zagros_to_asset_hub_pezkuwichain() {
|
||||
assert_expected_events!(
|
||||
AssetHubPezkuwichain,
|
||||
vec![
|
||||
// TYR is withdrawn from AHW's SA on AHR
|
||||
// HEZ is withdrawn from AHW's SA on AHR
|
||||
RuntimeEvent::Balances(
|
||||
pezpallet_balances::Event::Burned { who, amount }
|
||||
) => {
|
||||
who: *who == sov_ahw_on_ahr,
|
||||
amount: *amount == amount_to_send,
|
||||
},
|
||||
// TYRs deposited to beneficiary
|
||||
// HEZ deposited to beneficiary
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
|
||||
who: *who == receiver,
|
||||
},
|
||||
@@ -754,7 +754,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
let (wnd_at_zagros_teyrchains, _) =
|
||||
set_up_wnds_for_penpal_zagros_through_ahw_to_ahr(&sender, amount);
|
||||
|
||||
// set up TYRs for transfer
|
||||
// set up HEZ for transfer
|
||||
let penpal_location = AssetHubZagros::sibling_location_of(PenpalB::para_id());
|
||||
let sov_penpal_on_ahw = AssetHubZagros::sovereign_account_id_of(penpal_location);
|
||||
let reserves = vec![(asset_hub_pezkuwichain_location(), false).into()];
|
||||
@@ -768,7 +768,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
ASSET_MIN_BALANCE,
|
||||
vec![(sender.clone(), amount * 2)],
|
||||
);
|
||||
// Configure source Penpal chain to trust local AH as reserve of bridged TYR
|
||||
// Configure source Penpal chain to trust local AH as reserve of bridged HEZ
|
||||
PenpalB::execute_with(|| {
|
||||
assert_ok!(<PenpalB as Chain>::System::set_storage(
|
||||
<PenpalB as Chain>::RuntimeOrigin::root(),
|
||||
@@ -779,7 +779,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
));
|
||||
});
|
||||
|
||||
// fund the AHW's SA on AHR with the TYR tokens held in reserve
|
||||
// fund the AHW's SA on AHR with the HEZ tokens held in reserve
|
||||
let sov_ahw_on_ahr =
|
||||
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
|
||||
ByGenesis(ZAGROS_GENESIS_HASH),
|
||||
@@ -795,7 +795,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
let receiver_rocs_before =
|
||||
<AssetHubPezkuwichain as Chain>::account_data_of(receiver.clone()).free;
|
||||
|
||||
// send TYRs over the bridge, ZGRs only used to pay fees on local AH, pay with TYR on remote AH
|
||||
// send HEZ over the bridge, ZGRs only used to pay fees on local AH, pay with HEZ on remote AH
|
||||
{
|
||||
let final_destination = asset_hub_pezkuwichain_location();
|
||||
let intermediary_hop = PenpalB::sibling_location_of(AssetHubZagros::para_id());
|
||||
@@ -803,10 +803,10 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
|
||||
// what happens at final destination
|
||||
let beneficiary = AccountId32Junction { network: None, id: receiver.clone().into() }.into();
|
||||
// use TYR as fees on the final destination (AHW)
|
||||
// use HEZ as fees on the final destination (AHW)
|
||||
let remote_fees: Asset = (roc_at_zagros_teyrchains.clone(), amount).into();
|
||||
let remote_fees = remote_fees.reanchored(&final_destination, &context).unwrap();
|
||||
// buy execution using TYRs, then deposit all remaining TYRs
|
||||
// buy execution using HEZ, then deposit all remaining HEZ
|
||||
let xcm_on_final_dest = Xcm::<()>(vec![
|
||||
BuyExecution { fees: remote_fees, weight_limit: WeightLimit::Unlimited },
|
||||
DepositAsset { assets: Wild(AllCounted(1)), beneficiary },
|
||||
@@ -816,13 +816,13 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
// reanchor final dest (Asset Hub Pezkuwichain) to the view of hop (Asset Hub Zagros)
|
||||
let mut final_destination = final_destination.clone();
|
||||
final_destination.reanchor(&intermediary_hop, &context).unwrap();
|
||||
// reanchor TYRs to the view of hop (Asset Hub Zagros)
|
||||
// reanchor HEZ to the view of hop (Asset Hub Zagros)
|
||||
let asset: Asset = (roc_at_zagros_teyrchains.clone(), amount).into();
|
||||
let asset = asset.reanchored(&intermediary_hop, &context).unwrap();
|
||||
// on Asset Hub Zagros, forward a request to withdraw TYRs from reserve on Asset Hub
|
||||
// on Asset Hub Zagros, forward a request to withdraw HEZ from reserve on Asset Hub
|
||||
// Pezkuwichain
|
||||
let xcm_on_hop = Xcm::<()>(vec![InitiateReserveWithdraw {
|
||||
assets: Definite(asset.into()), // TYRs
|
||||
assets: Definite(asset.into()), // HEZ
|
||||
reserve: final_destination, // AHR
|
||||
xcm: xcm_on_final_dest, // XCM to execute on AHR
|
||||
}]);
|
||||
@@ -883,7 +883,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
let sender = PenpalBSender::get();
|
||||
let receiver = PenpalAReceiver::get();
|
||||
|
||||
// set up TYRs for transfer
|
||||
// set up HEZ for transfer
|
||||
let penpal_location = AssetHubZagros::sibling_location_of(PenpalB::para_id());
|
||||
let sov_penpal_on_ahw = AssetHubZagros::sovereign_account_id_of(penpal_location);
|
||||
let reserves = vec![(asset_hub_pezkuwichain_location(), false).into()];
|
||||
@@ -903,7 +903,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
sender.clone(),
|
||||
amount,
|
||||
);
|
||||
// Create and fund bridged TYRs on Zagros Penpal
|
||||
// Create and fund bridged HEZ on Zagros Penpal
|
||||
PenpalB::force_create_foreign_asset(
|
||||
roc_at_zagros_teyrchains.clone(),
|
||||
asset_owner.clone(),
|
||||
@@ -911,7 +911,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
ASSET_MIN_BALANCE,
|
||||
vec![(sender.clone(), amount * 2)],
|
||||
);
|
||||
// Configure source Penpal chain to trust local AH as reserve of bridged TYR
|
||||
// Configure source Penpal chain to trust local AH as reserve of bridged HEZ
|
||||
PenpalB::execute_with(|| {
|
||||
assert_ok!(<PenpalB as Chain>::System::set_storage(
|
||||
<PenpalB as Chain>::RuntimeOrigin::root(),
|
||||
@@ -922,7 +922,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
));
|
||||
});
|
||||
|
||||
// fund the AHW's SA on AHR with the TYR tokens held in reserve
|
||||
// fund the AHW's SA on AHR with the HEZ tokens held in reserve
|
||||
let sov_ahw_on_ahr =
|
||||
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
|
||||
ByGenesis(ZAGROS_GENESIS_HASH),
|
||||
@@ -940,7 +940,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
<Assets as Inspect<_>>::balance(roc_at_pezkuwichain_teyrchains.clone(), &receiver)
|
||||
});
|
||||
|
||||
// send TYRs over the bridge, all fees paid with TYR along the way
|
||||
// send HEZ over the bridge, all fees paid with HEZ along the way
|
||||
{
|
||||
let local_asset_hub = PenpalB::sibling_location_of(AssetHubZagros::para_id());
|
||||
let beneficiary: Location =
|
||||
@@ -1026,7 +1026,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
assert_expected_events!(
|
||||
AssetHubPezkuwichain,
|
||||
vec![
|
||||
// burn TYRs from AHW's SA on AHR
|
||||
// burn HEZ from AHW's SA on AHR
|
||||
RuntimeEvent::Balances(
|
||||
pezpallet_balances::Event::Burned { who, .. }
|
||||
) => {
|
||||
@@ -1073,7 +1073,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
let receiver = PezkuwichainReceiver::get();
|
||||
let mut topic_id_tracker = TopicIdTracker::new();
|
||||
|
||||
// set up TYRs for transfer
|
||||
// set up HEZ for transfer
|
||||
let penpal_location = AssetHubZagros::sibling_location_of(PenpalB::para_id());
|
||||
let sov_penpal_on_ahw = AssetHubZagros::sovereign_account_id_of(penpal_location);
|
||||
let reserves = vec![(asset_hub_pezkuwichain_location(), false).into()];
|
||||
@@ -1093,7 +1093,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
sender.clone(),
|
||||
amount,
|
||||
);
|
||||
// Create and fund bridged TYRs on Zagros Penpal
|
||||
// Create and fund bridged HEZ on Zagros Penpal
|
||||
PenpalB::force_create_foreign_asset(
|
||||
roc_at_zagros_teyrchains.clone(),
|
||||
asset_owner.clone(),
|
||||
@@ -1101,7 +1101,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
ASSET_MIN_BALANCE,
|
||||
vec![(sender.clone(), amount * 2)],
|
||||
);
|
||||
// Configure source Penpal chain to trust local AH as reserve of bridged TYR
|
||||
// Configure source Penpal chain to trust local AH as reserve of bridged HEZ
|
||||
PenpalB::execute_with(|| {
|
||||
assert_ok!(<PenpalB as Chain>::System::set_storage(
|
||||
<PenpalB as Chain>::RuntimeOrigin::root(),
|
||||
@@ -1112,7 +1112,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
));
|
||||
});
|
||||
|
||||
// fund the AHW's SA on AHR with the TYR tokens held in reserve
|
||||
// fund the AHW's SA on AHR with the HEZ tokens held in reserve
|
||||
let sov_ahw_on_ahr =
|
||||
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
|
||||
ByGenesis(ZAGROS_GENESIS_HASH),
|
||||
@@ -1133,7 +1133,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
});
|
||||
let receiver_rocs_before = <Pezkuwichain as Chain>::account_data_of(receiver.clone()).free;
|
||||
|
||||
// send TYRs over the bridge, all fees paid with TYR along the way
|
||||
// send HEZ over the bridge, all fees paid with HEZ along the way
|
||||
{
|
||||
let local_asset_hub = PenpalB::sibling_location_of(AssetHubZagros::para_id());
|
||||
let beneficiary: Location =
|
||||
@@ -1227,7 +1227,7 @@ fn send_back_rocs_from_penpal_zagros_through_asset_hub_zagros_to_asset_hub_pezku
|
||||
assert_expected_events!(
|
||||
AssetHubPezkuwichain,
|
||||
vec![
|
||||
// burn TYRs from AHW's SA on AHR
|
||||
// burn HEZ from AHW's SA on AHR
|
||||
RuntimeEvent::Balances(
|
||||
pezpallet_balances::Event::Burned { who, .. }
|
||||
) => {
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ pub(crate) fn bridged_wnd_at_ah_pezkuwichain() -> Location {
|
||||
Location::new(2, [GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH))])
|
||||
}
|
||||
|
||||
// TYR and wTYR
|
||||
// HEZ and wTYR
|
||||
pub(crate) fn bridged_roc_at_ah_zagros() -> Location {
|
||||
Location::new(2, [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH))])
|
||||
}
|
||||
|
||||
+2
-2
@@ -434,7 +434,7 @@ pub fn erc20_token_location(token_id: H160) -> Location {
|
||||
)
|
||||
}
|
||||
|
||||
// TYR and wTYR
|
||||
// HEZ and wTYR
|
||||
pub(crate) fn roc_at_ah_pezkuwichain() -> Location {
|
||||
Parent.into()
|
||||
}
|
||||
@@ -507,7 +507,7 @@ pub fn register_roc_on_bh() {
|
||||
type RuntimeEvent = <BridgeHubZagros as Chain>::RuntimeEvent;
|
||||
type RuntimeOrigin = <BridgeHubZagros as Chain>::RuntimeOrigin;
|
||||
|
||||
// Register TYR on BH
|
||||
// Register HEZ on BH
|
||||
assert_ok!(<BridgeHubZagros as BridgeHubZagrosPallet>::EthereumSystem::register_token(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(VersionedLocation::from(bridged_roc_at_ah_zagros())),
|
||||
|
||||
+3
-3
@@ -472,7 +472,7 @@ fn send_roc_from_ethereum_to_pezkuwichain() {
|
||||
));
|
||||
});
|
||||
|
||||
// fund the AHW's SA on AHR with the TYR tokens held in reserve
|
||||
// fund the AHW's SA on AHR with the HEZ tokens held in reserve
|
||||
let sov_ahw_on_ahr =
|
||||
AssetHubPezkuwichain::sovereign_account_of_teyrchain_on_other_global_consensus(
|
||||
ByGenesis(ZAGROS_GENESIS_HASH),
|
||||
@@ -581,14 +581,14 @@ fn send_roc_from_ethereum_to_pezkuwichain() {
|
||||
assert_expected_events!(
|
||||
AssetHubPezkuwichain,
|
||||
vec![
|
||||
// TYR is withdrawn from AHW's SA on AHR
|
||||
// HEZ is withdrawn from AHW's SA on AHR
|
||||
RuntimeEvent::Balances(
|
||||
pezpallet_balances::Event::Burned { who, amount }
|
||||
) => {
|
||||
who: *who == sov_ahw_on_ahr,
|
||||
amount: *amount == TOKEN_AMOUNT,
|
||||
},
|
||||
// TYRs deposited to beneficiary
|
||||
// HEZ deposited to beneficiary
|
||||
RuntimeEvent::Balances(pezpallet_balances::Event::Minted { who, .. }) => {
|
||||
who: *who == AssetHubPezkuwichainReceiver::get(),
|
||||
},
|
||||
|
||||
+1
-1
@@ -185,7 +185,7 @@ fn send_roc_from_asset_hub_pezkuwichain_to_ethereum() {
|
||||
AssetHubPezkuwichain::force_xcm_version(asset_hub_zagros_global_location(), XCM_VERSION);
|
||||
BridgeHubPezkuwichain::force_xcm_version(bridge_hub_zagros_location(), XCM_VERSION);
|
||||
|
||||
// send TYRs, use them for fees
|
||||
// send HEZ, use them for fees
|
||||
let local_fee_asset: Asset = (roc_at_asset_hub_pezkuwichain.clone(), roc_fee_amount).into();
|
||||
let remote_fee_on_zagros: Asset =
|
||||
(roc_at_asset_hub_pezkuwichain.clone(), roc_fee_amount).into();
|
||||
|
||||
@@ -187,7 +187,14 @@ pub mod pezpallet {
|
||||
pub type IdentityHashes<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, H256>;
|
||||
|
||||
/// Reverse mapping: identity hash -> account ID (uniqueness enforcement)
|
||||
/// Ensures no two accounts can register with the same identity hash
|
||||
/// Ensures no two accounts can register with the same identity hash.
|
||||
///
|
||||
/// IMPORTANT: This map is populated at `apply_for_citizenship` time (not just
|
||||
/// at `confirm_citizenship` time) so that the hash is *reserved* for the whole
|
||||
/// lifetime of an in-flight application. This prevents two different accounts
|
||||
/// from concurrently applying with the same identity_hash (Sybil resistance) -
|
||||
/// without this early reservation, two pending applications could both later
|
||||
/// confirm and silently overwrite each other's reverse mapping.
|
||||
#[pezpallet::storage]
|
||||
#[pezpallet::getter(fn identity_hash_owner)]
|
||||
pub type IdentityHashToAccount<T: Config> = StorageMap<_, Blake2_128Concat, H256, T::AccountId>;
|
||||
@@ -353,6 +360,12 @@ pub mod pezpallet {
|
||||
let deposit = T::KycApplicationDeposit::get();
|
||||
T::Currency::reserve(&applicant, deposit)?;
|
||||
|
||||
// Reserve the identity hash immediately (not just at confirm time).
|
||||
// This closes the Sybil window where two different accounts could
|
||||
// both apply with the same identity_hash before either confirms -
|
||||
// see the doc comment on IdentityHashToAccount for details.
|
||||
IdentityHashToAccount::<T>::insert(identity_hash, &applicant);
|
||||
|
||||
// Store application (only hash, no personal data)
|
||||
let application =
|
||||
CitizenshipApplication { identity_hash, referrer: actual_referrer.clone() };
|
||||
@@ -398,6 +411,16 @@ pub mod pezpallet {
|
||||
// Only the referrer can approve
|
||||
ensure!(application.referrer == caller, Error::<T>::NotTheReferrer);
|
||||
|
||||
// Re-check the referrer's *current* KYC status. The referrer may have
|
||||
// been an Approved citizen when the application was created, but could
|
||||
// have been revoked by governance since then (e.g. for running a
|
||||
// referral-selling scheme). A revoked citizen must not retain the
|
||||
// ability to vouch new citizens into the system.
|
||||
ensure!(
|
||||
KycStatuses::<T>::get(&caller) == KycLevel::Approved,
|
||||
Error::<T>::ReferrerNotCitizen
|
||||
);
|
||||
|
||||
// Update status to ReferrerApproved
|
||||
KycStatuses::<T>::insert(&applicant, KycLevel::ReferrerApproved);
|
||||
|
||||
@@ -438,7 +461,10 @@ pub mod pezpallet {
|
||||
// Store identity hash permanently (for proof of citizenship)
|
||||
IdentityHashes::<T>::insert(&applicant, application.identity_hash);
|
||||
|
||||
// Store reverse mapping for uniqueness enforcement
|
||||
// Reverse mapping was already reserved for this applicant at apply time
|
||||
// (see apply_for_citizenship); re-affirm it here for good measure. This
|
||||
// is a no-op in the normal path since no other account can hold the
|
||||
// same identity_hash (the apply-time reservation guarantees that).
|
||||
IdentityHashToAccount::<T>::insert(application.identity_hash, &applicant);
|
||||
|
||||
// Store referrer permanently (for direct responsibility tracking)
|
||||
@@ -532,8 +558,11 @@ pub mod pezpallet {
|
||||
Error::<T>::CannotCancelInCurrentState
|
||||
);
|
||||
|
||||
// Remove application
|
||||
Applications::<T>::remove(&applicant);
|
||||
// Remove application and release the reserved identity hash so it can
|
||||
// be used again (by this account or another) in a future application.
|
||||
let application =
|
||||
Applications::<T>::take(&applicant).ok_or(Error::<T>::ApplicationNotFound)?;
|
||||
IdentityHashToAccount::<T>::remove(application.identity_hash);
|
||||
|
||||
// Reset status
|
||||
KycStatuses::<T>::insert(&applicant, KycLevel::NotStarted);
|
||||
|
||||
@@ -8,7 +8,7 @@ use pezframe_support::{assert_noop, assert_ok, traits::Currency};
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::DispatchError;
|
||||
|
||||
// Kolay erişim için paletimize bir takma ad veriyoruz.
|
||||
// We give our pallet an alias for easy access.
|
||||
type IdentityKycPallet = crate::Pezpallet<Test>;
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -142,21 +142,21 @@ where
|
||||
)
|
||||
}
|
||||
}
|
||||
// --- Dış Dünya İçin Arayüzler (Traits) ---
|
||||
// --- Interfaces for the Outside World (Traits) ---
|
||||
|
||||
/// Bir hesabın KYC durumunu sorgulamak için arayüz.
|
||||
/// Interface for querying the KYC status of an account.
|
||||
pub trait KycStatus<AccountId> {
|
||||
fn get_kyc_status(who: &AccountId) -> KycLevel;
|
||||
}
|
||||
|
||||
/// Bir hesabın kimlik bilgilerini sorgulamak için arayüz.
|
||||
/// Interface for querying the identity information of an account.
|
||||
pub trait IdentityInfoProvider<AccountId, MaxStringLength: Get<u32>> {
|
||||
fn get_identity_info(who: &AccountId) -> Option<IdentityInfo<MaxStringLength>>;
|
||||
}
|
||||
|
||||
/// KYC onaylandığında tetiklenecek eylemleri tanımlayan arayüz.
|
||||
/// Bu trait identity-kyc palletinde tanımlanır ve diğer palletler (örn. referral)
|
||||
/// tarafından implement edilir, böylece circular dependency oluşmaz.
|
||||
/// Interface defining the actions to be triggered when KYC is approved.
|
||||
/// This trait is defined in the identity-kyc pallet and is implemented by other
|
||||
/// pallets (e.g. referral), so that no circular dependency is created.
|
||||
///
|
||||
/// UPDATED (Gemini suggestion): Now includes referrer parameter to avoid
|
||||
/// data loss when identity-kyc and referral have separate storage.
|
||||
@@ -172,9 +172,9 @@ impl<AccountId> OnKycApproved<AccountId> for () {
|
||||
fn on_kyc_approved(_who: &AccountId, _referrer: &AccountId) {}
|
||||
}
|
||||
|
||||
/// Vatandaşlık NFT'si mintlemek için arayüz.
|
||||
/// Bu trait identity-kyc palletinde tanımlanır ve tiki pezpallet tarafından
|
||||
/// implement edilir, böylece circular dependency oluşmaz.
|
||||
/// Interface for minting a citizenship NFT.
|
||||
/// This trait is defined in the identity-kyc pallet and is implemented by the tiki
|
||||
/// pezpallet, so that no circular dependency is created.
|
||||
pub trait CitizenNftProvider<AccountId> {
|
||||
fn mint_citizen_nft(who: &AccountId) -> pezsp_runtime::DispatchResult;
|
||||
|
||||
|
||||
@@ -179,6 +179,36 @@ pub mod pezpallet {
|
||||
#[pezpallet::storage]
|
||||
pub type CleanupCursor<T: Config> = StorageValue<_, (u32, BoundedVec<u8, ConstU32<256>>)>;
|
||||
|
||||
/// FIFO queue of era indices awaiting cleanup, oldest first.
|
||||
///
|
||||
/// `on_initialize` enqueues the newly-expired era on every rotation instead of
|
||||
/// clobbering `CleanupCursor` directly. `on_idle` only pulls the next era off this
|
||||
/// queue once the current `CleanupCursor` entry (if any) has fully finished, so a
|
||||
/// slow/congested era's cleanup is never silently dropped by a later rotation.
|
||||
///
|
||||
/// Bounded to `MaxPendingCleanupEras` eras of backlog. If cleanup falls behind by
|
||||
/// more than that many eras (i.e. `on_idle` gets essentially no weight for that many
|
||||
/// consecutive era lengths), the oldest still-pending era is dropped to make room for
|
||||
/// the newest one -- an explicit, generous, and documented bound rather than the
|
||||
/// previous single-slot behaviour where any one stalled era was silently orphaned.
|
||||
#[pezpallet::storage]
|
||||
pub type PendingCleanupEras<T: Config> =
|
||||
StorageValue<_, BoundedVec<u32, ConstU32<64>>, ValueQuery>;
|
||||
|
||||
/// Per-account encryption-key registration counter for the current era.
|
||||
/// Reset (via era-prefix cleanup) when the era rotates. Used for rate limiting
|
||||
/// `register_encryption_key`, mirroring `SendCount` for `send_message`.
|
||||
#[pezpallet::storage]
|
||||
pub type KeyRegistrationCount<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
u32, // era_index
|
||||
Blake2_128Concat,
|
||||
T::AccountId, // who
|
||||
u32, // count
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
// ============= EVENTS =============
|
||||
|
||||
#[pezpallet::event]
|
||||
@@ -227,7 +257,8 @@ pub mod pezpallet {
|
||||
#[pezpallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
|
||||
/// Check for era rotation at the start of each block.
|
||||
/// Cost: 2 reads (CurrentEra + EraStartBlock), 0-2 writes on rotation.
|
||||
/// Cost: 2 reads (CurrentEra + EraStartBlock), plus on rotation an extra
|
||||
/// read+write of PendingCleanupEras to enqueue the newly-expired era.
|
||||
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
|
||||
let era_length = T::EraLength::get();
|
||||
let era_start = EraStartBlock::<T>::get();
|
||||
@@ -241,14 +272,24 @@ pub mod pezpallet {
|
||||
|
||||
Self::deposit_event(Event::EraRotated { old_era, new_era });
|
||||
|
||||
// Schedule cleanup: store the expired era index for on_idle
|
||||
// Schedule cleanup: enqueue the expired era index for on_idle instead of
|
||||
// overwriting CleanupCursor directly, so an in-progress cleanup (or a
|
||||
// backlog of not-yet-started ones) is never dropped by this rotation.
|
||||
if old_era > 0 {
|
||||
// Clean era before the one that just ended (2 eras of grace)
|
||||
let cleanup_era = old_era.saturating_sub(1);
|
||||
CleanupCursor::<T>::put((cleanup_era, BoundedVec::default()));
|
||||
let mut pending = PendingCleanupEras::<T>::get();
|
||||
if pending.try_push(cleanup_era).is_err() {
|
||||
// Backlog is at capacity (MaxPendingCleanupEras eras of unresolved
|
||||
// cleanup). Drop the oldest pending era to make room for the one
|
||||
// that just expired, rather than silently dropping the newest.
|
||||
pending.remove(0);
|
||||
let _ = pending.try_push(cleanup_era);
|
||||
}
|
||||
PendingCleanupEras::<T>::put(pending);
|
||||
}
|
||||
|
||||
T::DbWeight::get().reads_writes(2, 3)
|
||||
T::DbWeight::get().reads_writes(3, 4)
|
||||
} else {
|
||||
T::DbWeight::get().reads(2)
|
||||
}
|
||||
@@ -265,6 +306,20 @@ pub mod pezpallet {
|
||||
return meter.consumed();
|
||||
}
|
||||
|
||||
// If no cleanup is currently in progress, pull the next oldest era off the
|
||||
// pending queue so a backlog left behind by stalled/congested blocks is
|
||||
// eventually worked through instead of being silently forgotten.
|
||||
if CleanupCursor::<T>::get().is_none() {
|
||||
let _ = meter.try_consume(T::DbWeight::get().reads(1));
|
||||
let mut pending = PendingCleanupEras::<T>::get();
|
||||
if !pending.is_empty() {
|
||||
let next_era = pending.remove(0);
|
||||
PendingCleanupEras::<T>::put(pending);
|
||||
CleanupCursor::<T>::put((next_era, BoundedVec::default()));
|
||||
let _ = meter.try_consume(T::DbWeight::get().writes(2));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((cleanup_era, cursor)) = CleanupCursor::<T>::get() {
|
||||
// Consume weight for reading the cursor
|
||||
let _ = meter.try_consume(T::DbWeight::get().reads(1));
|
||||
@@ -278,11 +333,15 @@ pub mod pezpallet {
|
||||
let writes_weight = T::DbWeight::get().writes(result.unique as u64);
|
||||
let _ = meter.try_consume(writes_weight);
|
||||
|
||||
// Also clean SendCount for this era
|
||||
// Also clean SendCount and KeyRegistrationCount for this era
|
||||
let send_result = SendCount::<T>::clear_prefix(cleanup_era, 50, None);
|
||||
let send_writes = T::DbWeight::get().writes(send_result.unique as u64);
|
||||
let _ = meter.try_consume(send_writes);
|
||||
|
||||
let key_reg_result = KeyRegistrationCount::<T>::clear_prefix(cleanup_era, 50, None);
|
||||
let key_reg_writes = T::DbWeight::get().writes(key_reg_result.unique as u64);
|
||||
let _ = meter.try_consume(key_reg_writes);
|
||||
|
||||
match result.maybe_cursor {
|
||||
Some(new_cursor) => {
|
||||
// More items remain, save cursor for next block
|
||||
@@ -340,8 +399,16 @@ pub mod pezpallet {
|
||||
Error::<T>::InsufficientTrustScore
|
||||
);
|
||||
|
||||
// Rate limit: this call is fee-free for citizens, so it needs the same
|
||||
// per-era counter protection as `send_message` to stay consistent with the
|
||||
// pallet's spam-resistance design. Reuses `MaxMessagesPerEra` as the cap.
|
||||
let current_era = CurrentEra::<T>::get();
|
||||
let reg_count = KeyRegistrationCount::<T>::get(current_era, &who);
|
||||
ensure!(reg_count < T::MaxMessagesPerEra::get(), Error::<T>::RateLimitExceeded);
|
||||
|
||||
// Store the encryption key
|
||||
EncryptionKeys::<T>::insert(&who, public_key);
|
||||
KeyRegistrationCount::<T>::insert(current_era, &who, reg_count.saturating_add(1));
|
||||
|
||||
Self::deposit_event(Event::EncryptionKeyRegistered { who });
|
||||
Ok(())
|
||||
|
||||
@@ -192,6 +192,13 @@ pub mod pezpallet {
|
||||
#[pezpallet::getter(fn next_course_id)]
|
||||
pub type NextCourseId<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// Number of students currently enrolled in a given course.
|
||||
/// Enforces the `MaxStudentsPerCourse` bound in `enroll()`.
|
||||
#[pezpallet::storage]
|
||||
#[pezpallet::getter(fn course_enrollment_count)]
|
||||
pub type CourseEnrollmentCount<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, u32, u32, ValueQuery>;
|
||||
|
||||
#[pezpallet::storage]
|
||||
#[pezpallet::getter(fn enrollments)]
|
||||
pub type Enrollments<T: Config> =
|
||||
@@ -232,6 +239,8 @@ pub mod pezpallet {
|
||||
CourseIdOverflow,
|
||||
/// Points exceed the maximum allowed per course
|
||||
PointsExceedMax,
|
||||
/// Course has reached its maximum number of enrolled students
|
||||
TooManyStudents,
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
@@ -278,6 +287,10 @@ pub mod pezpallet {
|
||||
Error::<T>::AlreadyEnrolled
|
||||
);
|
||||
|
||||
// Enforce the per-course enrollment cap.
|
||||
let enrolled_count = CourseEnrollmentCount::<T>::get(course_id);
|
||||
ensure!(enrolled_count < T::MaxStudentsPerCourse::get(), Error::<T>::TooManyStudents);
|
||||
|
||||
let enrollment = Enrollment {
|
||||
student: student.clone(),
|
||||
course_id,
|
||||
@@ -290,6 +303,7 @@ pub mod pezpallet {
|
||||
StudentCourses::<T>::try_mutate(&student, |courses| {
|
||||
courses.try_push(course_id).map_err(|_| Error::<T>::TooManyCourses)
|
||||
})?;
|
||||
CourseEnrollmentCount::<T>::insert(course_id, enrolled_count.saturating_add(1));
|
||||
|
||||
Self::deposit_event(Event::StudentEnrolled { student, course_id });
|
||||
Ok(())
|
||||
|
||||
@@ -15,13 +15,13 @@ use pezsp_runtime::{
|
||||
BuildStorage,
|
||||
};
|
||||
|
||||
// Temel tipleri tanımlıyoruz.
|
||||
// Define the basic types.
|
||||
pub type AccountId = u64;
|
||||
pub type Balance = u128;
|
||||
pub type BlockNumber = u64;
|
||||
pub type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
// Test runtime'ımızı kuruyoruz.
|
||||
// Set up our test runtime.
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
@@ -32,7 +32,7 @@ construct_runtime!(
|
||||
}
|
||||
);
|
||||
|
||||
// pezframe_system için implementasyon.
|
||||
// Implementation for pezframe_system.
|
||||
impl pezframe_system::Config for Test {
|
||||
type BaseCallFilter = Everything;
|
||||
type BlockWeights = ();
|
||||
@@ -66,7 +66,7 @@ impl pezframe_system::Config for Test {
|
||||
type PostTransactions = ();
|
||||
}
|
||||
|
||||
// pezpallet_balances için implementasyon.
|
||||
// Implementation for pezpallet_balances.
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type Balance = Balance;
|
||||
type DustRemoval = ();
|
||||
@@ -93,23 +93,23 @@ parameter_types! {
|
||||
pub const MaxPointsPerCourse: u32 = 1000; // Max points per course completion
|
||||
}
|
||||
|
||||
// --- KESİN ÇÖZÜM BURADA BAŞLIYOR ---
|
||||
// --- THE DEFINITIVE SOLUTION STARTS HERE ---
|
||||
|
||||
// AdminOrigin'i test etmek için kendi özel yetki sağlayıcımızı oluşturuyoruz.
|
||||
// We create our own custom authority provider to test AdminOrigin.
|
||||
use pezframe_system::EnsureSignedBy;
|
||||
|
||||
// Bu struct, derleyicinin talep ettiği `SortedMembers` trait'ini manuel olarak uygular.
|
||||
// Bu, harici ve versiyona bağımlı araçlara olan ihtiyacı ortadan kaldırır.
|
||||
// This struct manually implements the `SortedMembers` trait that the compiler requires.
|
||||
// This eliminates the need for external, version-dependent tools.
|
||||
pub struct TestAdminProvider;
|
||||
impl SortedMembers<AccountId> for TestAdminProvider {
|
||||
fn sorted_members() -> Vec<AccountId> {
|
||||
// Test için admin olarak sadece 0 ID'li hesabı yetkili kılıyoruz.
|
||||
// For tests, we authorize only the account with ID 0 as admin.
|
||||
vec![0]
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_perwerde::Config for Test {
|
||||
// AdminOrigin'i, kendi yazdığımız ve sadece 0'ı admin kabul eden sağlayıcıya bağlıyoruz.
|
||||
// We bind AdminOrigin to our own provider, which accepts only 0 as admin.
|
||||
type AdminOrigin = EnsureSignedBy<TestAdminProvider, AccountId>;
|
||||
type WeightInfo = ();
|
||||
type MaxCourseNameLength = MaxCourseNameLength;
|
||||
@@ -121,7 +121,7 @@ impl pezpallet_perwerde::Config for Test {
|
||||
type TrustScoreUpdater = ();
|
||||
}
|
||||
|
||||
// Council Paletinin Mock Kurulumu (construct_runtime'da gerekli olduğu için kalıyor)
|
||||
// Mock setup for the Council pallet (kept because it is required in construct_runtime)
|
||||
use pezpallet_collective::Instance1;
|
||||
parameter_types! {
|
||||
pub const CouncilMotionDuration: BlockNumber = 5 * 60; // 5 minutes
|
||||
@@ -147,8 +147,8 @@ impl pezpallet_collective::Config<Instance1> for Test {
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
// `pezpallet-collective`'in genesis'ini de kurmamıza gerek kalmadı çünkü artık testimiz ona
|
||||
// bağlı değil.
|
||||
// We no longer need to set up the genesis of `pezpallet-collective` because our test is no
|
||||
// longer dependent on it.
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
|
||||
@@ -17,10 +17,10 @@ fn create_bounded_vec<L: Get<u32>>(s: &[u8]) -> BoundedVec<u8, L> {
|
||||
#[test]
|
||||
fn create_course_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Admin olarak mock.rs'te TestAdminProvider içinde tanımladığımız hesabı kullanıyoruz.
|
||||
// As admin, we use the account defined in TestAdminProvider in mock.rs.
|
||||
let admin_account_id = 0;
|
||||
|
||||
// Eylem: Yetkili admin ile kurs oluştur.
|
||||
// Action: create a course with the authorized admin.
|
||||
assert_ok!(PerwerdePallet::create_course(
|
||||
RuntimeOrigin::signed(admin_account_id),
|
||||
create_bounded_vec(b"Blockchain 101"),
|
||||
@@ -28,7 +28,7 @@ fn create_course_works() {
|
||||
create_bounded_vec(b"http://example.com")
|
||||
));
|
||||
|
||||
// Doğrulama
|
||||
// Verification
|
||||
assert!(crate::Courses::<Test>::contains_key(0));
|
||||
let course = crate::Courses::<Test>::get(0).unwrap();
|
||||
assert_eq!(course.owner, admin_account_id);
|
||||
@@ -41,7 +41,7 @@ fn create_course_works() {
|
||||
#[test]
|
||||
fn create_course_fails_for_non_admin() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Admin (0) dışındaki bir hesap (2) kurs oluşturamaz.
|
||||
// An account (2) other than the admin (0) cannot create a course.
|
||||
let non_admin = 2;
|
||||
assert_noop!(
|
||||
PerwerdePallet::create_course(
|
||||
|
||||
@@ -164,7 +164,7 @@ pub mod pezpallet {
|
||||
/// Authority check for root origin
|
||||
type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// NFT Collection ID ve Item ID types - must match pezpallet_nfts::Config
|
||||
/// NFT Collection ID and Item ID types - must match pezpallet_nfts::Config
|
||||
type CollectionId: Member + Parameter + MaxEncodedLen + Copy + From<u32> + Into<u32>;
|
||||
type ItemId: Member + Parameter + MaxEncodedLen + Copy + From<u32> + Into<u32>;
|
||||
}
|
||||
@@ -481,7 +481,7 @@ pub mod pezpallet {
|
||||
|
||||
ensure!(total_reward_pool > Zero::zero(), Error::<T>::InsufficientIncentivePot);
|
||||
|
||||
// Parliamentary rewards distribute et (10%)
|
||||
// Distribute parliamentary rewards (10%)
|
||||
Self::distribute_parliamentary_rewards(current_epoch, total_reward_pool)?;
|
||||
|
||||
// Remaining 90% for trust score rewards
|
||||
@@ -507,7 +507,7 @@ pub mod pezpallet {
|
||||
Zero::zero()
|
||||
};
|
||||
|
||||
// Talep son tarihini belirle (1 hafta sonra)
|
||||
// Set the claim deadline (1 week later)
|
||||
let claim_deadline = current_block.saturating_add(CLAIM_PERIOD_BLOCKS.into());
|
||||
|
||||
// Save reward pool information
|
||||
|
||||
@@ -170,7 +170,7 @@ fn finalize_epoch_calculates_rewards_correctly() {
|
||||
System::block_number() + crate::CLAIM_PERIOD_BLOCKS as u64
|
||||
);
|
||||
|
||||
// FIX: Event'te trust_score_pool (90%) bekle
|
||||
// FIX: Expect trust_score_pool (90%) in the event
|
||||
System::assert_has_event(
|
||||
Event::EpochRewardPoolCalculated {
|
||||
epoch_index: 0,
|
||||
@@ -188,7 +188,7 @@ fn finalize_epoch_calculates_rewards_correctly() {
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
// FIX: Finalize sonrası ClaimPeriod
|
||||
// FIX: ClaimPeriod after finalize
|
||||
assert_eq!(PezRewards::epoch_status(0), EpochState::ClaimPeriod);
|
||||
assert_eq!(PezRewards::epoch_status(1), EpochState::Open);
|
||||
});
|
||||
@@ -233,7 +233,7 @@ fn finalize_epoch_no_participants() {
|
||||
#[test]
|
||||
fn finalize_epoch_zero_trust_score_participant() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(dave()))); // Skor 0
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(dave()))); // Score 0
|
||||
// FIX: Zero scores are now being recorded
|
||||
assert_eq!(PezRewards::get_user_trust_score_for_epoch(0, &dave()), Some(0));
|
||||
|
||||
@@ -425,7 +425,7 @@ fn claim_reward_fails_for_wrong_epoch() {
|
||||
Error::<Test>::ClaimPeriodExpired
|
||||
);
|
||||
|
||||
// Epoch 999 yok -> ClaimPeriodExpired
|
||||
// Epoch 999 does not exist -> ClaimPeriodExpired
|
||||
assert_noop!(
|
||||
PezRewards::claim_reward(RuntimeOrigin::signed(alice()), 999),
|
||||
Error::<Test>::ClaimPeriodExpired
|
||||
@@ -440,8 +440,8 @@ fn claim_reward_fails_for_wrong_epoch() {
|
||||
#[test]
|
||||
fn close_epoch_works_after_claim_period() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(alice()))); // Claim etmeyecek
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(bob()))); // Claim edecek
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(alice()))); // Will not claim
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(bob()))); // Will claim
|
||||
|
||||
let incentive_pot = PezRewards::incentive_pot_account_id();
|
||||
let _pot_balance_before_finalize = pez_balance(&incentive_pot);
|
||||
@@ -453,7 +453,7 @@ fn close_epoch_works_after_claim_period() {
|
||||
let _alice_reward = reward_pool.reward_per_trust_point * 100;
|
||||
let bob_reward = reward_pool.reward_per_trust_point * 50;
|
||||
|
||||
assert_ok!(PezRewards::claim_reward(RuntimeOrigin::signed(bob()), 0)); // Bob claim etti
|
||||
assert_ok!(PezRewards::claim_reward(RuntimeOrigin::signed(bob()), 0)); // Bob claimed
|
||||
|
||||
let clawback_recipient = ClawbackRecipient::get();
|
||||
let balance_before = pez_balance(&clawback_recipient);
|
||||
@@ -702,13 +702,13 @@ fn multiple_epochs_work_correctly() {
|
||||
|
||||
fund_incentive_pot(1_000_000_000_000_000);
|
||||
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(alice()))); // 100 (Epoch 1 için)
|
||||
assert_ok!(PezRewards::record_trust_score(RuntimeOrigin::signed(alice()))); // 100 (for Epoch 1)
|
||||
advance_blocks(crate::BLOCKS_PER_EPOCH as u64);
|
||||
assert_ok!(PezRewards::finalize_epoch(RuntimeOrigin::root())); // Epoch 1'i finalize et
|
||||
assert_ok!(PezRewards::finalize_epoch(RuntimeOrigin::root())); // Finalize Epoch 1
|
||||
|
||||
let reward_pool_1 = PezRewards::get_epoch_reward_pool(1).unwrap(); // Epoch 1 havuzu
|
||||
let reward_pool_1 = PezRewards::get_epoch_reward_pool(1).unwrap(); // Epoch 1 pool
|
||||
let reward1_1 = reward_pool_1.reward_per_trust_point * 100;
|
||||
assert_ok!(PezRewards::claim_reward(RuntimeOrigin::signed(alice()), 1)); // Epoch 1'den claim et
|
||||
assert_ok!(PezRewards::claim_reward(RuntimeOrigin::signed(alice()), 1)); // Claim from Epoch 1
|
||||
|
||||
// Check balances
|
||||
let alice_balance = pez_balance(&alice());
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::Pezpallet as PezTreasury;
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::traits::{
|
||||
fungibles::{Inspect, Mutate},
|
||||
Get, // HATA GİDERİLDİ: .get() fonksiyonu için bu trait eklendi
|
||||
Get, // BUG FIXED: this trait was added for the .get() function
|
||||
};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::traits::{Saturating, Zero};
|
||||
@@ -60,7 +60,7 @@ mod benchmarks {
|
||||
crate::HalvingInfo::<T>::kill();
|
||||
crate::NextReleaseMonth::<T>::kill();
|
||||
crate::GenesisDistributionDone::<T>::kill();
|
||||
// Deprecated `remove_all` yerine `clear` kullanılıyor.
|
||||
// Using `clear` instead of the deprecated `remove_all`.
|
||||
let _ = crate::MonthlyReleases::<T>::clear(u32::MAX, None);
|
||||
|
||||
// First do genesis distribution to properly fund the treasury
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// pezkuwi/pallets/pez-treasury/src/mock.rs
|
||||
// VERSION 3: AccountId tipi H256 yapıldı (u64 yerine)
|
||||
// VERSION 3: AccountId type changed to H256 (instead of u64)
|
||||
|
||||
use crate::{self as pezpallet_pez_treasury, weights};
|
||||
use pezframe_support::{
|
||||
@@ -44,7 +44,7 @@ impl pezframe_system::Config for Test {
|
||||
type Nonce = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = H256; // V3: u64 -> H256 değişti
|
||||
type AccountId = H256; // V3: changed u64 -> H256
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
@@ -123,7 +123,7 @@ impl pezpallet_assets::Config for Test {
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
// CRITICAL: Bu üç PalletId FARKLI olmak ZORUNDA
|
||||
// CRITICAL: These three PalletIds MUST be DIFFERENT
|
||||
parameter_types! {
|
||||
pub const PezTreasuryPalletId: PalletId = PalletId(*b"py/pztrs");
|
||||
pub const PezIncentivePotId: PalletId = PalletId(*b"py/pzinc");
|
||||
@@ -131,7 +131,7 @@ parameter_types! {
|
||||
pub const PezAssetId: u32 = 1;
|
||||
}
|
||||
|
||||
// V3: Test accounts - H256 formatında
|
||||
// V3: Test accounts - in H256 format
|
||||
use pezsp_runtime::traits::AccountIdConversion;
|
||||
|
||||
pub fn alice() -> H256 {
|
||||
@@ -227,7 +227,7 @@ pub fn assert_pez_balance(account: H256, expected: u128) {
|
||||
);
|
||||
}
|
||||
|
||||
// V3: Helper fonksiyonlar - H256 dönüyor
|
||||
// V3: Helper functions - return H256
|
||||
#[allow(dead_code)]
|
||||
pub fn treasury_account() -> H256 {
|
||||
PezTreasuryPalletId::get().into_account_truncating()
|
||||
|
||||
@@ -239,7 +239,7 @@ fn release_monthly_funds_splits_correctly() {
|
||||
|
||||
// 75% to incentive, 25% to government
|
||||
assert_eq!(incentive_balance, monthly_amount * 75 / 100);
|
||||
// lib.rs'deki mantıkla aynı olmalı (saturating_sub)
|
||||
// Must match the logic in lib.rs (saturating_sub)
|
||||
let incentive_amount_calculated = monthly_amount * 75 / 100;
|
||||
assert_eq!(government_balance, monthly_amount - incentive_amount_calculated);
|
||||
|
||||
@@ -410,8 +410,8 @@ fn release_exactly_at_boundary_block_fails() {
|
||||
assert_ok!(PezTreasury::do_genesis_distribution());
|
||||
assert_ok!(PezTreasury::initialize_treasury(RuntimeOrigin::root()));
|
||||
|
||||
// Tam 432_000. blok (start_block=1 olduğu için) 431_999 blok geçti demektir.
|
||||
// Bu, 1 tam ay (432_000 blok) değildir.
|
||||
// Exactly block 432_000 (since start_block=1) means 431_999 blocks have passed.
|
||||
// This is not a full month (432_000 blocks).
|
||||
run_to_block(432_000);
|
||||
assert_noop!(
|
||||
PezTreasury::release_monthly_funds(RuntimeOrigin::root()),
|
||||
@@ -555,7 +555,7 @@ fn pot_accounts_are_different() {
|
||||
println!("Government: {government:?}");
|
||||
println!("================================\n");
|
||||
|
||||
// Tüm üçü farklı olmalı
|
||||
// All three must be different
|
||||
assert_ne!(treasury, incentive, "Treasury and Incentive must be different");
|
||||
assert_ne!(treasury, government, "Treasury and Government must be different");
|
||||
assert_ne!(incentive, government, "Incentive and Government must be different");
|
||||
@@ -674,7 +674,7 @@ fn full_halving_cycle_test() {
|
||||
if month < 47 {
|
||||
cumulative_released += initial_monthly;
|
||||
} else {
|
||||
// 48. sürümde (index 47) halving tetiklenir ve yarı tutar kullanılır
|
||||
// On the 48th release (index 47) halving is triggered and the halved amount is used
|
||||
cumulative_released += initial_monthly / 2;
|
||||
}
|
||||
}
|
||||
@@ -690,7 +690,7 @@ fn full_halving_cycle_test() {
|
||||
if month < 95 {
|
||||
cumulative_released += initial_monthly / 2;
|
||||
} else {
|
||||
// 96. sürümde (index 95) ikinci halving tetiklenir
|
||||
// On the 96th release (index 95) the second halving is triggered
|
||||
cumulative_released += initial_monthly / 4;
|
||||
}
|
||||
}
|
||||
@@ -944,7 +944,7 @@ fn first_period_total_is_half_of_treasury() {
|
||||
let expected_first_period = treasury_allocation / 2;
|
||||
|
||||
let diff = expected_first_period.saturating_sub(first_period_total);
|
||||
// Kalanların toplamı 48'den az olmalı (her ay en fazla 1 birim kalan)
|
||||
// The sum of the remainders must be less than 48 (at most 1 unit of remainder per month)
|
||||
assert!(diff < 48, "Rounding error too large: {diff}");
|
||||
});
|
||||
}
|
||||
@@ -966,7 +966,7 @@ fn geometric_series_sum_validates() {
|
||||
// After infinite halvings, total distributed = treasury_allocation
|
||||
// first_period_total * 2 = treasury_allocation
|
||||
let diff = treasury_allocation.saturating_sub(first_period_total * 2);
|
||||
// Kalanların toplamı (2 ile çarpılmış) 96'dan az olmalı
|
||||
// The sum of the remainders (multiplied by 2) must be less than 96
|
||||
assert!(diff < 96, "Rounding error too large: {diff}");
|
||||
});
|
||||
}
|
||||
@@ -261,9 +261,18 @@ mod benchmarks {
|
||||
// Advance blocks past presale end
|
||||
pezframe_system::Pezpallet::<T>::set_block_number(2000u32.into());
|
||||
|
||||
// Finalize presale (requires root)
|
||||
// Finalize presale (requires root) -> Successful (not yet Finalized).
|
||||
let _ = Presale::<T>::finalize_presale(RawOrigin::Root.into(), presale_id);
|
||||
|
||||
// Distribute to the sole contributor -> Finalized, and records the
|
||||
// immediate-release portion in VestingClaimed that claim_vested builds on.
|
||||
let _ = Presale::<T>::batch_distribute(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
presale_id,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
|
||||
// Advance past cliff period
|
||||
pezframe_system::Pezpallet::<T>::set_block_number(3000u32.into());
|
||||
|
||||
@@ -345,10 +354,11 @@ mod benchmarks {
|
||||
#[extrinsic_call]
|
||||
finalize_presale(RawOrigin::Root, presale_id);
|
||||
|
||||
// Verify presale was finalized
|
||||
// finalize_presale only ever transitions to Successful (soft cap reached;
|
||||
// Finalized is reached later via batch_distribute) or Failed (soft cap missed).
|
||||
let presale = crate::Presales::<T>::get(presale_id).unwrap();
|
||||
assert!(
|
||||
presale.status == PresaleStatus::Finalized || presale.status == PresaleStatus::Failed
|
||||
presale.status == PresaleStatus::Successful || presale.status == PresaleStatus::Failed
|
||||
);
|
||||
}
|
||||
|
||||
@@ -430,5 +440,49 @@ mod benchmarks {
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmark worst case: owner withdraws raised payment-asset funds from a
|
||||
/// Finalized presale (reached only after `finalize_presale` + a
|
||||
/// `batch_distribute` covering every contributor).
|
||||
#[benchmark]
|
||||
fn withdraw_funds() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
// Get next presale ID before creating
|
||||
let presale_id = NextPresaleId::<T>::get();
|
||||
let presale_treasury = Presale::<T>::presale_account_id(presale_id);
|
||||
|
||||
// Setup assets
|
||||
let (payment_asset, reward_asset) = setup_benchmark_assets::<T>(&caller, &presale_treasury);
|
||||
|
||||
// Create presale (will get the presale_id we calculated)
|
||||
let _ = create_test_presale::<T>(&caller, payment_asset, reward_asset, false, false);
|
||||
|
||||
// Contribute enough to clear the soft cap.
|
||||
let amount: u128 = 1_000_000u128;
|
||||
let _ =
|
||||
Presale::<T>::contribute(RawOrigin::Signed(caller.clone()).into(), presale_id, amount);
|
||||
|
||||
// Advance blocks past presale end, then finalize (-> Successful).
|
||||
pezframe_system::Pezpallet::<T>::set_block_number(2000u32.into());
|
||||
let _ = Presale::<T>::finalize_presale(RawOrigin::Root.into(), presale_id);
|
||||
|
||||
// Distribute to every contributor in one batch (-> Finalized), leaving
|
||||
// the raised payment-asset balance sitting in the treasury sub-account
|
||||
// for withdraw_funds to move out.
|
||||
let _ = Presale::<T>::batch_distribute(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
presale_id,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
let presale = crate::Presales::<T>::get(presale_id).unwrap();
|
||||
assert_eq!(presale.status, PresaleStatus::Finalized);
|
||||
|
||||
#[extrinsic_call]
|
||||
withdraw_funds(RawOrigin::Signed(caller), presale_id);
|
||||
|
||||
// Verify the withdrawal was recorded (guards double-withdrawal).
|
||||
assert!(crate::FundsWithdrawn::<T>::get(presale_id));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Presale, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ pub mod pezpallet {
|
||||
BoundedVec, PalletId,
|
||||
};
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
use pezsp_runtime::traits::{AtLeast32BitUnsigned, Saturating};
|
||||
use pezsp_runtime::traits::{AtLeast32BitUnsigned, CheckedAdd, Saturating};
|
||||
|
||||
pub type PresaleId = u32;
|
||||
|
||||
@@ -435,6 +435,13 @@ pub mod pezpallet {
|
||||
#[pezpallet::getter(fn successful_presales)]
|
||||
pub type SuccessfulPresales<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// Whether the raised payment-asset funds have been withdrawn by the owner for a
|
||||
/// Successful/Finalized presale. Guards against double-withdrawal.
|
||||
#[pezpallet::storage]
|
||||
#[pezpallet::getter(fn funds_withdrawn)]
|
||||
pub type FundsWithdrawn<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, PresaleId, bool, ValueQuery>;
|
||||
|
||||
#[pezpallet::event]
|
||||
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
@@ -479,6 +486,8 @@ pub mod pezpallet {
|
||||
distributed_count: u32,
|
||||
total_distributed: u128,
|
||||
},
|
||||
/// Raised funds withdrawn by presale owner [presale_id, owner, amount]
|
||||
FundsWithdrawn { presale_id: PresaleId, owner: T::AccountId, amount: u128 },
|
||||
}
|
||||
|
||||
#[pezpallet::error]
|
||||
@@ -510,6 +519,14 @@ pub mod pezpallet {
|
||||
PresaleNotSuccessful,
|
||||
SoftCapNotReached,
|
||||
InvalidSoftCap,
|
||||
/// `duration`, `grace_period_blocks`, `cliff_blocks`, or `vesting_duration_blocks`
|
||||
/// is large enough that computing the presale's end/vesting block numbers would
|
||||
/// overflow the runtime's BlockNumber type.
|
||||
InvalidDuration,
|
||||
/// Withdrawal is only allowed once, on a Successful or Finalized presale.
|
||||
WithdrawalNotAllowed,
|
||||
/// The raised funds for this presale have already been withdrawn.
|
||||
FundsAlreadyWithdrawn,
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
@@ -547,6 +564,22 @@ pub mod pezpallet {
|
||||
let presale_id = NextPresaleId::<T>::get();
|
||||
let start_block = <pezframe_system::Pezpallet<T>>::block_number();
|
||||
|
||||
// Reject durations/vesting parameters that would make downstream block-number
|
||||
// arithmetic (start_block + duration [+ cliff_blocks [+ vesting_duration_blocks]])
|
||||
// overflow BlockNumberFor<T>. Without this, a permissionless caller (any signed
|
||||
// account, since CreatePresaleOrigin = EnsureSigned) could craft a presale that
|
||||
// traps every later contribute()/refund()/finalize_presale()/claim_vested() call.
|
||||
let end_block =
|
||||
start_block.checked_add(¶ms.duration).ok_or(Error::<T>::InvalidDuration)?;
|
||||
if let Some(ref vesting) = params.vesting {
|
||||
let vesting_start = end_block
|
||||
.checked_add(&vesting.cliff_blocks)
|
||||
.ok_or(Error::<T>::InvalidDuration)?;
|
||||
vesting_start
|
||||
.checked_add(&vesting.vesting_duration_blocks)
|
||||
.ok_or(Error::<T>::InvalidDuration)?;
|
||||
}
|
||||
|
||||
// Start with empty bonus tiers - can be added later
|
||||
let bounded_bonus_tiers = BoundedVec::<BonusTier, T::MaxBonusTiers>::default();
|
||||
|
||||
@@ -600,7 +633,10 @@ pub mod pezpallet {
|
||||
ensure!(amount > 0, Error::<T>::ZeroContribution);
|
||||
|
||||
let current_block = <pezframe_system::Pezpallet<T>>::block_number();
|
||||
let end_block = presale.start_block + presale.duration;
|
||||
let end_block = presale
|
||||
.start_block
|
||||
.checked_add(&presale.duration)
|
||||
.ok_or(Error::<T>::ArithmeticOverflow)?;
|
||||
ensure!(current_block < end_block, Error::<T>::PresaleEnded);
|
||||
|
||||
// Check whitelist
|
||||
@@ -651,33 +687,42 @@ pub mod pezpallet {
|
||||
// Distribute platform fee
|
||||
Self::distribute_platform_fee(presale.payment_asset, &who, platform_fee)?;
|
||||
|
||||
// Track contribution with timestamp preservation
|
||||
let contribution = if let Some(existing) = existing_contribution {
|
||||
// Update existing contribution - preserve original timestamp
|
||||
ContributionInfo {
|
||||
// Ensure `who` occupies a slot in the Contributors list. A fully-refunded
|
||||
// contributor is removed from this list by `refund()` (to free the slot for
|
||||
// `MaxContributors`), so we must re-add them here rather than assuming presence
|
||||
// is implied by having a (possibly stale/refunded) ContributionInfo entry.
|
||||
Contributors::<T>::try_mutate(presale_id, |contributors| -> DispatchResult {
|
||||
if !contributors.contains(&who) {
|
||||
contributors
|
||||
.try_push(who.clone())
|
||||
.map_err(|_| Error::<T>::TooManyContributors)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Track contribution with timestamp preservation.
|
||||
// Only accumulate onto an existing record if it still has a non-zero amount
|
||||
// (i.e. it was never refunded, or a prior partial state). A fully-refunded
|
||||
// contribution (amount == 0, refunded == true, set by `refund()`) starts fresh:
|
||||
// this is what prevents a refund-then-recontribute cycle from inflating the
|
||||
// recorded `amount` far beyond what is actually held in the treasury.
|
||||
let contribution = match existing_contribution {
|
||||
Some(existing) if existing.amount > 0 => ContributionInfo {
|
||||
// Update existing contribution - preserve original timestamp
|
||||
amount: existing.amount.saturating_add(amount),
|
||||
contributed_at: existing.contributed_at, // ✅ Keep original timestamp
|
||||
refunded: false,
|
||||
refunded_at: None,
|
||||
refund_fee_paid: 0,
|
||||
}
|
||||
} else {
|
||||
// New contribution - add to contributors list
|
||||
Contributors::<T>::try_mutate(presale_id, |contributors| -> DispatchResult {
|
||||
contributors
|
||||
.try_push(who.clone())
|
||||
.map_err(|_| Error::<T>::TooManyContributors)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Create new contribution with current timestamp
|
||||
ContributionInfo {
|
||||
},
|
||||
_ => ContributionInfo {
|
||||
// New contribution, or re-contribution after a full refund
|
||||
amount,
|
||||
contributed_at: current_block, // ✅ Set timestamp for first contribution only
|
||||
contributed_at: current_block, // ✅ Fresh timestamp for grace-period purposes
|
||||
refunded: false,
|
||||
refunded_at: None,
|
||||
refund_fee_paid: 0,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Contributions::<T>::insert(presale_id, &who, contribution);
|
||||
@@ -705,7 +750,10 @@ pub mod pezpallet {
|
||||
ensure!(presale.status == PresaleStatus::Active, Error::<T>::PresaleNotActive);
|
||||
|
||||
let current_block = <pezframe_system::Pezpallet<T>>::block_number();
|
||||
let end_block = presale.start_block + presale.duration;
|
||||
let end_block = presale
|
||||
.start_block
|
||||
.checked_add(&presale.duration)
|
||||
.ok_or(Error::<T>::ArithmeticOverflow)?;
|
||||
ensure!(current_block >= end_block, Error::<T>::PresaleNotEnded);
|
||||
|
||||
let total_raised = TotalRaised::<T>::get(presale_id);
|
||||
@@ -754,7 +802,10 @@ pub mod pezpallet {
|
||||
ensure!(presale.status == PresaleStatus::Active, Error::<T>::RefundNotAllowed);
|
||||
|
||||
let current_block = <pezframe_system::Pezpallet<T>>::block_number();
|
||||
let end_block = presale.start_block + presale.duration;
|
||||
let end_block = presale
|
||||
.start_block
|
||||
.checked_add(&presale.duration)
|
||||
.ok_or(Error::<T>::ArithmeticOverflow)?;
|
||||
ensure!(current_block < end_block, Error::<T>::RefundNotAllowed);
|
||||
|
||||
let mut contribution_info =
|
||||
@@ -801,14 +852,30 @@ pub mod pezpallet {
|
||||
Self::distribute_platform_fee(presale.payment_asset, &treasury, fee)?;
|
||||
}
|
||||
|
||||
// Update contribution info (mark as refunded instead of removing)
|
||||
// Decrement TotalRaised using the amount that was actually on record before we
|
||||
// clear it below.
|
||||
let refunded_amount = contribution_info.amount;
|
||||
TotalRaised::<T>::mutate(presale_id, |r| *r = r.saturating_sub(refunded_amount));
|
||||
|
||||
// Update contribution info: mark as refunded AND zero the recorded amount.
|
||||
// Zeroing `amount` is essential — otherwise a refund-then-recontribute cycle
|
||||
// would let `contribute()`'s `existing.amount.saturating_add(amount)` keep
|
||||
// accumulating a stale amount that is no longer backed by real treasury funds,
|
||||
// inflating this wallet's share of reward-token distribution at other
|
||||
// contributors' expense.
|
||||
contribution_info.refunded = true;
|
||||
contribution_info.refunded_at = Some(current_block);
|
||||
contribution_info.refund_fee_paid = fee;
|
||||
contribution_info.amount = 0;
|
||||
Contributions::<T>::insert(presale_id, &who, contribution_info);
|
||||
|
||||
TotalRaised::<T>::mutate(presale_id, |r| {
|
||||
*r = r.saturating_sub(contribution_info.amount)
|
||||
// Free up this wallet's slot in the bounded Contributors list now that it has
|
||||
// no active contribution. Without this, a Sybil attacker could contribute the
|
||||
// minimum amount from many accounts and immediately refund, permanently
|
||||
// consuming MaxContributors capacity at near-zero cost (only the refund fee is
|
||||
// lost) and locking legitimate contributors out with `TooManyContributors`.
|
||||
Contributors::<T>::mutate(presale_id, |contributors| {
|
||||
contributors.retain(|acc| acc != &who);
|
||||
});
|
||||
|
||||
Self::deposit_event(Event::Refunded { presale_id, who, amount: refund_amount, fee });
|
||||
@@ -834,8 +901,13 @@ pub mod pezpallet {
|
||||
ensure!(!contribution_info.refunded, Error::<T>::NoContribution);
|
||||
|
||||
let current_block = <pezframe_system::Pezpallet<T>>::block_number();
|
||||
let end_block = presale.start_block + presale.duration;
|
||||
let vesting_start = end_block + vesting.cliff_blocks;
|
||||
let end_block = presale
|
||||
.start_block
|
||||
.checked_add(&presale.duration)
|
||||
.ok_or(Error::<T>::ArithmeticOverflow)?;
|
||||
let vesting_start = end_block
|
||||
.checked_add(&vesting.cliff_blocks)
|
||||
.ok_or(Error::<T>::ArithmeticOverflow)?;
|
||||
|
||||
ensure!(current_block >= vesting_start, Error::<T>::NothingToClaim);
|
||||
|
||||
@@ -853,7 +925,9 @@ pub mod pezpallet {
|
||||
|
||||
// Calculate vested amount
|
||||
let already_claimed = VestingClaimed::<T>::get(presale_id, &who);
|
||||
let vesting_end = vesting_start + vesting.vesting_duration_blocks;
|
||||
let vesting_end = vesting_start
|
||||
.checked_add(&vesting.vesting_duration_blocks)
|
||||
.ok_or(Error::<T>::ArithmeticOverflow)?;
|
||||
|
||||
let claimable = if current_block >= vesting_end {
|
||||
// All vested
|
||||
@@ -983,6 +1057,15 @@ pub mod pezpallet {
|
||||
let contributor = &contributors[i as usize];
|
||||
|
||||
if let Some(contribution_info) = Contributions::<T>::get(presale_id, contributor) {
|
||||
// Skip contributors who already received their reward-token payout via
|
||||
// batch_distribute() before this presale was cancelled (a Successful
|
||||
// presale can sit in that status across many batch_distribute() calls).
|
||||
// Without this check they would double-dip: keep the distributed reward
|
||||
// tokens AND receive their payment-asset contribution back.
|
||||
if VestingClaimed::<T>::contains_key(presale_id, contributor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !contribution_info.refunded && contribution_info.amount > 0 {
|
||||
// Calculate net amount in treasury (original - platform fee already deducted at contribution)
|
||||
let platform_fee_at_contribution = contribution_info
|
||||
@@ -1239,6 +1322,59 @@ pub mod pezpallet {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Withdraw the raised payment-asset funds from a Finalized presale's treasury
|
||||
/// sub-account to the presale owner. Callable once per presale by the owner only.
|
||||
#[pezpallet::call_index(10)]
|
||||
#[pezpallet::weight(T::PresaleWeightInfo::withdraw_funds())]
|
||||
pub fn withdraw_funds(origin: OriginFor<T>, presale_id: PresaleId) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
let presale = Presales::<T>::get(presale_id).ok_or(Error::<T>::PresaleNotFound)?;
|
||||
|
||||
ensure!(who == presale.owner, Error::<T>::NotPresaleOwner);
|
||||
|
||||
// Only allow withdrawal once the presale is Finalized (i.e. batch_distribute has
|
||||
// paid out every contributor's reward tokens). Deliberately NOT allowed while
|
||||
// still Successful: cancel_presale() can cancel a Successful presale, in which
|
||||
// case refund_cancelled_presale() must be able to refund contributors out of the
|
||||
// same treasury sub-account. Once Finalized, cancel_presale() can no longer
|
||||
// target this presale (it only matches Active/Pending/Paused/Successful), so
|
||||
// withdrawing here can never race with a contributor refund claim.
|
||||
ensure!(presale.status == PresaleStatus::Finalized, Error::<T>::WithdrawalNotAllowed);
|
||||
|
||||
ensure!(!FundsWithdrawn::<T>::get(presale_id), Error::<T>::FundsAlreadyWithdrawn);
|
||||
|
||||
let treasury = Self::presale_account_id(presale_id);
|
||||
let balance = T::Assets::reducible_balance(
|
||||
presale.payment_asset,
|
||||
&treasury,
|
||||
Preservation::Expendable,
|
||||
Fortitude::Polite,
|
||||
);
|
||||
let balance_u128: u128 = balance.into();
|
||||
ensure!(balance_u128 > 0, Error::<T>::InsufficientBalance);
|
||||
|
||||
T::Assets::transfer(
|
||||
presale.payment_asset,
|
||||
&treasury,
|
||||
&who,
|
||||
balance,
|
||||
Preservation::Expendable,
|
||||
)?;
|
||||
|
||||
// Guard against double-withdrawal regardless of whether more funds later land
|
||||
// in the treasury sub-account (e.g. dust from later batch operations).
|
||||
FundsWithdrawn::<T>::insert(presale_id, true);
|
||||
|
||||
Self::deposit_event(Event::FundsWithdrawn {
|
||||
presale_id,
|
||||
owner: who,
|
||||
amount: balance_u128,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pezpallet<T> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -18,23 +18,23 @@
|
||||
|
||||
//! Autogenerated weights for `pezpallet_presale`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-12-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.1
|
||||
//! DATE: 2026-07-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `MamostePC`, CPU: `11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz`
|
||||
//! HOSTNAME: `vmi3386610`, CPU: `AMD EPYC Processor (with IBPB)`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// ./target/release/frame-omni-bencher
|
||||
// target/release/pezframe-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --runtime
|
||||
// target/release/wbuild/asset-hub-pezkuwichain-runtime/asset_hub_pezkuwichain_runtime.compact.compressed.wasm
|
||||
// --pallets
|
||||
// --pallet
|
||||
// pezpallet_presale
|
||||
// -e
|
||||
// all
|
||||
// --extrinsic
|
||||
// *
|
||||
// --steps
|
||||
// 50
|
||||
// --repeat
|
||||
@@ -64,6 +64,7 @@ pub trait WeightInfo {
|
||||
fn refund_cancelled_presale() -> Weight;
|
||||
fn finalize_presale(n: u32, ) -> Weight;
|
||||
fn batch_refund_failed_presale(n: u32, ) -> Weight;
|
||||
fn withdraw_funds() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_presale` using the Bizinikiwi node and recommended hardware.
|
||||
@@ -77,8 +78,8 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `147`
|
||||
// Estimated: `1489`
|
||||
// Minimum execution time: 9_638_000 picoseconds.
|
||||
Weight::from_parts(10_003_000, 1489)
|
||||
// Minimum execution time: 35_647_000 picoseconds.
|
||||
Weight::from_parts(40_765_000, 1489)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
@@ -88,8 +89,8 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `356`
|
||||
// Estimated: `3717`
|
||||
// Minimum execution time: 12_202_000 picoseconds.
|
||||
Weight::from_parts(12_492_000, 3717)
|
||||
// Minimum execution time: 35_606_000 picoseconds.
|
||||
Weight::from_parts(47_778_000, 3717)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
@@ -101,8 +102,8 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `356`
|
||||
// Estimated: `3717`
|
||||
// Minimum execution time: 12_973_000 picoseconds.
|
||||
Weight::from_parts(14_077_000, 3717)
|
||||
// Minimum execution time: 27_901_000 picoseconds.
|
||||
Weight::from_parts(39_263_000, 3717)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
@@ -128,10 +129,10 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Proof: `Presale::TotalPlatformFees` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn contribute() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1169`
|
||||
// Measured: `1203`
|
||||
// Estimated: `323487`
|
||||
// Minimum execution time: 160_665_000 picoseconds.
|
||||
Weight::from_parts(165_629_000, 323487)
|
||||
// Minimum execution time: 464_453_000 picoseconds.
|
||||
Weight::from_parts(495_981_000, 323487)
|
||||
.saturating_add(T::DbWeight::get().reads(13_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(11_u64))
|
||||
}
|
||||
@@ -151,14 +152,16 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::TotalRaised` (r:1 w:1)
|
||||
/// Proof: `Presale::TotalRaised` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributors` (r:1 w:1)
|
||||
/// Proof: `Presale::Contributors` (`max_values`: None, `max_size`: Some(320022), added: 322497, mode: `MaxEncodedLen`)
|
||||
fn refund() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1531`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 169_420_000 picoseconds.
|
||||
Weight::from_parts(173_774_000, 11426)
|
||||
.saturating_add(T::DbWeight::get().reads(11_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(10_u64))
|
||||
// Measured: `1608`
|
||||
// Estimated: `323487`
|
||||
// Minimum execution time: 335_754_000 picoseconds.
|
||||
Weight::from_parts(511_931_000, 323487)
|
||||
.saturating_add(T::DbWeight::get().reads(12_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(11_u64))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:0)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
@@ -176,10 +179,10 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn claim_vested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1392`
|
||||
// Measured: `1426`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 72_526_000 picoseconds.
|
||||
Weight::from_parts(74_529_000, 6208)
|
||||
// Minimum execution time: 173_863_000 picoseconds.
|
||||
Weight::from_parts(221_982_000, 6208)
|
||||
.saturating_add(T::DbWeight::get().reads(8_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
@@ -189,6 +192,8 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Proof: `Presale::Contributors` (`max_values`: None, `max_size`: Some(320022), added: 322497, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributions` (r:1 w:1)
|
||||
/// Proof: `Presale::Contributions` (`max_values`: None, `max_size`: Some(110), added: 2585, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::VestingClaimed` (r:1 w:0)
|
||||
/// Proof: `Presale::VestingClaimed` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
@@ -197,45 +202,31 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn refund_cancelled_presale() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1370`
|
||||
// Measured: `1428`
|
||||
// Estimated: `323487`
|
||||
// Minimum execution time: 66_454_000 picoseconds.
|
||||
Weight::from_parts(69_615_000, 323487)
|
||||
.saturating_add(T::DbWeight::get().reads(7_u64))
|
||||
// Minimum execution time: 162_231_000 picoseconds.
|
||||
Weight::from_parts(227_132_000, 323487)
|
||||
.saturating_add(T::DbWeight::get().reads(8_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:1)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::TotalRaised` (r:1 w:0)
|
||||
/// Proof: `Presale::TotalRaised` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributors` (r:1 w:0)
|
||||
/// Proof: `Presale::Contributors` (`max_values`: None, `max_size`: Some(320022), added: 322497, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributions` (r:100 w:0)
|
||||
/// Proof: `Presale::Contributions` (`max_values`: None, `max_size`: Some(110), added: 2585, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:101 w:101)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:101 w:101)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::SuccessfulPresales` (r:1 w:1)
|
||||
/// Proof: `Presale::SuccessfulPresales` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[1, 100]`.
|
||||
fn finalize_presale(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `525`
|
||||
// Estimated: `3717 + n * (3235 ±0)`
|
||||
// Minimum execution time: 17_663_000 picoseconds.
|
||||
Weight::from_parts(18_454_000, 3717)
|
||||
// Standard Error: 463_975
|
||||
.saturating_add(Weight::from_parts(2_728_899, 0).saturating_mul(n.into()))
|
||||
// Estimated: `3717 + n * (15 ±0)`
|
||||
// Minimum execution time: 45_775_000 picoseconds.
|
||||
Weight::from_parts(79_721_748, 3717)
|
||||
// Standard Error: 16_126
|
||||
.saturating_add(Weight::from_parts(317_025, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 3235).saturating_mul(n.into()))
|
||||
.saturating_add(Weight::from_parts(0, 15).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:0)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
@@ -252,18 +243,41 @@ impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// The range of component `n` is `[1, 100]`.
|
||||
fn batch_refund_failed_presale(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1244 + n * (198 ±0)`
|
||||
// Measured: `1278 + n * (198 ±0)`
|
||||
// Estimated: `323487 + n * (2609 ±0)`
|
||||
// Minimum execution time: 70_612_000 picoseconds.
|
||||
Weight::from_parts(17_304_947, 323487)
|
||||
// Standard Error: 88_572
|
||||
.saturating_add(Weight::from_parts(42_218_217, 0).saturating_mul(n.into()))
|
||||
// Minimum execution time: 149_508_000 picoseconds.
|
||||
Weight::from_parts(190_635_000, 323487)
|
||||
// Standard Error: 335_699
|
||||
.saturating_add(Weight::from_parts(127_279_841, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into())))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into())))
|
||||
.saturating_add(Weight::from_parts(0, 2609).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:0)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::FundsWithdrawn` (r:1 w:1)
|
||||
/// Proof: `Presale::FundsWithdrawn` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
fn withdraw_funds() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1366`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 159_568_000 picoseconds.
|
||||
Weight::from_parts(253_502_000, 6208)
|
||||
.saturating_add(T::DbWeight::get().reads(8_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(7_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
@@ -276,8 +290,8 @@ impl WeightInfo for () {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `147`
|
||||
// Estimated: `1489`
|
||||
// Minimum execution time: 9_638_000 picoseconds.
|
||||
Weight::from_parts(10_003_000, 1489)
|
||||
// Minimum execution time: 35_647_000 picoseconds.
|
||||
Weight::from_parts(40_765_000, 1489)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
@@ -287,8 +301,8 @@ impl WeightInfo for () {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `356`
|
||||
// Estimated: `3717`
|
||||
// Minimum execution time: 12_202_000 picoseconds.
|
||||
Weight::from_parts(12_492_000, 3717)
|
||||
// Minimum execution time: 35_606_000 picoseconds.
|
||||
Weight::from_parts(47_778_000, 3717)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
@@ -300,8 +314,8 @@ impl WeightInfo for () {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `356`
|
||||
// Estimated: `3717`
|
||||
// Minimum execution time: 12_973_000 picoseconds.
|
||||
Weight::from_parts(14_077_000, 3717)
|
||||
// Minimum execution time: 27_901_000 picoseconds.
|
||||
Weight::from_parts(39_263_000, 3717)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
@@ -327,10 +341,10 @@ impl WeightInfo for () {
|
||||
/// Proof: `Presale::TotalPlatformFees` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn contribute() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1169`
|
||||
// Measured: `1203`
|
||||
// Estimated: `323487`
|
||||
// Minimum execution time: 160_665_000 picoseconds.
|
||||
Weight::from_parts(165_629_000, 323487)
|
||||
// Minimum execution time: 464_453_000 picoseconds.
|
||||
Weight::from_parts(495_981_000, 323487)
|
||||
.saturating_add(RocksDbWeight::get().reads(13_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(11_u64))
|
||||
}
|
||||
@@ -350,14 +364,16 @@ impl WeightInfo for () {
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::TotalRaised` (r:1 w:1)
|
||||
/// Proof: `Presale::TotalRaised` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributors` (r:1 w:1)
|
||||
/// Proof: `Presale::Contributors` (`max_values`: None, `max_size`: Some(320022), added: 322497, mode: `MaxEncodedLen`)
|
||||
fn refund() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1531`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 169_420_000 picoseconds.
|
||||
Weight::from_parts(173_774_000, 11426)
|
||||
.saturating_add(RocksDbWeight::get().reads(11_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(10_u64))
|
||||
// Measured: `1608`
|
||||
// Estimated: `323487`
|
||||
// Minimum execution time: 335_754_000 picoseconds.
|
||||
Weight::from_parts(511_931_000, 323487)
|
||||
.saturating_add(RocksDbWeight::get().reads(12_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(11_u64))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:0)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
@@ -375,10 +391,10 @@ impl WeightInfo for () {
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn claim_vested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1392`
|
||||
// Measured: `1426`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 72_526_000 picoseconds.
|
||||
Weight::from_parts(74_529_000, 6208)
|
||||
// Minimum execution time: 173_863_000 picoseconds.
|
||||
Weight::from_parts(221_982_000, 6208)
|
||||
.saturating_add(RocksDbWeight::get().reads(8_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
@@ -388,6 +404,8 @@ impl WeightInfo for () {
|
||||
/// Proof: `Presale::Contributors` (`max_values`: None, `max_size`: Some(320022), added: 322497, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributions` (r:1 w:1)
|
||||
/// Proof: `Presale::Contributions` (`max_values`: None, `max_size`: Some(110), added: 2585, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::VestingClaimed` (r:1 w:0)
|
||||
/// Proof: `Presale::VestingClaimed` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
@@ -396,45 +414,31 @@ impl WeightInfo for () {
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn refund_cancelled_presale() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1370`
|
||||
// Measured: `1428`
|
||||
// Estimated: `323487`
|
||||
// Minimum execution time: 66_454_000 picoseconds.
|
||||
Weight::from_parts(69_615_000, 323487)
|
||||
.saturating_add(RocksDbWeight::get().reads(7_u64))
|
||||
// Minimum execution time: 162_231_000 picoseconds.
|
||||
Weight::from_parts(227_132_000, 323487)
|
||||
.saturating_add(RocksDbWeight::get().reads(8_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:1)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::TotalRaised` (r:1 w:0)
|
||||
/// Proof: `Presale::TotalRaised` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributors` (r:1 w:0)
|
||||
/// Proof: `Presale::Contributors` (`max_values`: None, `max_size`: Some(320022), added: 322497, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::Contributions` (r:100 w:0)
|
||||
/// Proof: `Presale::Contributions` (`max_values`: None, `max_size`: Some(110), added: 2585, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:101 w:101)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:101 w:101)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::SuccessfulPresales` (r:1 w:1)
|
||||
/// Proof: `Presale::SuccessfulPresales` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[1, 100]`.
|
||||
fn finalize_presale(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `525`
|
||||
// Estimated: `3717 + n * (3235 ±0)`
|
||||
// Minimum execution time: 17_663_000 picoseconds.
|
||||
Weight::from_parts(18_454_000, 3717)
|
||||
// Standard Error: 463_975
|
||||
.saturating_add(Weight::from_parts(2_728_899, 0).saturating_mul(n.into()))
|
||||
// Estimated: `3717 + n * (15 ±0)`
|
||||
// Minimum execution time: 45_775_000 picoseconds.
|
||||
Weight::from_parts(79_721_748, 3717)
|
||||
// Standard Error: 16_126
|
||||
.saturating_add(Weight::from_parts(317_025, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 3235).saturating_mul(n.into()))
|
||||
.saturating_add(Weight::from_parts(0, 15).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:0)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
@@ -451,16 +455,39 @@ impl WeightInfo for () {
|
||||
/// The range of component `n` is `[1, 100]`.
|
||||
fn batch_refund_failed_presale(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1244 + n * (198 ±0)`
|
||||
// Measured: `1278 + n * (198 ±0)`
|
||||
// Estimated: `323487 + n * (2609 ±0)`
|
||||
// Minimum execution time: 70_612_000 picoseconds.
|
||||
Weight::from_parts(17_304_947, 323487)
|
||||
// Standard Error: 88_572
|
||||
.saturating_add(Weight::from_parts(42_218_217, 0).saturating_mul(n.into()))
|
||||
// Minimum execution time: 149_508_000 picoseconds.
|
||||
Weight::from_parts(190_635_000, 323487)
|
||||
// Standard Error: 335_699
|
||||
.saturating_add(Weight::from_parts(127_279_841, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into())))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(n.into())))
|
||||
.saturating_add(Weight::from_parts(0, 2609).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `Presale::Presales` (r:1 w:0)
|
||||
/// Proof: `Presale::Presales` (`max_values`: None, `max_size`: Some(252), added: 2727, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Presale::FundsWithdrawn` (r:1 w:1)
|
||||
/// Proof: `Presale::FundsWithdrawn` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
fn withdraw_funds() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1366`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 159_568_000 picoseconds.
|
||||
Weight::from_parts(253_502_000, 6208)
|
||||
.saturating_add(RocksDbWeight::get().reads(8_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(7_u64))
|
||||
}
|
||||
}
|
||||
@@ -376,7 +376,7 @@ pub mod pezpallet {
|
||||
fn get_referral_score(who: &T::AccountId) -> RawScore {
|
||||
let stats = ReferrerStatsStorage::<T>::get(who);
|
||||
|
||||
// Step 1: "Haksız olanı geri alma" - Remove revoked referrals from count
|
||||
// Step 1: "Reverse the unfair ones" - Remove revoked referrals from count
|
||||
// This is NOT a penalty, it's correcting the record to reflect reality
|
||||
let good_referrals = stats.total_referrals.saturating_sub(stats.revoked_referrals);
|
||||
|
||||
@@ -395,7 +395,7 @@ pub mod pezpallet {
|
||||
_ => 500,
|
||||
};
|
||||
|
||||
// Step 3: "Cezalandırma" - Apply stored penalty from PenaltyPerRevocation
|
||||
// Step 3: "Punishment" - Apply stored penalty from PenaltyPerRevocation
|
||||
// Uses the pre-calculated penalty_score accumulated in on_citizenship_revoked()
|
||||
// This is the actual punishment: "you should have been more careful"
|
||||
base_score.saturating_sub(stats.penalty_score)
|
||||
|
||||
@@ -6,11 +6,25 @@
|
||||
//! Benchmarking setup for pezpallet-staking-score
|
||||
|
||||
use crate::{
|
||||
CachedStakingDetails, Call, Config, Pezpallet, StakingDetails, StakingSource,
|
||||
StakingStartBlock, UNITS,
|
||||
BalanceOf, CachedStakingDetails, Call, Config, NoterBonds, NoterCheck, PendingStakingDetails,
|
||||
PendingSubmission, Pezpallet, StakingDetails, StakingSource, StakingStartBlock, UNITS,
|
||||
};
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::traits::{Currency, EnsureOrigin, Get};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::traits::Saturating;
|
||||
|
||||
/// Fund `who` with double the noter bond and register them as an active
|
||||
/// (bonded) noter — `T::NoterChecker::make_noter` handles whatever a real
|
||||
/// runtime's checker needs (e.g. minting the tiki pallet's Noter role NFT),
|
||||
/// so this pallet's benchmarks stay decoupled from that pallet's internals.
|
||||
fn fund_and_register_noter<T: Config>(who: &T::AccountId) {
|
||||
let bond: BalanceOf<T> = T::NoterBondAmount::get();
|
||||
let _ = T::Currency::deposit_creating(who, bond + bond);
|
||||
T::NoterChecker::make_noter(who);
|
||||
Pezpallet::<T>::register_as_noter(RawOrigin::Signed(who.clone()).into())
|
||||
.expect("benchmark setup: register_as_noter must succeed");
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
@@ -68,4 +82,122 @@ mod benchmarks {
|
||||
|
||||
assert!(CachedStakingDetails::<T>::get(&target, StakingSource::RelayChain).is_some());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_as_noter() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let bond: BalanceOf<T> = T::NoterBondAmount::get();
|
||||
let _ = T::Currency::deposit_creating(&caller, bond + bond);
|
||||
T::NoterChecker::make_noter(&caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
assert!(NoterBonds::<T>::get(&caller).is_some());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn unregister_as_noter() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
fund_and_register_noter::<T>(&caller);
|
||||
// No submission on record — the dispute-window gate is a no-op here,
|
||||
// which is the cheapest path but still exercises the full call.
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
assert!(NoterBonds::<T>::get(&caller).is_none());
|
||||
}
|
||||
|
||||
/// Benchmark worst case: DisputeOrigin freezing a pending submission that
|
||||
/// does exist (the existence check + removal, not just the origin check).
|
||||
#[benchmark]
|
||||
fn dispute_staking_details() {
|
||||
let noter: T::AccountId = whitelisted_caller();
|
||||
fund_and_register_noter::<T>(¬er);
|
||||
|
||||
let target: T::AccountId = account("target", 0, 0);
|
||||
PendingStakingDetails::<T>::insert(
|
||||
&target,
|
||||
StakingSource::RelayChain,
|
||||
PendingSubmission {
|
||||
details: StakingDetails {
|
||||
staked_amount: (500u128 * UNITS).into(),
|
||||
nominations_count: 3,
|
||||
unlocking_chunks_count: 0,
|
||||
},
|
||||
submitted_by: noter,
|
||||
submitted_at: 0u32.into(),
|
||||
},
|
||||
);
|
||||
|
||||
let dispute_origin = T::DisputeOrigin::try_successful_origin()
|
||||
.expect("DisputeOrigin must have a benchmark origin");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(dispute_origin, target.clone(), StakingSource::RelayChain);
|
||||
|
||||
assert!(PendingStakingDetails::<T>::get(&target, StakingSource::RelayChain).is_none());
|
||||
}
|
||||
|
||||
/// Benchmark worst case: same cost profile as the Root path of
|
||||
/// `receive_staking_details` (a full `apply_staking_update`), plus the
|
||||
/// pending-entry lookup/removal.
|
||||
#[benchmark]
|
||||
fn finalize_staking_details() {
|
||||
let noter: T::AccountId = whitelisted_caller();
|
||||
fund_and_register_noter::<T>(¬er);
|
||||
|
||||
let target: T::AccountId = account("target", 0, 0);
|
||||
CachedStakingDetails::<T>::insert(
|
||||
&target,
|
||||
StakingSource::AssetHub,
|
||||
StakingDetails {
|
||||
staked_amount: (200u128 * UNITS).into(),
|
||||
nominations_count: 1,
|
||||
unlocking_chunks_count: 0,
|
||||
},
|
||||
);
|
||||
PendingStakingDetails::<T>::insert(
|
||||
&target,
|
||||
StakingSource::RelayChain,
|
||||
PendingSubmission {
|
||||
details: StakingDetails {
|
||||
staked_amount: (500u128 * UNITS).into(),
|
||||
nominations_count: 3,
|
||||
unlocking_chunks_count: 0,
|
||||
},
|
||||
submitted_by: noter,
|
||||
submitted_at: 0u32.into(),
|
||||
},
|
||||
);
|
||||
// Advance past the runtime's actual configured DisputeWindow so the
|
||||
// submission above has matured — submitted_at alone (block 0) proves
|
||||
// nothing if the current block hasn't moved past it too.
|
||||
pezframe_system::Pezpallet::<T>::set_block_number(
|
||||
T::DisputeWindow::get().saturating_add(1u32.into()),
|
||||
);
|
||||
|
||||
let caller: T::AccountId = account("finalizer", 0, 0);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller), target.clone(), StakingSource::RelayChain);
|
||||
|
||||
assert!(PendingStakingDetails::<T>::get(&target, StakingSource::RelayChain).is_none());
|
||||
assert!(CachedStakingDetails::<T>::get(&target, StakingSource::RelayChain).is_some());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn slash_noter() {
|
||||
let noter: T::AccountId = whitelisted_caller();
|
||||
fund_and_register_noter::<T>(¬er);
|
||||
|
||||
let slash_origin = T::SlashOrigin::try_successful_origin()
|
||||
.expect("SlashOrigin must have a benchmark origin");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(slash_origin, noter.clone());
|
||||
|
||||
assert!(NoterBonds::<T>::get(¬er).is_none());
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,25 @@
|
||||
//!
|
||||
//! The sudo account delegates `receive_staking_details` authority to accounts that
|
||||
//! hold the `Noter` tiki (role NFT). A bot collects staking data from Relay Chain
|
||||
//! and Asset Hub, then a noter signs and submits the data to People Chain.
|
||||
//! and Asset Hub, then a noter signs and submits the data to People Chain. The
|
||||
//! Noter tiki is not a single hardcoded account — any number of independent
|
||||
//! accounts can hold it, the same way a state can authorize any number of notaries.
|
||||
//!
|
||||
//! ## Bonded Registration and Dispute Window
|
||||
//!
|
||||
//! Holding the Noter tiki alone is not enough to submit data: an account must
|
||||
//! also call `register_as_noter()` and post a bond (`NoterBondAmount`), mirroring
|
||||
//! a real notary's insurance/liability bond. A noter-submitted update does not
|
||||
//! take effect immediately — it sits in `PendingStakingDetails` for
|
||||
//! `DisputeWindow` blocks (like a deed's recording/contestability period) during
|
||||
//! which `T::DisputeOrigin` (any single Council member) can call
|
||||
//! `dispute_staking_details()` to freeze it pending governance review. If
|
||||
//! governance (`T::SlashOrigin`) confirms the submission was fraudulent, it can
|
||||
//! call `slash_noter()` to burn the noter's bond to `T::SlashDestination`.
|
||||
//!
|
||||
//! Root (XCM Transact) submissions are exempt from the bond and the dispute
|
||||
//! window: that origin is chain-authenticated (backed by consensus, not a single
|
||||
//! private key), so it carries strictly more assurance than any bonded noter.
|
||||
//!
|
||||
//! ## Dual-Chain Staking
|
||||
//!
|
||||
@@ -47,12 +65,29 @@ pub mod weights;
|
||||
pub mod pezpallet {
|
||||
use super::weights::WeightInfo;
|
||||
use core::ops::Div;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
traits::{Currency, ExistenceRequirement, ReservableCurrency},
|
||||
};
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
use pezsp_runtime::traits::{Saturating, Zero};
|
||||
|
||||
// --- Constants ---
|
||||
//
|
||||
// Block-time assumption: 10 blocks/minute (6s blocks), matching this
|
||||
// chain's actual configured slot duration (`testnet_teyrchains_constants::
|
||||
// pezkuwichain::consensus::MILLISECS_PER_BLOCK = 6_000` — the module
|
||||
// `people-pezkuwichain` actually imports its `HOURS`/`DAYS`/`SlotDuration`
|
||||
// from; `teyrchains_common::MILLISECS_PER_BLOCK = 12_000` is a
|
||||
// same-named but unrelated, unused-for-timing constant in a different
|
||||
// crate and does not govern this runtime's real block cadence). An
|
||||
// earlier revision of this file assumed the 12s figure and halved every
|
||||
// duration-tier threshold below (confirmed against live mainnet state via
|
||||
// try-runtime and empirical block-time measurement before it shipped). If
|
||||
// this chain's block time ever changes, these two constants need
|
||||
// updating to match — they are not derived automatically.
|
||||
pub const MONTH_IN_BLOCKS: u32 = 30 * 24 * 60 * 10;
|
||||
pub const HOUR_IN_BLOCKS: u32 = 60 * 10;
|
||||
pub const UNITS: u128 = 1_000_000_000_000;
|
||||
|
||||
/// The chain from which staking data originates.
|
||||
@@ -82,6 +117,15 @@ pub mod pezpallet {
|
||||
/// Noter-authorized accounts can submit staking details on behalf of users.
|
||||
pub trait NoterCheck<AccountId> {
|
||||
fn is_noter(who: &AccountId) -> bool;
|
||||
|
||||
/// Grant noter authority to `who`, for benchmarking only. Lets a
|
||||
/// runtime's real checker (e.g. one backed by the tiki pallet's role
|
||||
/// NFTs) make its own benchmark setup self-contained, instead of this
|
||||
/// pallet's generic benchmarks needing to know how an external role
|
||||
/// system works. Default no-op; runtimes whose `is_noter` depends on
|
||||
/// external state should override it.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn make_noter(_who: &AccountId) {}
|
||||
}
|
||||
|
||||
/// Default implementation: nobody is noter (safe default for tests).
|
||||
@@ -118,8 +162,42 @@ pub mod pezpallet {
|
||||
/// Checker for noter authority. Accounts with the Noter tiki can submit
|
||||
/// staking details without requiring root origin.
|
||||
type NoterChecker: NoterCheck<Self::AccountId>;
|
||||
|
||||
/// Reservable currency used for the noter registration bond.
|
||||
type Currency: ReservableCurrency<Self::AccountId>;
|
||||
|
||||
/// Bond a Noter-tiki holder must reserve via `register_as_noter()` before
|
||||
/// their submissions are accepted. Slashable by `SlashOrigin` if governance
|
||||
/// confirms a submission was fraudulent.
|
||||
#[pezpallet::constant]
|
||||
type NoterBondAmount: Get<BalanceOf<Self>>;
|
||||
|
||||
/// How long a noter-submitted (signed-origin) staking update sits as a
|
||||
/// candidate before it takes effect, giving `DisputeOrigin` a window to
|
||||
/// freeze it. Root/XCM-Transact submissions are exempt.
|
||||
#[pezpallet::constant]
|
||||
type DisputeWindow: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// Origin allowed to dispute (freeze) a pending noter submission before it
|
||||
/// matures. Deliberately lightweight — any single authorized member.
|
||||
/// `Success = AccountId` so the disputing member's identity can be
|
||||
/// recorded in `Event::StakingDetailsDisputed`.
|
||||
type DisputeOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
|
||||
|
||||
/// Origin allowed to slash a noter's bond, after confirming a disputed
|
||||
/// submission was fraudulent. Deliberately a stronger, collective bar than
|
||||
/// `DisputeOrigin`.
|
||||
type SlashOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Where a slashed noter's bond goes.
|
||||
type SlashDestination: Get<Self::AccountId>;
|
||||
}
|
||||
|
||||
/// Balance type of `T::Currency` (the noter bond currency) — distinct from
|
||||
/// `T::Balance` above, which represents staking amounts, not reservable funds.
|
||||
pub type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
|
||||
|
||||
// --- Storage ---
|
||||
|
||||
#[pezpallet::storage]
|
||||
@@ -141,17 +219,73 @@ pub mod pezpallet {
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// Registered (bonded) noters, and the exact amount each reserved. An
|
||||
/// account must appear here — in addition to holding the Noter tiki — before
|
||||
/// `receive_staking_details` accepts their submissions.
|
||||
#[pezpallet::storage]
|
||||
#[pezpallet::getter(fn noter_bond)]
|
||||
pub type NoterBonds<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, OptionQuery>;
|
||||
|
||||
/// Block a noter last submitted data at. Gates `unregister_as_noter` until
|
||||
/// any submission they made has cleared its dispute window, so a noter can't
|
||||
/// submit fraudulent data and immediately withdraw their bond ahead of review.
|
||||
#[pezpallet::storage]
|
||||
pub type NoterLastSubmission<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
/// A noter-submitted staking update awaiting its dispute window. Does not
|
||||
/// affect `get_staking_score` until finalized into `CachedStakingDetails` —
|
||||
/// either opportunistically (before the same account's next submission),
|
||||
/// explicitly via `finalize_staking_details`, or discarded via
|
||||
/// `dispute_staking_details`. Root/XCM-Transact submissions skip this
|
||||
/// entirely and write `CachedStakingDetails` directly.
|
||||
#[pezpallet::storage]
|
||||
#[pezpallet::getter(fn pending_staking_details)]
|
||||
pub type PendingStakingDetails<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
Blake2_128Concat,
|
||||
StakingSource,
|
||||
PendingSubmission<T::Balance, T::AccountId, BlockNumberFor<T>>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
#[pezpallet::event]
|
||||
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// A user started time-based scoring.
|
||||
ScoreTrackingStarted { who: T::AccountId, start_block: BlockNumberFor<T> },
|
||||
/// Staking details received from a chain via XCM.
|
||||
/// Staking details took effect — either a Root/XCM-Transact submission
|
||||
/// (immediate) or a noter submission that has cleared its dispute window.
|
||||
StakingDetailsReceived {
|
||||
who: T::AccountId,
|
||||
source: StakingSource,
|
||||
staked_amount: T::Balance,
|
||||
},
|
||||
/// A noter-signed staking update was recorded as a pending candidate and
|
||||
/// will take effect at `matures_at` unless disputed before then.
|
||||
StakingDetailsPending {
|
||||
who: T::AccountId,
|
||||
source: StakingSource,
|
||||
staked_amount: T::Balance,
|
||||
submitted_by: T::AccountId,
|
||||
matures_at: BlockNumberFor<T>,
|
||||
},
|
||||
/// A pending noter submission was frozen before it could take effect.
|
||||
StakingDetailsDisputed {
|
||||
who: T::AccountId,
|
||||
source: StakingSource,
|
||||
disputed_by: T::AccountId,
|
||||
},
|
||||
/// An account registered as an active (bonded) noter.
|
||||
NoterRegistered { who: T::AccountId, bond: BalanceOf<T> },
|
||||
/// An account unregistered as noter and reclaimed its bond.
|
||||
NoterUnregistered { who: T::AccountId, bond_returned: BalanceOf<T> },
|
||||
/// A noter's bond was slashed by governance after a confirmed fraudulent
|
||||
/// submission.
|
||||
NoterSlashed { who: T::AccountId, amount: BalanceOf<T> },
|
||||
}
|
||||
|
||||
#[pezpallet::error]
|
||||
@@ -160,8 +294,19 @@ pub mod pezpallet {
|
||||
NoStakeFound,
|
||||
/// Score tracking has already been started for this account.
|
||||
TrackingAlreadyStarted,
|
||||
/// Caller does not have noter authority.
|
||||
/// Caller does not have noter authority (missing the Noter tiki).
|
||||
NotAuthorized,
|
||||
/// Caller holds the Noter tiki but has not posted the registration bond.
|
||||
NotRegisteredNoter,
|
||||
/// Caller has already registered (and bonded) as a noter.
|
||||
AlreadyRegisteredNoter,
|
||||
/// Cannot unregister while a submission is still within its dispute
|
||||
/// window — wait for it to mature or be disputed first.
|
||||
PendingSubmissionExists,
|
||||
/// No pending submission exists for this (account, source) pair.
|
||||
NoPendingSubmission,
|
||||
/// The pending submission's dispute window has not yet elapsed.
|
||||
NotYetMatured,
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
@@ -196,13 +341,11 @@ pub mod pezpallet {
|
||||
|
||||
/// Receive staking details for an account.
|
||||
///
|
||||
/// Accepts root origin (XCM Transact) or a signed origin from an account
|
||||
/// that holds the Noter tiki. This allows a noter-authorized bot to submit
|
||||
/// staking data collected from Relay Chain and Asset Hub.
|
||||
///
|
||||
/// If `staked_amount` is zero, the cached entry for the given source is
|
||||
/// removed. If no stake remains from any source, `StakingStartBlock` is
|
||||
/// also cleaned up, effectively resetting the user's staking score to zero.
|
||||
/// Root origin (XCM Transact) is chain-authenticated and takes effect
|
||||
/// immediately, exactly as before. A signed origin from a *registered*
|
||||
/// Noter-tiki holder instead records the update as a pending candidate
|
||||
/// (see `PendingStakingDetails`) that only takes effect after
|
||||
/// `DisputeWindow` blocks — see the module docs for why.
|
||||
#[pezpallet::call_index(1)]
|
||||
#[pezpallet::weight(T::WeightInfo::receive_staking_details())]
|
||||
pub fn receive_staking_details(
|
||||
@@ -213,31 +356,174 @@ pub mod pezpallet {
|
||||
nominations_count: u32,
|
||||
unlocking_chunks_count: u32,
|
||||
) -> DispatchResult {
|
||||
// Root (XCM Transact) OR noter-authorized signed origin.
|
||||
if ensure_root(origin.clone()).is_err() {
|
||||
let caller = ensure_signed(origin)?;
|
||||
ensure!(T::NoterChecker::is_noter(&caller), Error::<T>::NotAuthorized);
|
||||
if ensure_root(origin.clone()).is_ok() {
|
||||
Self::apply_staking_update(
|
||||
&who,
|
||||
source,
|
||||
staked_amount,
|
||||
nominations_count,
|
||||
unlocking_chunks_count,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if staked_amount.is_zero() {
|
||||
// Zero stake: remove the cached entry for this source.
|
||||
CachedStakingDetails::<T>::remove(&who, source);
|
||||
let caller = ensure_signed(origin)?;
|
||||
ensure!(T::NoterChecker::is_noter(&caller), Error::<T>::NotAuthorized);
|
||||
ensure!(NoterBonds::<T>::contains_key(&caller), Error::<T>::NotRegisteredNoter);
|
||||
|
||||
// Check if any stake remains from other sources.
|
||||
let remaining = Self::total_cached_stake(&who);
|
||||
if remaining.is_zero() {
|
||||
// No stake from any source — clean up tracking.
|
||||
StakingStartBlock::<T>::remove(&who);
|
||||
}
|
||||
} else {
|
||||
let details =
|
||||
StakingDetails { staked_amount, nominations_count, unlocking_chunks_count };
|
||||
CachedStakingDetails::<T>::insert(&who, source, details);
|
||||
// Opportunistically finalize a previously-matured pending entry for
|
||||
// this (who, source) before recording the new candidate. Without this,
|
||||
// a steady stream of legitimate noter updates would keep resetting the
|
||||
// dispute window and the account's effective stake would never
|
||||
// progress past whatever Root last committed (often zero).
|
||||
Self::maybe_finalize(&who, source);
|
||||
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
NoterLastSubmission::<T>::insert(&caller, current_block);
|
||||
|
||||
let details =
|
||||
StakingDetails { staked_amount, nominations_count, unlocking_chunks_count };
|
||||
PendingStakingDetails::<T>::insert(
|
||||
&who,
|
||||
source,
|
||||
PendingSubmission {
|
||||
details,
|
||||
submitted_by: caller.clone(),
|
||||
submitted_at: current_block,
|
||||
},
|
||||
);
|
||||
|
||||
Self::deposit_event(Event::StakingDetailsPending {
|
||||
who,
|
||||
source,
|
||||
staked_amount,
|
||||
submitted_by: caller,
|
||||
matures_at: current_block.saturating_add(T::DisputeWindow::get()),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register as an active noter: reserve `NoterBondAmount` and become
|
||||
/// eligible to submit staking data. Requires already holding the Noter
|
||||
/// tiki — this only adds the bond on top of that role, it does not grant
|
||||
/// the role itself (that remains a separate governance decision).
|
||||
#[pezpallet::call_index(2)]
|
||||
#[pezpallet::weight(T::WeightInfo::register_as_noter())]
|
||||
pub fn register_as_noter(origin: OriginFor<T>) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(T::NoterChecker::is_noter(&who), Error::<T>::NotAuthorized);
|
||||
ensure!(!NoterBonds::<T>::contains_key(&who), Error::<T>::AlreadyRegisteredNoter);
|
||||
|
||||
let bond = T::NoterBondAmount::get();
|
||||
T::Currency::reserve(&who, bond)?;
|
||||
NoterBonds::<T>::insert(&who, bond);
|
||||
|
||||
Self::deposit_event(Event::NoterRegistered { who, bond });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unregister as noter and reclaim the bond. Blocked while a submission
|
||||
/// this account made is still within its dispute window, so a noter can't
|
||||
/// submit fraudulent data and withdraw ahead of review.
|
||||
#[pezpallet::call_index(3)]
|
||||
#[pezpallet::weight(T::WeightInfo::unregister_as_noter())]
|
||||
pub fn unregister_as_noter(origin: OriginFor<T>) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let bond = NoterBonds::<T>::get(&who).ok_or(Error::<T>::NotRegisteredNoter)?;
|
||||
|
||||
if let Some(last_submission) = NoterLastSubmission::<T>::get(&who) {
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
ensure!(
|
||||
current_block >= last_submission.saturating_add(T::DisputeWindow::get()),
|
||||
Error::<T>::PendingSubmissionExists
|
||||
);
|
||||
}
|
||||
|
||||
T::OnStakingUpdate::on_staking_data_changed(&who);
|
||||
T::Currency::unreserve(&who, bond);
|
||||
NoterBonds::<T>::remove(&who);
|
||||
NoterLastSubmission::<T>::remove(&who);
|
||||
|
||||
Self::deposit_event(Event::StakingDetailsReceived { who, source, staked_amount });
|
||||
Self::deposit_event(Event::NoterUnregistered { who, bond_returned: bond });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Freeze a pending noter submission before it matures. Discards the
|
||||
/// candidate entirely — it does not become effective, and does not by
|
||||
/// itself slash the noter's bond (that is a separate governance decision
|
||||
/// via `slash_noter`, once the dispute has actually been reviewed).
|
||||
#[pezpallet::call_index(4)]
|
||||
#[pezpallet::weight(T::WeightInfo::dispute_staking_details())]
|
||||
pub fn dispute_staking_details(
|
||||
origin: OriginFor<T>,
|
||||
who: T::AccountId,
|
||||
source: StakingSource,
|
||||
) -> DispatchResult {
|
||||
let disputed_by = T::DisputeOrigin::ensure_origin(origin)?;
|
||||
ensure!(
|
||||
PendingStakingDetails::<T>::contains_key(&who, source),
|
||||
Error::<T>::NoPendingSubmission
|
||||
);
|
||||
|
||||
PendingStakingDetails::<T>::remove(&who, source);
|
||||
|
||||
Self::deposit_event(Event::StakingDetailsDisputed { who, source, disputed_by });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Commit a pending noter submission once its dispute window has elapsed.
|
||||
/// Permissionless (any signed, fee-paying account may call it) — like
|
||||
/// recording a notarized deed once its contestability period has passed,
|
||||
/// this is a mechanical step anyone can trigger, not a privileged action.
|
||||
#[pezpallet::call_index(5)]
|
||||
#[pezpallet::weight(T::WeightInfo::finalize_staking_details())]
|
||||
pub fn finalize_staking_details(
|
||||
origin: OriginFor<T>,
|
||||
who: T::AccountId,
|
||||
source: StakingSource,
|
||||
) -> DispatchResult {
|
||||
ensure_signed(origin)?;
|
||||
|
||||
let pending = PendingStakingDetails::<T>::get(&who, source)
|
||||
.ok_or(Error::<T>::NoPendingSubmission)?;
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
ensure!(
|
||||
current_block >= pending.submitted_at.saturating_add(T::DisputeWindow::get()),
|
||||
Error::<T>::NotYetMatured
|
||||
);
|
||||
|
||||
PendingStakingDetails::<T>::remove(&who, source);
|
||||
Self::apply_staking_update(
|
||||
&who,
|
||||
source,
|
||||
pending.details.staked_amount,
|
||||
pending.details.nominations_count,
|
||||
pending.details.unlocking_chunks_count,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Slash a noter's bond after governance confirms a disputed submission
|
||||
/// was fraudulent, and remove their registration. This is a deliberate,
|
||||
/// reviewed governance action (`SlashOrigin`) — never automatic — the
|
||||
/// same way a real notary's insurance bond is only drawn on after a
|
||||
/// finding of fault, not on a bare accusation.
|
||||
#[pezpallet::call_index(6)]
|
||||
#[pezpallet::weight(T::WeightInfo::slash_noter())]
|
||||
pub fn slash_noter(origin: OriginFor<T>, noter: T::AccountId) -> DispatchResult {
|
||||
T::SlashOrigin::ensure_origin(origin)?;
|
||||
|
||||
let bond = NoterBonds::<T>::take(¬er).ok_or(Error::<T>::NotRegisteredNoter)?;
|
||||
NoterLastSubmission::<T>::remove(¬er);
|
||||
|
||||
T::Currency::unreserve(¬er, bond);
|
||||
T::Currency::transfer(
|
||||
¬er,
|
||||
&T::SlashDestination::get(),
|
||||
bond,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
)?;
|
||||
|
||||
Self::deposit_event(Event::NoterSlashed { who: noter, amount: bond });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -266,6 +552,16 @@ pub mod pezpallet {
|
||||
pub unlocking_chunks_count: u32,
|
||||
}
|
||||
|
||||
/// A noter-submitted staking update awaiting its dispute window.
|
||||
#[derive(
|
||||
Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, TypeInfo, Debug, MaxEncodedLen,
|
||||
)]
|
||||
pub struct PendingSubmission<Balance, AccountId, BlockNumber> {
|
||||
pub details: StakingDetails<Balance>,
|
||||
pub submitted_by: AccountId,
|
||||
pub submitted_at: BlockNumber,
|
||||
}
|
||||
|
||||
// --- Traits ---
|
||||
|
||||
/// Interface for querying staking scores. Used by trust pallet.
|
||||
@@ -295,6 +591,77 @@ pub mod pezpallet {
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
/// Actually apply a staking update to `CachedStakingDetails`. This is the
|
||||
/// single place data becomes *effective* — called directly for Root/XCM
|
||||
/// submissions, and via `maybe_finalize` once a noter submission's
|
||||
/// dispute window has elapsed. The anti flash-stake duration-reset guard
|
||||
/// lives here so it fires when data becomes effective, not at submission
|
||||
/// time (which, for noter submissions, is not yet trustworthy).
|
||||
fn apply_staking_update(
|
||||
who: &T::AccountId,
|
||||
source: StakingSource,
|
||||
staked_amount: T::Balance,
|
||||
nominations_count: u32,
|
||||
unlocking_chunks_count: u32,
|
||||
) {
|
||||
let previous_total = Self::total_cached_stake(who);
|
||||
|
||||
if staked_amount.is_zero() {
|
||||
CachedStakingDetails::<T>::remove(who, source);
|
||||
|
||||
let remaining = Self::total_cached_stake(who);
|
||||
if remaining.is_zero() {
|
||||
StakingStartBlock::<T>::remove(who);
|
||||
}
|
||||
} else {
|
||||
let details =
|
||||
StakingDetails { staked_amount, nominations_count, unlocking_chunks_count };
|
||||
CachedStakingDetails::<T>::insert(who, source, details);
|
||||
|
||||
let new_total = Self::total_cached_stake(who);
|
||||
if !previous_total.is_zero()
|
||||
&& new_total > previous_total
|
||||
&& StakingStartBlock::<T>::contains_key(who)
|
||||
{
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
StakingStartBlock::<T>::insert(who, current_block);
|
||||
}
|
||||
}
|
||||
|
||||
T::OnStakingUpdate::on_staking_data_changed(who);
|
||||
Self::deposit_event(Event::StakingDetailsReceived {
|
||||
who: who.clone(),
|
||||
source,
|
||||
staked_amount,
|
||||
});
|
||||
}
|
||||
|
||||
/// If a pending noter submission for `(who, source)` exists and has
|
||||
/// cleared its dispute window, commit it via `apply_staking_update` and
|
||||
/// clear the pending slot. Returns `true` if it finalized something.
|
||||
/// No-op (returns `false`) if there's nothing pending, or it hasn't
|
||||
/// matured yet — callers that need to distinguish those two cases (like
|
||||
/// `finalize_staking_details`) check the storage directly instead.
|
||||
fn maybe_finalize(who: &T::AccountId, source: StakingSource) -> bool {
|
||||
let Some(pending) = PendingStakingDetails::<T>::get(who, source) else {
|
||||
return false;
|
||||
};
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
if current_block < pending.submitted_at.saturating_add(T::DisputeWindow::get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PendingStakingDetails::<T>::remove(who, source);
|
||||
Self::apply_staking_update(
|
||||
who,
|
||||
source,
|
||||
pending.details.staked_amount,
|
||||
pending.details.nominations_count,
|
||||
pending.details.unlocking_chunks_count,
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// --- StakingScoreProvider Implementation ---
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
|
||||
use crate as pezpallet_staking_score;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types, traits::ConstU32,
|
||||
construct_runtime, derive_impl, parameter_types,
|
||||
traits::{ConstU32, SortedMembers},
|
||||
weights::constants::RocksDbWeight,
|
||||
};
|
||||
use pezframe_system::EnsureSignedBy;
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
use crate::UNITS;
|
||||
@@ -21,10 +23,45 @@ pub type AccountId = u64;
|
||||
pub type Balance = u128;
|
||||
pub type BlockNumber = u64;
|
||||
|
||||
// --- Test-only well-known accounts ---
|
||||
/// A second, independent registered noter (distinct from account 99, the
|
||||
/// pre-existing "the" noter in most tests) — used by dispute-window tests
|
||||
/// that need two noters to exist simultaneously, mirroring how the Noter
|
||||
/// tiki is designed to support any number of accounts holding it.
|
||||
pub const NOTER_2: AccountId = 98;
|
||||
/// A Council-equivalent member for `DisputeOrigin` in tests.
|
||||
pub const DISPUTE_MEMBER: AccountId = 40;
|
||||
/// A stronger governance-equivalent member for `SlashOrigin` in tests.
|
||||
pub const SLASH_MEMBER: AccountId = 41;
|
||||
/// Where slashed noter bonds land in tests (stands in for `RelayTreasuryAccount`).
|
||||
pub const TREASURY: AccountId = 999;
|
||||
/// A short, test-friendly dispute window — real enough to prove the
|
||||
/// mechanism (tests advance blocks past it) without needing huge block jumps
|
||||
/// in every test. The runtime uses `HOUR_IN_BLOCKS` (600 blocks); this mock
|
||||
/// deliberately does not reuse that constant so tests stay fast.
|
||||
pub const DISPUTE_WINDOW: BlockNumber = 10;
|
||||
|
||||
// --- Constants ---
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: BlockNumber = 250;
|
||||
pub const ExistentialDeposit: Balance = 1;
|
||||
pub const NoterBondAmount: Balance = 1_000 * UNITS;
|
||||
pub const DisputeWindowBlocks: BlockNumber = DISPUTE_WINDOW;
|
||||
pub const SlashDestinationAccount: AccountId = TREASURY;
|
||||
}
|
||||
|
||||
pub struct DisputeMemberProvider;
|
||||
impl SortedMembers<AccountId> for DisputeMemberProvider {
|
||||
fn sorted_members() -> Vec<AccountId> {
|
||||
vec![DISPUTE_MEMBER]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SlashMemberProvider;
|
||||
impl SortedMembers<AccountId> for SlashMemberProvider {
|
||||
fn sorted_members() -> Vec<AccountId> {
|
||||
vec![SLASH_MEMBER]
|
||||
}
|
||||
}
|
||||
|
||||
// --- Runtime ---
|
||||
@@ -53,11 +90,11 @@ impl pezpallet_balances::Config for Test {
|
||||
}
|
||||
|
||||
/// Mock noter checker for tests.
|
||||
/// Account 99 is noter, everyone else is not.
|
||||
/// Accounts 99 and 98 (`NOTER_2`) are noters, everyone else is not.
|
||||
pub struct MockNoterChecker;
|
||||
impl crate::NoterCheck<AccountId> for MockNoterChecker {
|
||||
fn is_noter(who: &AccountId) -> bool {
|
||||
*who == 99
|
||||
*who == 99 || *who == NOTER_2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +103,12 @@ impl crate::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type OnStakingUpdate = ();
|
||||
type NoterChecker = MockNoterChecker;
|
||||
type Currency = Balances;
|
||||
type NoterBondAmount = NoterBondAmount;
|
||||
type DisputeWindow = DisputeWindowBlocks;
|
||||
type DisputeOrigin = EnsureSignedBy<DisputeMemberProvider, AccountId>;
|
||||
type SlashOrigin = EnsureSignedBy<SlashMemberProvider, AccountId>;
|
||||
type SlashDestination = SlashDestinationAccount;
|
||||
}
|
||||
|
||||
// --- ExtBuilder ---
|
||||
@@ -89,7 +132,8 @@ impl ExtBuilder {
|
||||
(10, 1_000_000 * UNITS),
|
||||
(20, 100_000 * UNITS),
|
||||
(30, 100_000 * UNITS), // Charlie
|
||||
(99, 100_000 * UNITS), // NOTER
|
||||
(99, 100_000 * UNITS), // NOTER — enough to post NoterBondAmount (1,000 UNITS)
|
||||
(NOTER_2, 100_000 * UNITS),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
@@ -103,3 +147,49 @@ impl ExtBuilder {
|
||||
self.build().execute_with(test);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Test helpers: bonded-noter registration + dispute-window resolution ---
|
||||
|
||||
/// Register `noter` (idempotent — no-op if already registered) as a bonded,
|
||||
/// active noter. Every noter-signed `receive_staking_details` call in tests
|
||||
/// now requires this first, matching the real pallet's `register_as_noter`
|
||||
/// gate.
|
||||
pub fn ensure_registered(noter: AccountId) {
|
||||
if crate::NoterBonds::<Test>::get(noter).is_none() {
|
||||
assert!(
|
||||
StakingScore::register_as_noter(RuntimeOrigin::signed(noter)).is_ok(),
|
||||
"test setup: register_as_noter failed for {noter}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Submit staking details as `noter`, then advance the block number past
|
||||
/// `DisputeWindow` and finalize — i.e. simulate a noter submission that goes
|
||||
/// unchallenged and takes effect. This is the noter-path equivalent of the
|
||||
/// old (pre dispute-window) immediate-commit behavior most tests want.
|
||||
pub fn submit_and_finalize(
|
||||
noter: AccountId,
|
||||
who: AccountId,
|
||||
source: crate::StakingSource,
|
||||
staked_amount: Balance,
|
||||
nominations_count: u32,
|
||||
unlocking_chunks_count: u32,
|
||||
) {
|
||||
ensure_registered(noter);
|
||||
assert!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(noter),
|
||||
who,
|
||||
source,
|
||||
staked_amount,
|
||||
nominations_count,
|
||||
unlocking_chunks_count,
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let matured_at = System::block_number() + DISPUTE_WINDOW;
|
||||
System::set_block_number(matured_at);
|
||||
assert!(
|
||||
StakingScore::finalize_staking_details(RuntimeOrigin::signed(noter), who, source).is_ok(),
|
||||
"test setup: finalize_staking_details failed"
|
||||
);
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
//! mirroring the real People Chain architecture.
|
||||
|
||||
use crate::{
|
||||
mock::*, CachedStakingDetails, Error, Event, StakingScoreProvider, StakingSource,
|
||||
StakingStartBlock, MONTH_IN_BLOCKS, UNITS,
|
||||
mock::*, CachedStakingDetails, Error, Event, NoterBonds, PendingStakingDetails,
|
||||
StakingScoreProvider, StakingSource, StakingStartBlock, MONTH_IN_BLOCKS, UNITS,
|
||||
};
|
||||
use pezframe_support::{assert_noop, assert_ok};
|
||||
|
||||
@@ -565,6 +565,17 @@ const NOTER: AccountId = 99; // MockNoterChecker recognizes 99 as noter
|
||||
#[test]
|
||||
fn noter_can_submit_staking_details() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 30);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noter_signed_submission_is_pending_not_immediate() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(1);
|
||||
ensure_registered(NOTER);
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
@@ -574,7 +585,15 @@ fn noter_can_submit_staking_details() {
|
||||
0
|
||||
));
|
||||
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 30);
|
||||
// Not yet effective: sits in PendingStakingDetails, not CachedStakingDetails.
|
||||
assert!(CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_none());
|
||||
assert!(PendingStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_some());
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 0);
|
||||
|
||||
let events = System::events();
|
||||
assert!(events.iter().any(|event| {
|
||||
matches!(event.event, RuntimeEvent::StakingScore(Event::StakingDetailsPending { .. }))
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -602,25 +621,11 @@ fn root_can_still_submit_staking_details() {
|
||||
fn zero_stake_removes_cached_entry() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
// Setup: noter submits 200 HEZ for relay chain
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
assert!(CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_some());
|
||||
|
||||
// Zero stake removes the entry
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
assert!(CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_none());
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 0);
|
||||
});
|
||||
@@ -634,24 +639,10 @@ fn zero_stake_cleans_up_tracking_when_no_stake_remains() {
|
||||
assert!(StakingStartBlock::<Test>::get(USER_STASH).is_some());
|
||||
|
||||
// Noter submits stake
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
|
||||
// Zero out the only source → StakingStartBlock should be cleaned up
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
assert!(StakingStartBlock::<Test>::get(USER_STASH).is_none());
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 0);
|
||||
});
|
||||
@@ -664,34 +655,13 @@ fn zero_stake_one_source_keeps_tracking_if_other_source_has_stake() {
|
||||
assert_ok!(StakingScore::start_score_tracking(RuntimeOrigin::signed(USER_STASH)));
|
||||
|
||||
// Noter submits stake for both sources
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::AssetHub,
|
||||
150 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 100 * UNITS, 0, 0);
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::AssetHub, 150 * UNITS, 0, 0);
|
||||
// Total 250 HEZ → tier 30
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 30);
|
||||
|
||||
// Zero out relay chain only
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
|
||||
// Tracking preserved (AssetHub still has stake)
|
||||
assert!(StakingStartBlock::<Test>::get(USER_STASH).is_some());
|
||||
@@ -715,19 +685,12 @@ fn full_workflow_ali_scenario() {
|
||||
assert_eq!(StakingScore::get_staking_score(&ali).0, 0); // No data yet
|
||||
|
||||
// 2. Bot + noter submit Ali's 200 HEZ relay stake
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
ali,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, ali, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
// 200 HEZ → tier 30, duration < 1 month → x1.0 → 30
|
||||
assert_eq!(StakingScore::get_staking_score(&ali).0, 30);
|
||||
|
||||
// 3. After 28 days → still < 1 month → x1.0 → 30
|
||||
System::set_block_number(1000 + 28 * 24 * 60 * 10);
|
||||
System::set_block_number(1000 + 28 * 24 * 60 * 5);
|
||||
assert_eq!(StakingScore::get_staking_score(&ali).0, 30);
|
||||
});
|
||||
}
|
||||
@@ -739,25 +702,11 @@ fn full_workflow_bob_unbond_scenario() {
|
||||
|
||||
// Bob opts in and has stake
|
||||
assert_ok!(StakingScore::start_score_tracking(RuntimeOrigin::signed(bob)));
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
bob,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, bob, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
assert_eq!(StakingScore::get_staking_score(&bob).0, 30);
|
||||
|
||||
// Bob unbonds → noter reports zero
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
bob,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, bob, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
|
||||
// Score = 0, tracking cleaned up
|
||||
assert_eq!(StakingScore::get_staking_score(&bob).0, 0);
|
||||
@@ -775,56 +724,32 @@ fn full_workflow_charlie_dual_chain_partial_unbond() {
|
||||
assert_ok!(StakingScore::start_score_tracking(RuntimeOrigin::signed(charlie)));
|
||||
|
||||
// Noter submits both sources: 100 relay + 150 asset hub = 250 HEZ
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
charlie,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
charlie,
|
||||
StakingSource::AssetHub,
|
||||
150 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, charlie, StakingSource::RelayChain, 100 * UNITS, 0, 0);
|
||||
// The anti flash-stake guard restarts StakingStartBlock on this second,
|
||||
// stake-increasing submission — read it back instead of assuming 500.
|
||||
submit_and_finalize(NOTER, charlie, StakingSource::AssetHub, 150 * UNITS, 0, 0);
|
||||
// 250 HEZ → tier 30
|
||||
assert_eq!(StakingScore::get_staking_score(&charlie).0, 30);
|
||||
let effective_start = StakingStartBlock::<Test>::get(charlie).unwrap();
|
||||
|
||||
// Charlie unbonds from Relay Chain
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
charlie,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, charlie, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
|
||||
// Relay entry removed, Asset Hub remains
|
||||
assert!(CachedStakingDetails::<Test>::get(charlie, StakingSource::RelayChain).is_none());
|
||||
assert!(CachedStakingDetails::<Test>::get(charlie, StakingSource::AssetHub).is_some());
|
||||
// Tracking preserved
|
||||
assert!(StakingStartBlock::<Test>::get(charlie).is_some());
|
||||
// Tracking preserved, unaffected by the zero-stake unbond above
|
||||
// (the guard only fires on stake *increases*, not removals).
|
||||
assert_eq!(StakingStartBlock::<Test>::get(charlie), Some(effective_start));
|
||||
// 150 HEZ → tier 30 (still in 101-250 range)
|
||||
assert_eq!(StakingScore::get_staking_score(&charlie).0, 30);
|
||||
|
||||
// After 3 months: 30 * 1.4 = 42
|
||||
System::set_block_number(500 + (3 * MONTH_IN_BLOCKS) as u64);
|
||||
// After 3 months from the effective start: 30 * 1.4 = 42
|
||||
System::set_block_number(effective_start + (3 * MONTH_IN_BLOCKS) as u64);
|
||||
assert_eq!(StakingScore::get_staking_score(&charlie).0, 42);
|
||||
|
||||
// Charlie unbonds from Asset Hub too → fully zeroed
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
charlie,
|
||||
StakingSource::AssetHub,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, charlie, StakingSource::AssetHub, 0u128, 0, 0);
|
||||
assert!(StakingStartBlock::<Test>::get(charlie).is_none());
|
||||
assert_eq!(StakingScore::get_staking_score(&charlie).0, 0);
|
||||
});
|
||||
@@ -842,22 +767,16 @@ fn duration_counts_from_optin_not_from_data_arrival() {
|
||||
assert_ok!(StakingScore::start_score_tracking(RuntimeOrigin::signed(USER_STASH)));
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH), (0, 0u64));
|
||||
|
||||
// Block 50_000: Bot + noter submit data (much later)
|
||||
// Block 50_000: Bot + noter submit data (much later). It finalizes
|
||||
// DISPUTE_WINDOW blocks after submission (block 50_010), not at 50_000.
|
||||
System::set_block_number(50_000);
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
|
||||
// Duration is from block 100, NOT from block 50_000.
|
||||
// 50_000 - 100 = 49_900 blocks. MONTH_IN_BLOCKS = 432_000
|
||||
// 49_900 < 432_000 → x1.0 → 30
|
||||
// Duration is from block 100, NOT from block 50_000 (or 50_010).
|
||||
// 50_010 - 100 = 49_910 blocks. MONTH_IN_BLOCKS = 432_000
|
||||
// 49_910 < 432_000 → x1.0 → 30
|
||||
let (score, duration) = StakingScore::get_staking_score(&USER_STASH);
|
||||
assert_eq!(duration, 49_900);
|
||||
assert_eq!(duration, 49_910);
|
||||
assert_eq!(score, 30);
|
||||
|
||||
// After reaching 1 month from opt-in: 100 + 432_000 = 432_100
|
||||
@@ -878,25 +797,11 @@ fn re_optin_after_full_unbond() {
|
||||
// Phase 1: opt-in + stake + score
|
||||
System::set_block_number(100);
|
||||
assert_ok!(StakingScore::start_score_tracking(RuntimeOrigin::signed(USER_STASH)));
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 30);
|
||||
|
||||
// Phase 2: full unbond → cleanup
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
assert!(StakingStartBlock::<Test>::get(USER_STASH).is_none());
|
||||
|
||||
// Phase 3: re-opt-in at block 1000 (fresh start)
|
||||
@@ -905,14 +810,7 @@ fn re_optin_after_full_unbond() {
|
||||
assert_eq!(StakingStartBlock::<Test>::get(USER_STASH), Some(1000));
|
||||
|
||||
// Phase 4: new stake data
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
500 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 500 * UNITS, 0, 0);
|
||||
// 500 HEZ → tier 40, duration from block 1000
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 40);
|
||||
|
||||
@@ -939,30 +837,9 @@ fn noter_batch_multiple_users() {
|
||||
assert_ok!(StakingScore::start_score_tracking(RuntimeOrigin::signed(user3)));
|
||||
|
||||
// Noter submits batch: different amounts for each user
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
user1,
|
||||
StakingSource::RelayChain,
|
||||
50 * UNITS, // tier 20
|
||||
0,
|
||||
0
|
||||
));
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
user2,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS, // tier 30
|
||||
0,
|
||||
0
|
||||
));
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
user3,
|
||||
StakingSource::AssetHub,
|
||||
800 * UNITS, // tier 50
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, user1, StakingSource::RelayChain, 50 * UNITS, 0, 0); // tier 20
|
||||
submit_and_finalize(NOTER, user2, StakingSource::RelayChain, 200 * UNITS, 0, 0); // tier 30
|
||||
submit_and_finalize(NOTER, user3, StakingSource::AssetHub, 800 * UNITS, 0, 0); // tier 50
|
||||
|
||||
assert_eq!(StakingScore::get_staking_score(&user1).0, 20);
|
||||
assert_eq!(StakingScore::get_staking_score(&user2).0, 30);
|
||||
@@ -980,14 +857,7 @@ fn data_without_optin_still_cached_but_no_duration() {
|
||||
System::set_block_number(100);
|
||||
|
||||
// Noter submits data for user who hasn't opted in
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
300 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 300 * UNITS, 0, 0);
|
||||
|
||||
// Data is cached → score is base tier only (no duration)
|
||||
assert!(CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_some());
|
||||
@@ -1138,24 +1008,10 @@ fn zero_stake_emits_event_with_zero_amount() {
|
||||
System::set_block_number(1);
|
||||
|
||||
// Setup
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
|
||||
// Zero-stake
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
|
||||
let events = System::events();
|
||||
// Last event should be StakingDetailsReceived with amount 0
|
||||
@@ -1184,14 +1040,7 @@ fn zero_stake_emits_event_with_zero_amount() {
|
||||
fn noter_overwrites_previous_submission() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
// First submission: 100 HEZ
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS,
|
||||
5,
|
||||
2
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 100 * UNITS, 5, 2);
|
||||
let details =
|
||||
CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).unwrap();
|
||||
assert_eq!(details.staked_amount, 100 * UNITS);
|
||||
@@ -1199,14 +1048,7 @@ fn noter_overwrites_previous_submission() {
|
||||
assert_eq!(details.unlocking_chunks_count, 2);
|
||||
|
||||
// Second submission: updated values
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
300 * UNITS,
|
||||
10,
|
||||
1
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 300 * UNITS, 10, 1);
|
||||
let details =
|
||||
CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).unwrap();
|
||||
assert_eq!(details.staked_amount, 300 * UNITS);
|
||||
@@ -1223,14 +1065,7 @@ fn noter_overwrites_previous_submission() {
|
||||
fn zero_stake_for_nonexistent_source_is_noop() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
// Zero-stake for a source that was never set — should be a no-op
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
0u128,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 0u128, 0, 0);
|
||||
|
||||
// No ghost entries
|
||||
assert!(CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_none());
|
||||
@@ -1251,30 +1086,21 @@ fn max_score_scenario() {
|
||||
assert_ok!(StakingScore::start_score_tracking(RuntimeOrigin::signed(USER_STASH)));
|
||||
|
||||
// Dual-chain max: 500 relay + 500 asset hub = 1000 HEZ → tier 50
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
500 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::AssetHub,
|
||||
500 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 500 * UNITS, 0, 0);
|
||||
// The anti flash-stake guard (pre-existing, see receive_staking_details'
|
||||
// docs) restarts StakingStartBlock on this second, stake-increasing
|
||||
// submission — so "start" for duration purposes is wherever this
|
||||
// finalizes, not block 1. Read it back instead of assuming block 1.
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::AssetHub, 500 * UNITS, 0, 0);
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 50);
|
||||
let effective_start = StakingStartBlock::<Test>::get(USER_STASH).unwrap();
|
||||
|
||||
// 12+ months → 50 * 2.0 = 100 (capped)
|
||||
System::set_block_number(1 + (12 * MONTH_IN_BLOCKS) as u64);
|
||||
System::set_block_number(effective_start + (12 * MONTH_IN_BLOCKS) as u64);
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 100);
|
||||
|
||||
// 24 months → still 100 (cap)
|
||||
System::set_block_number(1 + (24 * MONTH_IN_BLOCKS) as u64);
|
||||
System::set_block_number(effective_start + (24 * MONTH_IN_BLOCKS) as u64);
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 100);
|
||||
});
|
||||
}
|
||||
@@ -1322,3 +1148,407 @@ fn duration_exact_month_boundaries() {
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 40);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Bonded Noter Registration
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn unregistered_noter_cannot_submit() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
// NOTER holds the tiki (MockNoterChecker recognizes 99) but has not
|
||||
// called register_as_noter — submissions must be rejected.
|
||||
assert_noop!(
|
||||
StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS,
|
||||
0,
|
||||
0
|
||||
),
|
||||
Error::<Test>::NotRegisteredNoter
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_as_noter_reserves_bond() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(1);
|
||||
let free_before = Balances::free_balance(NOTER);
|
||||
|
||||
assert_ok!(StakingScore::register_as_noter(RuntimeOrigin::signed(NOTER)));
|
||||
|
||||
assert_eq!(NoterBonds::<Test>::get(NOTER), Some(NoterBondAmount::get()));
|
||||
assert_eq!(Balances::reserved_balance(NOTER), NoterBondAmount::get());
|
||||
assert_eq!(Balances::free_balance(NOTER), free_before - NoterBondAmount::get());
|
||||
|
||||
let events = System::events();
|
||||
assert!(events.iter().any(|event| {
|
||||
matches!(event.event, RuntimeEvent::StakingScore(Event::NoterRegistered { .. }))
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_as_noter_rejects_non_tiki_holder() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
// USER_STASH does not hold the Noter tiki per MockNoterChecker.
|
||||
assert_noop!(
|
||||
StakingScore::register_as_noter(RuntimeOrigin::signed(USER_STASH)),
|
||||
Error::<Test>::NotAuthorized
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_as_noter_rejects_double_registration() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
assert_ok!(StakingScore::register_as_noter(RuntimeOrigin::signed(NOTER)));
|
||||
assert_noop!(
|
||||
StakingScore::register_as_noter(RuntimeOrigin::signed(NOTER)),
|
||||
Error::<Test>::AlreadyRegisteredNoter
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_as_noter_returns_bond_when_no_pending_activity() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
let free_before = Balances::free_balance(NOTER);
|
||||
assert_ok!(StakingScore::register_as_noter(RuntimeOrigin::signed(NOTER)));
|
||||
|
||||
assert_ok!(StakingScore::unregister_as_noter(RuntimeOrigin::signed(NOTER)));
|
||||
|
||||
assert!(NoterBonds::<Test>::get(NOTER).is_none());
|
||||
assert_eq!(Balances::reserved_balance(NOTER), 0);
|
||||
assert_eq!(Balances::free_balance(NOTER), free_before);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_as_noter_blocked_within_dispute_window() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(100);
|
||||
ensure_registered(NOTER);
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
// Still inside the window: blocked.
|
||||
assert_noop!(
|
||||
StakingScore::unregister_as_noter(RuntimeOrigin::signed(NOTER)),
|
||||
Error::<Test>::PendingSubmissionExists
|
||||
);
|
||||
|
||||
// After the window elapses: allowed.
|
||||
System::set_block_number(100 + DISPUTE_WINDOW);
|
||||
assert_ok!(StakingScore::unregister_as_noter(RuntimeOrigin::signed(NOTER)));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregister_as_noter_rejects_never_registered() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
assert_noop!(
|
||||
StakingScore::unregister_as_noter(RuntimeOrigin::signed(USER_STASH)),
|
||||
Error::<Test>::NotRegisteredNoter
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Dispute Window
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn finalize_before_window_elapses_fails() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(100);
|
||||
ensure_registered(NOTER);
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
// One block before maturity.
|
||||
System::set_block_number(100 + DISPUTE_WINDOW - 1);
|
||||
assert_noop!(
|
||||
StakingScore::finalize_staking_details(
|
||||
RuntimeOrigin::signed(USER_STASH),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
),
|
||||
Error::<Test>::NotYetMatured
|
||||
);
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_is_permissionless() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(100);
|
||||
ensure_registered(NOTER);
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
System::set_block_number(100 + DISPUTE_WINDOW);
|
||||
// Finalized by an unrelated bystander account, not the noter or the
|
||||
// affected user — anyone may trigger it once matured.
|
||||
let bystander = 77;
|
||||
assert_ok!(StakingScore::finalize_staking_details(
|
||||
RuntimeOrigin::signed(bystander),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
));
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 20);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_with_nothing_pending_fails() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
assert_noop!(
|
||||
StakingScore::finalize_staking_details(
|
||||
RuntimeOrigin::signed(USER_STASH),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
),
|
||||
Error::<Test>::NoPendingSubmission
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispute_discards_pending_submission_before_it_takes_effect() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(100);
|
||||
ensure_registered(NOTER);
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
500 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
assert_ok!(StakingScore::dispute_staking_details(
|
||||
RuntimeOrigin::signed(DISPUTE_MEMBER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
));
|
||||
|
||||
// Even well past the window, a disputed submission never takes effect.
|
||||
System::set_block_number(100 + DISPUTE_WINDOW * 10);
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 0);
|
||||
assert_noop!(
|
||||
StakingScore::finalize_staking_details(
|
||||
RuntimeOrigin::signed(USER_STASH),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
),
|
||||
Error::<Test>::NoPendingSubmission
|
||||
);
|
||||
|
||||
let events = System::events();
|
||||
assert!(events.iter().any(|event| {
|
||||
matches!(event.event, RuntimeEvent::StakingScore(Event::StakingDetailsDisputed { .. }))
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispute_rejects_non_dispute_origin() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
ensure_registered(NOTER);
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
500 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
// A random signed account (not DISPUTE_MEMBER, not Root) cannot dispute.
|
||||
assert_noop!(
|
||||
StakingScore::dispute_staking_details(
|
||||
RuntimeOrigin::signed(USER_STASH),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
),
|
||||
pezsp_runtime::DispatchError::BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispute_with_nothing_pending_fails() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
assert_noop!(
|
||||
StakingScore::dispute_staking_details(
|
||||
RuntimeOrigin::signed(DISPUTE_MEMBER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
),
|
||||
Error::<Test>::NoPendingSubmission
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeated_legitimate_submissions_do_not_regress_score_during_window() {
|
||||
// A noter sending a second update before anyone finalized the first
|
||||
// shouldn't drop the account's effective score back to 0 for the second
|
||||
// update's own window — `receive_staking_details` opportunistically
|
||||
// finalizes an already-matured prior pending entry before recording the
|
||||
// new one, so the first update's result is never lost.
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(100);
|
||||
ensure_registered(NOTER);
|
||||
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
100 * UNITS, // tier 20
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
// First submission's window has matured, but nobody called
|
||||
// finalize_staking_details yet.
|
||||
System::set_block_number(100 + DISPUTE_WINDOW);
|
||||
|
||||
// A second submission arrives before anyone finalized the first.
|
||||
// Its own `receive_staking_details` call must opportunistically
|
||||
// commit the first (matured) value before recording this candidate.
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::signed(NOTER),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
300 * UNITS, // tier 40
|
||||
0,
|
||||
0
|
||||
));
|
||||
// The first submission's value is now visible via CachedStakingDetails
|
||||
// — not 0, even though the second submission's own window hasn't
|
||||
// elapsed yet.
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 20);
|
||||
|
||||
// Second submission's window elapses; finalize it explicitly.
|
||||
System::set_block_number(100 + DISPUTE_WINDOW + DISPUTE_WINDOW);
|
||||
assert_ok!(StakingScore::finalize_staking_details(
|
||||
RuntimeOrigin::signed(USER_STASH),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain
|
||||
));
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 40);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Root Path Unaffected (immediate, no bond, no window)
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn root_submission_is_immediate_and_needs_no_bond() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
// USER_STASH is not a registered noter at all — Root doesn't care.
|
||||
assert_ok!(StakingScore::receive_staking_details(
|
||||
RuntimeOrigin::root(),
|
||||
USER_STASH,
|
||||
StakingSource::RelayChain,
|
||||
200 * UNITS,
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
assert!(PendingStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_none());
|
||||
assert!(CachedStakingDetails::<Test>::get(USER_STASH, StakingSource::RelayChain).is_some());
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 30);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Slashing
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn slash_noter_burns_bond_to_treasury() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
System::set_block_number(1);
|
||||
assert_ok!(StakingScore::register_as_noter(RuntimeOrigin::signed(NOTER)));
|
||||
let treasury_before = Balances::free_balance(TREASURY);
|
||||
let bond = NoterBondAmount::get();
|
||||
|
||||
assert_ok!(StakingScore::slash_noter(RuntimeOrigin::signed(SLASH_MEMBER), NOTER));
|
||||
|
||||
assert!(NoterBonds::<Test>::get(NOTER).is_none());
|
||||
assert_eq!(Balances::reserved_balance(NOTER), 0);
|
||||
assert_eq!(Balances::free_balance(TREASURY), treasury_before + bond);
|
||||
|
||||
let events = System::events();
|
||||
assert!(events.iter().any(|event| {
|
||||
matches!(event.event, RuntimeEvent::StakingScore(Event::NoterSlashed { .. }))
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_noter_rejects_non_slash_origin() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
assert_ok!(StakingScore::register_as_noter(RuntimeOrigin::signed(NOTER)));
|
||||
|
||||
// DISPUTE_MEMBER can freeze a submission but cannot slash — that's a
|
||||
// deliberately stronger bar (SlashOrigin), not the lightweight
|
||||
// DisputeOrigin.
|
||||
assert_noop!(
|
||||
StakingScore::slash_noter(RuntimeOrigin::signed(DISPUTE_MEMBER), NOTER),
|
||||
pezsp_runtime::DispatchError::BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_noter_rejects_unregistered_account() {
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
assert_noop!(
|
||||
StakingScore::slash_noter(RuntimeOrigin::signed(SLASH_MEMBER), USER_STASH),
|
||||
Error::<Test>::NotRegisteredNoter
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_second_independent_noter_can_register_and_submit() {
|
||||
// The system is designed to support any number of independently
|
||||
// registered noters, not a single hardcoded account.
|
||||
ExtBuilder.build_and_execute(|| {
|
||||
submit_and_finalize(NOTER, USER_STASH, StakingSource::RelayChain, 100 * UNITS, 0, 0);
|
||||
submit_and_finalize(NOTER_2, 20, StakingSource::RelayChain, 200 * UNITS, 0, 0);
|
||||
|
||||
assert_eq!(StakingScore::get_staking_score(&USER_STASH).0, 20);
|
||||
assert_eq!(StakingScore::get_staking_score(&20).0, 30);
|
||||
assert!(NoterBonds::<Test>::get(NOTER).is_some());
|
||||
assert!(NoterBonds::<Test>::get(NOTER_2).is_some());
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -15,14 +15,34 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Manually estimated weights for `pezpallet_staking_score`
|
||||
|
||||
//! Autogenerated weights for `pezpallet_staking_score`
|
||||
//!
|
||||
//! These weights are conservative overestimates pending proper benchmark runs.
|
||||
//! They account for the `OnStakingUpdate` callback cost (trust pallet update).
|
||||
//!
|
||||
//! DATE: 2026-02-16
|
||||
//! UPDATED: 2026-02-16 (noter delegation + zero-stake cleanup)
|
||||
//! TODO: Run proper benchmarks to replace these estimates.
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.1
|
||||
//! DATE: 2026-07-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `vmi3386610`, CPU: `AMD EPYC Processor (with IBPB)`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// target/release/pezframe-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --runtime
|
||||
// target/release/wbuild/people-pezkuwichain-runtime/people_pezkuwichain_runtime.compact.compressed.wasm
|
||||
// --pallet
|
||||
// pezpallet_staking_score
|
||||
// --extrinsic
|
||||
// *
|
||||
// --steps
|
||||
// 50
|
||||
// --repeat
|
||||
// 20
|
||||
// --output
|
||||
// pezcumulus/teyrchains/pezpallets/staking-score/src/weights.rs
|
||||
// --template
|
||||
// bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
@@ -37,77 +57,264 @@ use core::marker::PhantomData;
|
||||
pub trait WeightInfo {
|
||||
fn start_score_tracking() -> Weight;
|
||||
fn receive_staking_details() -> Weight;
|
||||
fn register_as_noter() -> Weight;
|
||||
fn unregister_as_noter() -> Weight;
|
||||
fn dispute_staking_details() -> Weight;
|
||||
fn finalize_staking_details() -> Weight;
|
||||
fn slash_noter() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_staking_score` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// `start_score_tracking` — user opt-in, no stake check needed.
|
||||
///
|
||||
/// Pallet operations:
|
||||
/// StakingScore::StakingStartBlock (r:1 w:1) — check + insert
|
||||
///
|
||||
/// OnStakingUpdate callback (trust pallet):
|
||||
/// StakingScore::CachedStakingDetails (r:2 w:0) — iter_prefix for score calc
|
||||
/// StakingScore::StakingStartBlock (r:1 w:0) — duration lookup in score calc
|
||||
/// Trust::TrustScores (r:1 w:1) — update trust score
|
||||
/// Trust::TotalActiveTrustScore (r:1 w:1) — update aggregate
|
||||
/// IdentityKyc::KycStatuses (r:1 w:0) — citizenship gate
|
||||
///
|
||||
/// Total: r:7 w:3
|
||||
/// Storage: `StakingScore::StakingStartBlock` (r:1 w:1)
|
||||
/// Proof: `StakingScore::StakingStartBlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TrustScores` (r:1 w:1)
|
||||
/// Proof: `Trust::TrustScores` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::CachedStakingDetails` (r:2 w:0)
|
||||
/// Proof: `StakingScore::CachedStakingDetails` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Referral::ReferrerStatsStorage` (r:1 w:0)
|
||||
/// Proof: `Referral::ReferrerStatsStorage` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Perwerde::StudentCourses` (r:1 w:0)
|
||||
/// Proof: `Perwerde::StudentCourses` (`max_values`: None, `max_size`: Some(249), added: 2724, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TotalActiveTrustScore` (r:1 w:1)
|
||||
/// Proof: `Trust::TotalActiveTrustScore` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn start_score_tracking() -> Weight {
|
||||
// ~30us execution + 7 reads + 3 writes
|
||||
// Proof size: StakingStartBlock(52) + CachedStakingDetails(77*2) +
|
||||
// TrustScores(48) + TotalActiveTrustScore(16) + KycStatuses(34) = ~7500
|
||||
Weight::from_parts(30_000_000, 7_500)
|
||||
.saturating_add(T::DbWeight::get().reads(7_u64))
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `475`
|
||||
// Estimated: `6118`
|
||||
// Minimum execution time: 103_333_000 picoseconds.
|
||||
Weight::from_parts(129_582_000, 6118)
|
||||
.saturating_add(T::DbWeight::get().reads(8_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
|
||||
/// `receive_staking_details` — worst case: noter signed + zero-stake cleanup.
|
||||
///
|
||||
/// Origin check (noter path):
|
||||
/// Tiki::UserTikis (r:1 w:0) — noter authority check via has_tiki()
|
||||
///
|
||||
/// Zero-stake cleanup path (worst case):
|
||||
/// StakingScore::CachedStakingDetails (r:1 w:1) — remove entry for source
|
||||
/// StakingScore::CachedStakingDetails (r:2 w:0) — iter_prefix remaining check
|
||||
/// StakingScore::StakingStartBlock (r:1 w:1) — remove if no remaining stake
|
||||
///
|
||||
/// OnStakingUpdate callback (trust pallet):
|
||||
/// StakingScore::CachedStakingDetails (r:2 w:0) — iter for score calc (overlaps)
|
||||
/// StakingScore::StakingStartBlock (r:1 w:0) — duration lookup (overlaps)
|
||||
/// Trust::TrustScores (r:1 w:1) — update trust score
|
||||
/// Trust::TotalActiveTrustScore (r:1 w:1) — update aggregate
|
||||
/// IdentityKyc::KycStatuses (r:1 w:0) — citizenship gate
|
||||
///
|
||||
/// Note: Some reads overlap (CachedStakingDetails iter + StakingStartBlock read
|
||||
/// are done both in cleanup and in the trust callback score calculation).
|
||||
/// Counting unique reads conservatively:
|
||||
///
|
||||
/// Total: r:10 w:4
|
||||
/// Storage: `StakingScore::CachedStakingDetails` (r:3 w:1)
|
||||
/// Proof: `StakingScore::CachedStakingDetails` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::StakingStartBlock` (r:1 w:0)
|
||||
/// Proof: `StakingScore::StakingStartBlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TrustScores` (r:1 w:1)
|
||||
/// Proof: `Trust::TrustScores` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Referral::ReferrerStatsStorage` (r:1 w:0)
|
||||
/// Proof: `Referral::ReferrerStatsStorage` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Perwerde::StudentCourses` (r:1 w:0)
|
||||
/// Proof: `Perwerde::StudentCourses` (`max_values`: None, `max_size`: Some(249), added: 2724, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TotalActiveTrustScore` (r:1 w:1)
|
||||
/// Proof: `Trust::TotalActiveTrustScore` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn receive_staking_details() -> Weight {
|
||||
// ~40us execution + 10 reads + 4 writes
|
||||
// Proof size: Tiki::UserTikis(200) + CachedStakingDetails(77*2) +
|
||||
// StakingStartBlock(52) + TrustScores(48) +
|
||||
// TotalActiveTrustScore(16) + KycStatuses(34) = ~9500
|
||||
Weight::from_parts(40_000_000, 9_500)
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `475`
|
||||
// Estimated: `8682`
|
||||
// Minimum execution time: 127_528_000 picoseconds.
|
||||
Weight::from_parts(201_235_000, 8682)
|
||||
.saturating_add(T::DbWeight::get().reads(9_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::NoterBonds` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterBonds` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
fn register_as_noter() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `322`
|
||||
// Estimated: `3534`
|
||||
// Minimum execution time: 85_700_000 picoseconds.
|
||||
Weight::from_parts(132_988_000, 3534)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::NoterBonds` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterBonds` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::NoterLastSubmission` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterLastSubmission` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn unregister_as_noter() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `166`
|
||||
// Estimated: `3529`
|
||||
// Minimum execution time: 69_740_000 picoseconds.
|
||||
Weight::from_parts(72_445_000, 3529)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::PendingStakingDetails` (r:1 w:1)
|
||||
/// Proof: `StakingScore::PendingStakingDetails` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`)
|
||||
fn dispute_staking_details() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `232`
|
||||
// Estimated: `3590`
|
||||
// Minimum execution time: 30_306_000 picoseconds.
|
||||
Weight::from_parts(41_478_000, 3590)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::PendingStakingDetails` (r:1 w:1)
|
||||
/// Proof: `StakingScore::PendingStakingDetails` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::CachedStakingDetails` (r:3 w:1)
|
||||
/// Proof: `StakingScore::CachedStakingDetails` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::StakingStartBlock` (r:1 w:0)
|
||||
/// Proof: `StakingScore::StakingStartBlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TrustScores` (r:1 w:1)
|
||||
/// Proof: `Trust::TrustScores` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Referral::ReferrerStatsStorage` (r:1 w:0)
|
||||
/// Proof: `Referral::ReferrerStatsStorage` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Perwerde::StudentCourses` (r:1 w:0)
|
||||
/// Proof: `Perwerde::StudentCourses` (`max_values`: None, `max_size`: Some(249), added: 2724, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TotalActiveTrustScore` (r:1 w:1)
|
||||
/// Proof: `Trust::TotalActiveTrustScore` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn finalize_staking_details() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `674`
|
||||
// Estimated: `8682`
|
||||
// Minimum execution time: 172_351_000 picoseconds.
|
||||
Weight::from_parts(202_728_000, 8682)
|
||||
.saturating_add(T::DbWeight::get().reads(10_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::NoterBonds` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterBonds` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::NoterLastSubmission` (r:0 w:1)
|
||||
/// Proof: `StakingScore::NoterLastSubmission` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn slash_noter() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `166`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 151_553_000 picoseconds.
|
||||
Weight::from_parts(158_345_000, 3593)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `StakingScore::StakingStartBlock` (r:1 w:1)
|
||||
/// Proof: `StakingScore::StakingStartBlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TrustScores` (r:1 w:1)
|
||||
/// Proof: `Trust::TrustScores` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::CachedStakingDetails` (r:2 w:0)
|
||||
/// Proof: `StakingScore::CachedStakingDetails` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Referral::ReferrerStatsStorage` (r:1 w:0)
|
||||
/// Proof: `Referral::ReferrerStatsStorage` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Perwerde::StudentCourses` (r:1 w:0)
|
||||
/// Proof: `Perwerde::StudentCourses` (`max_values`: None, `max_size`: Some(249), added: 2724, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TotalActiveTrustScore` (r:1 w:1)
|
||||
/// Proof: `Trust::TotalActiveTrustScore` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn start_score_tracking() -> Weight {
|
||||
Weight::from_parts(30_000_000, 7_500)
|
||||
.saturating_add(RocksDbWeight::get().reads(7_u64))
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `475`
|
||||
// Estimated: `6118`
|
||||
// Minimum execution time: 103_333_000 picoseconds.
|
||||
Weight::from_parts(129_582_000, 6118)
|
||||
.saturating_add(RocksDbWeight::get().reads(8_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
|
||||
/// Storage: `StakingScore::CachedStakingDetails` (r:3 w:1)
|
||||
/// Proof: `StakingScore::CachedStakingDetails` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::StakingStartBlock` (r:1 w:0)
|
||||
/// Proof: `StakingScore::StakingStartBlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TrustScores` (r:1 w:1)
|
||||
/// Proof: `Trust::TrustScores` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Referral::ReferrerStatsStorage` (r:1 w:0)
|
||||
/// Proof: `Referral::ReferrerStatsStorage` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Perwerde::StudentCourses` (r:1 w:0)
|
||||
/// Proof: `Perwerde::StudentCourses` (`max_values`: None, `max_size`: Some(249), added: 2724, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TotalActiveTrustScore` (r:1 w:1)
|
||||
/// Proof: `Trust::TotalActiveTrustScore` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn receive_staking_details() -> Weight {
|
||||
Weight::from_parts(40_000_000, 9_500)
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `475`
|
||||
// Estimated: `8682`
|
||||
// Minimum execution time: 127_528_000 picoseconds.
|
||||
Weight::from_parts(201_235_000, 8682)
|
||||
.saturating_add(RocksDbWeight::get().reads(9_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::NoterBonds` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterBonds` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
fn register_as_noter() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `322`
|
||||
// Estimated: `3534`
|
||||
// Minimum execution time: 85_700_000 picoseconds.
|
||||
Weight::from_parts(132_988_000, 3534)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::NoterBonds` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterBonds` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::NoterLastSubmission` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterLastSubmission` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn unregister_as_noter() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `166`
|
||||
// Estimated: `3529`
|
||||
// Minimum execution time: 69_740_000 picoseconds.
|
||||
Weight::from_parts(72_445_000, 3529)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::PendingStakingDetails` (r:1 w:1)
|
||||
/// Proof: `StakingScore::PendingStakingDetails` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`)
|
||||
fn dispute_staking_details() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `232`
|
||||
// Estimated: `3590`
|
||||
// Minimum execution time: 30_306_000 picoseconds.
|
||||
Weight::from_parts(41_478_000, 3590)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::PendingStakingDetails` (r:1 w:1)
|
||||
/// Proof: `StakingScore::PendingStakingDetails` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::CachedStakingDetails` (r:3 w:1)
|
||||
/// Proof: `StakingScore::CachedStakingDetails` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::StakingStartBlock` (r:1 w:0)
|
||||
/// Proof: `StakingScore::StakingStartBlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TrustScores` (r:1 w:1)
|
||||
/// Proof: `Trust::TrustScores` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Referral::ReferrerStatsStorage` (r:1 w:0)
|
||||
/// Proof: `Referral::ReferrerStatsStorage` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Perwerde::StudentCourses` (r:1 w:0)
|
||||
/// Proof: `Perwerde::StudentCourses` (`max_values`: None, `max_size`: Some(249), added: 2724, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tiki::UserTikis` (r:1 w:0)
|
||||
/// Proof: `Tiki::UserTikis` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Trust::TotalActiveTrustScore` (r:1 w:1)
|
||||
/// Proof: `Trust::TotalActiveTrustScore` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
fn finalize_staking_details() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `674`
|
||||
// Estimated: `8682`
|
||||
// Minimum execution time: 172_351_000 picoseconds.
|
||||
Weight::from_parts(202_728_000, 8682)
|
||||
.saturating_add(RocksDbWeight::get().reads(10_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `StakingScore::NoterBonds` (r:1 w:1)
|
||||
/// Proof: `StakingScore::NoterBonds` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `StakingScore::NoterLastSubmission` (r:0 w:1)
|
||||
/// Proof: `StakingScore::NoterLastSubmission` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn slash_noter() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `166`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 151_553_000 picoseconds.
|
||||
Weight::from_parts(158_345_000, 3593)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@ use super::*;
|
||||
use crate::Pezpallet as Tiki;
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_system::RawOrigin;
|
||||
// Gerekli trait'leri import ediyoruz
|
||||
// Import the required traits
|
||||
use pezframe_support::traits::{Currency, Get};
|
||||
use pezpallet_balances::Pezpallet as Balances;
|
||||
use pezsp_runtime::traits::StaticLookup;
|
||||
extern crate alloc;
|
||||
|
||||
// Gerekli trait kısıtlamalarını ana benchmarks bloğuna ekliyoruz.
|
||||
// Add the required trait bounds to the main benchmarks block.
|
||||
#[benchmarks(
|
||||
where
|
||||
T::CollectionId: Copy + Default + PartialOrd,
|
||||
@@ -25,13 +25,13 @@ extern crate alloc;
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
// Bu yardımcı fonksiyon, runtime'da tanımlanan Tiki koleksiyonunu oluşturur.
|
||||
// This helper function creates the Tiki collection defined in the runtime.
|
||||
fn ensure_collection_exists<T: Config + pezpallet_balances::Config>()
|
||||
where
|
||||
T::CollectionId: Default + PartialOrd,
|
||||
{
|
||||
let collection_id = T::TikiCollectionId::get();
|
||||
// Koleksiyon sahibi olarak fonlanmış `whitelisted_caller`'ı kullanıyoruz.
|
||||
// Use the funded `whitelisted_caller` as the collection owner.
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
|
||||
// Fund the caller account with sufficient balance for NFT deposits
|
||||
@@ -39,7 +39,7 @@ mod benchmarks {
|
||||
let funding = Balances::<T>::minimum_balance() * 1_000_000_000u32.into();
|
||||
Balances::<T>::make_free_balance_be(&caller, funding);
|
||||
|
||||
// `while` döngüsü, 'Step' trait'ine olan ihtiyacı ortadan kaldırır.
|
||||
// The `while` loop removes the need for the 'Step' trait.
|
||||
while pezpallet_nfts::NextCollectionId::<T>::get().unwrap_or_default() <= collection_id {
|
||||
let _ = pezpallet_nfts::Pezpallet::<T>::force_create(
|
||||
RawOrigin::Root.into(),
|
||||
@@ -75,9 +75,9 @@ mod benchmarks {
|
||||
|
||||
#[benchmark]
|
||||
fn grant_tiki() -> Result<(), BenchmarkError> {
|
||||
// NFT'yi alacak 'dest' hesabı olarak fonlanmış `whitelisted_caller`'ı kullanıyoruz.
|
||||
// Use the funded `whitelisted_caller` as the 'dest' account that will receive the NFT.
|
||||
let dest: T::AccountId = whitelisted_caller();
|
||||
// Appointed role kullan (Serok yerine Wezir)
|
||||
// Use an appointed role (Wezir instead of Serok)
|
||||
let tiki = crate::Tiki::Wezir;
|
||||
|
||||
// Ensure the dest account has a citizen NFT before granting a tiki
|
||||
@@ -93,7 +93,7 @@ mod benchmarks {
|
||||
|
||||
#[benchmark]
|
||||
fn revoke_tiki() -> Result<(), BenchmarkError> {
|
||||
// NFT'yi alacak 'dest' hesabı olarak fonlanmış `whitelisted_caller`'ı kullanıyoruz.
|
||||
// Use the funded `whitelisted_caller` as the 'dest' account that will receive the NFT.
|
||||
let dest: T::AccountId = whitelisted_caller();
|
||||
let tiki = crate::Tiki::Wezir; // Use appointed role
|
||||
|
||||
@@ -119,13 +119,13 @@ mod benchmarks {
|
||||
// Ensure collection exists first
|
||||
ensure_collection_exists::<T>();
|
||||
|
||||
// Henüz vatandaş olmamalı
|
||||
// Should not be a citizen yet
|
||||
assert!(Tiki::<T>::citizen_nft(&dest).is_none());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, T::Lookup::unlookup(dest.clone()));
|
||||
|
||||
// Vatandaş olduğundan emin ol
|
||||
// Ensure they are now a citizen
|
||||
assert!(Tiki::<T>::citizen_nft(&dest).is_some());
|
||||
assert!(Tiki::<T>::is_citizen(&dest));
|
||||
|
||||
@@ -135,15 +135,15 @@ mod benchmarks {
|
||||
#[benchmark]
|
||||
fn grant_earned_role() -> Result<(), BenchmarkError> {
|
||||
let dest: T::AccountId = whitelisted_caller();
|
||||
let tiki = crate::Tiki::Axa; // Earned bir rol
|
||||
let tiki = crate::Tiki::Axa; // An earned role
|
||||
|
||||
// Ön koşul: Vatandaş olmalı
|
||||
// Precondition: must be a citizen
|
||||
ensure_citizen_nft::<T>(dest.clone())?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, T::Lookup::unlookup(dest.clone()), tiki);
|
||||
|
||||
// Rolün verildiğini doğrula
|
||||
// Verify the role was granted
|
||||
assert!(Tiki::<T>::has_tiki(&dest, &tiki));
|
||||
|
||||
Ok(())
|
||||
@@ -152,15 +152,15 @@ mod benchmarks {
|
||||
#[benchmark]
|
||||
fn grant_elected_role() -> Result<(), BenchmarkError> {
|
||||
let dest: T::AccountId = whitelisted_caller();
|
||||
let tiki = crate::Tiki::Parlementer; // Elected bir rol
|
||||
let tiki = crate::Tiki::Parlementer; // An elected role
|
||||
|
||||
// Ön koşul: Vatandaş olmalı
|
||||
// Precondition: must be a citizen
|
||||
ensure_citizen_nft::<T>(dest.clone())?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, T::Lookup::unlookup(dest.clone()), tiki);
|
||||
|
||||
// Rolün verildiğini doğrula
|
||||
// Verify the role was granted
|
||||
assert!(Tiki::<T>::has_tiki(&dest, &tiki));
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -97,6 +97,10 @@
|
||||
//! impl pezpallet_tiki::Config for Runtime {
|
||||
//! type RuntimeEvent = RuntimeEvent;
|
||||
//! type AdminOrigin = EnsureRoot<AccountId>;
|
||||
//! // Elected/Earned roles use their own origin, kept distinct from ordinary
|
||||
//! // admin appointment so a real election/exam pipeline can gate them later.
|
||||
//! type ElectedRoleOrigin = EnsureRoot<AccountId>;
|
||||
//! type EarnedRoleOrigin = EnsureRoot<AccountId>;
|
||||
//! type WeightInfo = pezpallet_tiki::weights::BizinikiwiWeight<Runtime>;
|
||||
//! type TikiCollectionId = ConstU32<1>; // Tiki collection ID
|
||||
//! type MaxTikisPerUser = ConstU32<20>; // Max 20 roles per user
|
||||
@@ -154,6 +158,20 @@ pub mod pezpallet {
|
||||
+ pezpallet_identity_kyc::Config
|
||||
{
|
||||
type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Origin required to grant a role through the election system
|
||||
/// (`grant_elected_role`). Deliberately kept distinct from `AdminOrigin`: an
|
||||
/// Elected office (Serok, SerokiMeclise, Parlementer, ...) is meant to carry
|
||||
/// evidence of a genuine election, not merely ordinary admin/committee say-so.
|
||||
/// Runtimes should wire this to whatever origin their election pipeline
|
||||
/// escalates to (e.g. `EnsureRoot`), rather than reusing `AdminOrigin`.
|
||||
type ElectedRoleOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Origin required to grant a role through the exam/earned system
|
||||
/// (`grant_earned_role`). Deliberately kept distinct from `AdminOrigin` for the
|
||||
/// same reason as `ElectedRoleOrigin`.
|
||||
type EarnedRoleOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
type WeightInfo: weights::WeightInfo;
|
||||
|
||||
/// Collection ID holding Tiki (Role) NFTs.
|
||||
@@ -265,6 +283,21 @@ pub mod pezpallet {
|
||||
WezireTend,
|
||||
WezireAva,
|
||||
WezireCand,
|
||||
// Newly added functional / professional roles. Appended at the end to preserve
|
||||
// the SCALE encoding (discriminant order) of existing on-chain values. The trust
|
||||
// bonuses assigned below are provisional and should be ratified by governance.
|
||||
Bernamenivîs, // Software developer / engineer (builds the chain itself)
|
||||
Wergêr, // Translator (a six-language nation needs this)
|
||||
Aborînas, // Economist
|
||||
Hesabdar, // Accountant
|
||||
Rojnamevan, // Journalist
|
||||
PisporêBazarkirinê, // Marketing specialist
|
||||
Statîstîknas, // Statistician
|
||||
Piştrastkar, // KYC verifier
|
||||
Hilbijartinkar, // Election officer
|
||||
Îcrakar, // Executor / enforcement officer
|
||||
Karguzar, // Human-resources officer
|
||||
Plansaz, // Budget planner
|
||||
}
|
||||
|
||||
impl From<Tiki> for u32 {
|
||||
@@ -301,6 +334,23 @@ pub mod pezpallet {
|
||||
#[pezpallet::getter(fn next_item_id)]
|
||||
pub type NextItemId<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// Records, per (account, tiki), which `RoleAssignmentType` path was used the last
|
||||
/// time that role was granted to that account (Automatic/Appointed/Elected/Earned).
|
||||
/// This gives an on-chain, queryable audit trail distinguishing a role that came
|
||||
/// through `grant_elected_role`/`grant_earned_role` from one granted directly via
|
||||
/// `grant_tiki`, even though today all of these entry points share similar origins.
|
||||
#[pezpallet::storage]
|
||||
#[pezpallet::getter(fn role_assignment_type_of)]
|
||||
pub type RoleAssignmentTypeOf<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
Blake2_128Concat,
|
||||
Tiki,
|
||||
RoleAssignmentType,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
#[pezpallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Role already belongs to someone else
|
||||
@@ -422,7 +472,7 @@ pub mod pezpallet {
|
||||
|
||||
#[pezpallet::call]
|
||||
impl<T: Config> Pezpallet<T> {
|
||||
/// Admin tarafından belirli bir kullanıcıya Tiki (rol) verme
|
||||
/// Grant a Tiki (role) to a specific user by an admin
|
||||
#[pezpallet::call_index(0)]
|
||||
#[pezpallet::weight(<T as crate::pezpallet::Config>::WeightInfo::grant_tiki())]
|
||||
pub fn grant_tiki(
|
||||
@@ -440,10 +490,11 @@ pub mod pezpallet {
|
||||
);
|
||||
|
||||
Self::internal_grant_role(&dest_account, tiki)?;
|
||||
RoleAssignmentTypeOf::<T>::insert(&dest_account, tiki, RoleAssignmentType::Appointed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Admin tarafından belirli bir kullanıcıdan Tiki (rol) alma
|
||||
/// Remove a Tiki (role) from a specific user by an admin
|
||||
#[pezpallet::call_index(1)]
|
||||
#[pezpallet::weight(<T as crate::pezpallet::Config>::WeightInfo::revoke_tiki())]
|
||||
pub fn revoke_tiki(
|
||||
@@ -480,7 +531,9 @@ pub mod pezpallet {
|
||||
dest: <T::Lookup as StaticLookup>::Source,
|
||||
tiki: Tiki,
|
||||
) -> DispatchResult {
|
||||
T::AdminOrigin::ensure_origin(origin)?; // pezpallet-voting will call with Root origin
|
||||
// Distinct from AdminOrigin: an Elected role must come through the
|
||||
// election-specific origin (see `ElectedRoleOrigin` doc comment).
|
||||
T::ElectedRoleOrigin::ensure_origin(origin)?;
|
||||
let dest_account = T::Lookup::lookup(dest)?;
|
||||
|
||||
// Check if the role can be granted through election
|
||||
@@ -490,6 +543,7 @@ pub mod pezpallet {
|
||||
);
|
||||
|
||||
Self::internal_grant_role(&dest_account, tiki)?;
|
||||
RoleAssignmentTypeOf::<T>::insert(&dest_account, tiki, RoleAssignmentType::Elected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -501,7 +555,9 @@ pub mod pezpallet {
|
||||
dest: <T::Lookup as StaticLookup>::Source,
|
||||
tiki: Tiki,
|
||||
) -> DispatchResult {
|
||||
T::AdminOrigin::ensure_origin(origin)?; // For now admin, later exam pezpallet
|
||||
// Distinct from AdminOrigin: an Earned role must come through the
|
||||
// exam-specific origin (see `EarnedRoleOrigin` doc comment).
|
||||
T::EarnedRoleOrigin::ensure_origin(origin)?;
|
||||
let dest_account = T::Lookup::lookup(dest)?;
|
||||
|
||||
// Check if the role can be earned
|
||||
@@ -511,6 +567,7 @@ pub mod pezpallet {
|
||||
);
|
||||
|
||||
Self::internal_grant_role(&dest_account, tiki)?;
|
||||
RoleAssignmentTypeOf::<T>::insert(&dest_account, tiki, RoleAssignmentType::Earned);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -543,7 +600,7 @@ pub mod pezpallet {
|
||||
from: T::AccountId,
|
||||
to: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
// Tiki NFT koleksiyonu ise transfer'e izin verme
|
||||
// If it is the Tiki NFT collection, do not allow the transfer
|
||||
if collection_id == T::TikiCollectionId::get() {
|
||||
Self::deposit_event(Event::TransferBlocked { collection_id, item_id, from, to });
|
||||
return Err(DispatchError::Other("Citizen NFTs are non-transferable"));
|
||||
@@ -582,6 +639,7 @@ pub mod pezpallet {
|
||||
UserTikis::<T>::mutate(user, |tikis| {
|
||||
let _ = tikis.try_push(Tiki::Welati);
|
||||
});
|
||||
RoleAssignmentTypeOf::<T>::insert(user, Tiki::Welati, RoleAssignmentType::Automatic);
|
||||
|
||||
// Set NFT metadata
|
||||
Self::update_nft_metadata(user)?;
|
||||
@@ -647,6 +705,9 @@ pub mod pezpallet {
|
||||
TikiHolder::<T>::remove(tiki);
|
||||
}
|
||||
|
||||
// Clear the recorded assignment-type provenance for this (account, tiki) pair
|
||||
RoleAssignmentTypeOf::<T>::remove(target_account, tiki);
|
||||
|
||||
// Update NFT metadata
|
||||
Self::update_nft_metadata(target_account)?;
|
||||
|
||||
@@ -702,7 +763,24 @@ pub mod pezpallet {
|
||||
|
||||
/// Checks if a specific role is unique (can belong to only one person)
|
||||
pub fn is_unique_role(tiki: &Tiki) -> bool {
|
||||
matches!(tiki, Tiki::Serok | Tiki::SerokiMeclise | Tiki::Xezinedar | Tiki::Balyoz)
|
||||
matches!(
|
||||
tiki,
|
||||
Tiki::Serok
|
||||
| Tiki::SerokiMeclise
|
||||
| Tiki::Xezinedar
|
||||
| Tiki::Balyoz
|
||||
// Government roles: single-portfolio cabinet offices, each of which
|
||||
// must have at most one holder at a time (see get_bonus_for_tiki,
|
||||
// which grants each of these a fixed per-office trust bonus).
|
||||
| Tiki::SerokWeziran
|
||||
| Tiki::WezireDarayiye
|
||||
| Tiki::WezireParez
|
||||
| Tiki::WezireDad
|
||||
| Tiki::WezireBelaw
|
||||
| Tiki::WezireTend
|
||||
| Tiki::WezireAva
|
||||
| Tiki::WezireCand
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the assignment type of a specific role
|
||||
@@ -744,12 +822,12 @@ pub mod pezpallet {
|
||||
}
|
||||
}
|
||||
|
||||
/// KYC sonrası otomatik Welati rolü verme
|
||||
/// Automatically grant the Welati role after KYC
|
||||
pub fn auto_grant_citizenship(account: &T::AccountId) -> DispatchResult {
|
||||
// KYC kontrolü
|
||||
// KYC check
|
||||
let kyc_status = pezpallet_identity_kyc::Pezpallet::<T>::kyc_status_of(account);
|
||||
if kyc_status == pezpallet_identity_kyc::types::KycLevel::Approved {
|
||||
// Vatandaşlık NFT'si yoksa bas
|
||||
// Mint the citizenship NFT if it does not exist
|
||||
if Self::citizen_nft(account).is_none() {
|
||||
Self::mint_citizen_nft_for_user(account)?;
|
||||
}
|
||||
@@ -757,31 +835,31 @@ pub mod pezpallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Kullanıcının belirli bir Tiki'ye sahip olup olmadığını kontrol eder
|
||||
/// Checks whether the user holds a specific Tiki
|
||||
pub fn has_tiki(who: &T::AccountId, tiki: &Tiki) -> bool {
|
||||
Self::user_tikis(who).contains(tiki)
|
||||
}
|
||||
|
||||
/// Kullanıcının vatandaş olup olmadığını kontrol eder
|
||||
/// Checks whether the user is a citizen
|
||||
pub fn is_citizen(who: &T::AccountId) -> bool {
|
||||
Self::citizen_nft(who).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Diğer paletlerin, bu paletten Tiki puanlarını sorgulaması için kullanılacak trait
|
||||
/// Trait used by other pallets to query Tiki scores from this pallet
|
||||
pub trait TikiScoreProvider<AccountId> {
|
||||
fn get_tiki_score(who: &AccountId) -> u32;
|
||||
}
|
||||
|
||||
/// Diğer paletlerin, Tiki sahipliğini sorgulaması için kullanılacak trait
|
||||
/// Trait used by other pallets to query Tiki ownership
|
||||
pub trait TikiProvider<AccountId> {
|
||||
fn has_tiki(who: &AccountId, tiki: &Tiki) -> bool;
|
||||
fn get_user_tikis(who: &AccountId) -> Vec<Tiki>;
|
||||
fn is_citizen(who: &AccountId) -> bool;
|
||||
}
|
||||
|
||||
/// Trait implementasyonları
|
||||
/// Trait implementations
|
||||
impl<T: Config> TikiScoreProvider<T::AccountId> for Pezpallet<T> {
|
||||
fn get_tiki_score(who: &T::AccountId) -> u32 {
|
||||
let tikis = Self::user_tikis(who);
|
||||
@@ -806,12 +884,12 @@ impl<T: Config> TikiProvider<T::AccountId> for Pezpallet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Puanlama mantığını ayrı bir impl bloğunda tutarak kodu daha düzenli hale getiriyoruz.
|
||||
// Keeping the scoring logic in a separate impl block to keep the code more organized.
|
||||
impl<T: Config> Pezpallet<T> {
|
||||
/// Belirli bir Tiki'nin Trust Puanı'na olan katkısını döndürür.
|
||||
/// Returns the contribution of a specific Tiki to the Trust Score.
|
||||
pub fn get_bonus_for_tiki(tiki: &Tiki) -> u32 {
|
||||
match tiki {
|
||||
// Anayasa v5.0'da Belirlenen Özel Puanlar
|
||||
// Special scores defined in Anayasa v5.0
|
||||
Tiki::Axa => 250,
|
||||
Tiki::RêveberêProjeyê => 250,
|
||||
Tiki::ModeratorêCivakê => 200,
|
||||
@@ -819,13 +897,13 @@ impl<T: Config> Pezpallet<T> {
|
||||
Tiki::Mela => 50,
|
||||
Tiki::Feqî => 50,
|
||||
|
||||
// Hiyerarşik Devlet Puanları
|
||||
// Yargı
|
||||
// Hierarchical State Scores
|
||||
// Judiciary
|
||||
Tiki::EndameDiwane => 175,
|
||||
Tiki::Dadger => 150,
|
||||
Tiki::Dozger => 120,
|
||||
Tiki::Hiquqnas => 75,
|
||||
// Yürütme
|
||||
// Executive
|
||||
Tiki::Serok => 200,
|
||||
Tiki::Wezir => 100,
|
||||
Tiki::SerokWeziran => 125,
|
||||
@@ -837,18 +915,18 @@ impl<T: Config> Pezpallet<T> {
|
||||
Tiki::WezireAva => 100,
|
||||
Tiki::WezireCand => 100,
|
||||
|
||||
// Yasama
|
||||
// Legislature
|
||||
Tiki::SerokiMeclise => 150,
|
||||
Tiki::Parlementer => 100,
|
||||
|
||||
// Atanmış Üst Düzey Memurlar
|
||||
// Appointed Senior Officials
|
||||
Tiki::Xezinedar => 100,
|
||||
Tiki::PisporêEwlehiyaSîber => 100,
|
||||
Tiki::Mufetîs => 90,
|
||||
Tiki::Balyoz => 80,
|
||||
Tiki::Berdevk => 70,
|
||||
|
||||
// Diğer Memurlar ve Uzmanlar
|
||||
// Other Officials and Experts
|
||||
Tiki::Mamoste => 70,
|
||||
Tiki::OperatorêTorê => 60,
|
||||
Tiki::Noter => 50,
|
||||
@@ -866,7 +944,21 @@ impl<T: Config> Pezpallet<T> {
|
||||
Tiki::Bazargan => 60,
|
||||
Tiki::Pêseng => 80,
|
||||
|
||||
// Temel Vatandaşlık
|
||||
// Newly added functional / professional roles (provisional bonuses — ratify via governance)
|
||||
Tiki::Bernamenivîs => 80,
|
||||
Tiki::Aborînas => 75,
|
||||
Tiki::Plansaz => 70,
|
||||
Tiki::Piştrastkar => 60,
|
||||
Tiki::Hilbijartinkar => 60,
|
||||
Tiki::Îcrakar => 60,
|
||||
Tiki::Wergêr => 50,
|
||||
Tiki::Hesabdar => 50,
|
||||
Tiki::Rojnamevan => 50,
|
||||
Tiki::Statîstîknas => 50,
|
||||
Tiki::PisporêBazarkirinê => 40,
|
||||
Tiki::Karguzar => 40,
|
||||
|
||||
// Basic Citizenship
|
||||
Tiki::Welati => 10,
|
||||
}
|
||||
}
|
||||
@@ -895,12 +987,13 @@ impl<T: Config> pezpallet_identity_kyc::types::CitizenNftProvider<T::AccountId>
|
||||
item_id,
|
||||
)?;
|
||||
|
||||
// Clear unique role mappings before removing roles
|
||||
// Clear unique role mappings and assignment-type provenance before removing roles
|
||||
let user_tikis = UserTikis::<T>::get(who);
|
||||
for tiki in user_tikis.iter() {
|
||||
if Self::is_unique_role(tiki) {
|
||||
TikiHolder::<T>::remove(tiki);
|
||||
}
|
||||
RoleAssignmentTypeOf::<T>::remove(who, tiki);
|
||||
}
|
||||
|
||||
// Remove all roles and citizen NFT mapping
|
||||
|
||||
@@ -64,7 +64,7 @@ type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
pub type AccountId = u64;
|
||||
pub type Balance = u128;
|
||||
|
||||
// Runtime'ı oluştur - Identity ve IdentityKyc pezpallet'lerini de ekle
|
||||
// Build the runtime - also add the Identity and IdentityKyc pezpallets
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
@@ -106,7 +106,7 @@ impl pezframe_system::Config for Test {
|
||||
type MultiBlockMigrator = ();
|
||||
type PreInherents = ();
|
||||
type PostInherents = ();
|
||||
type PostTransactions = (); // Eksik olan trait
|
||||
type PostTransactions = (); // The missing trait
|
||||
type ExtensionsWeightInfo = ();
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ impl pezpallet_balances::Config for Test {
|
||||
type DoneSlashHandler = ();
|
||||
}
|
||||
|
||||
// pezpallet_identity::Config implementasyonu
|
||||
// pezpallet_identity::Config implementation
|
||||
parameter_types! {
|
||||
pub const BasicDeposit: Balance = 1000;
|
||||
pub const ByteDeposit: Balance = 10;
|
||||
@@ -270,6 +270,8 @@ parameter_types! {
|
||||
|
||||
impl crate::Config for Test {
|
||||
type AdminOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type ElectedRoleOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type EarnedRoleOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type WeightInfo = ();
|
||||
type TikiCollectionId = TikiCollectionId;
|
||||
type MaxTikisPerUser = MaxTikisPerUser;
|
||||
@@ -291,7 +293,7 @@ pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
|
||||
// Tiki koleksiyonunu oluştur - mint permissions ile
|
||||
// Create the Tiki collection - with mint permissions
|
||||
assert_ok!(Nfts::force_create(
|
||||
RuntimeOrigin::root(),
|
||||
1, // owner
|
||||
|
||||
@@ -11,29 +11,29 @@ use pezsp_runtime::DispatchError;
|
||||
|
||||
type TikiPallet = crate::Pezpallet<Test>;
|
||||
|
||||
// === Temel NFT ve Rol Testleri ===
|
||||
// === Basic NFT and Role Tests ===
|
||||
|
||||
#[test]
|
||||
fn force_mint_citizen_nft_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user_account = 2;
|
||||
|
||||
// Başlangıçta vatandaşlık NFT'si olmamalı
|
||||
// Initially there should be no citizenship NFT
|
||||
assert_eq!(TikiPallet::citizen_nft(user_account), None);
|
||||
assert!(TikiPallet::user_tikis(user_account).is_empty());
|
||||
assert!(!TikiPallet::is_citizen(&user_account));
|
||||
|
||||
// Vatandaşlık NFT'si bas
|
||||
// Mint citizenship NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
|
||||
// NFT'nin basıldığını ve Welati rolünün eklendiğini kontrol et
|
||||
// Verify the NFT was minted and the Welati role was added
|
||||
assert!(TikiPallet::citizen_nft(user_account).is_some());
|
||||
assert!(TikiPallet::is_citizen(&user_account));
|
||||
let user_tikis = TikiPallet::user_tikis(user_account);
|
||||
assert!(user_tikis.contains(&TikiEnum::Welati));
|
||||
assert!(TikiPallet::has_tiki(&user_account, &TikiEnum::Welati));
|
||||
|
||||
// Event'in doğru atıldığını kontrol et
|
||||
// Verify the event was emitted correctly
|
||||
System::assert_has_event(
|
||||
Event::CitizenNftMinted {
|
||||
who: user_account,
|
||||
@@ -50,19 +50,19 @@ fn grant_appointed_role_works() {
|
||||
let user_account = 2;
|
||||
let tiki_to_grant = TikiEnum::Wezir; // Appointed role
|
||||
|
||||
// Önce vatandaşlık NFT'si bas
|
||||
// First mint the citizenship NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
|
||||
// Tiki ver
|
||||
// Grant the Tiki
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user_account, tiki_to_grant));
|
||||
|
||||
// Kullanıcının rollerini kontrol et
|
||||
// Verify the user's roles
|
||||
let user_tikis = TikiPallet::user_tikis(user_account);
|
||||
assert!(user_tikis.contains(&TikiEnum::Welati)); // Otomatik eklenen
|
||||
assert!(user_tikis.contains(&tiki_to_grant)); // Manuel eklenen
|
||||
assert!(user_tikis.contains(&TikiEnum::Welati)); // Automatically added
|
||||
assert!(user_tikis.contains(&tiki_to_grant)); // Manually added
|
||||
assert!(TikiPallet::has_tiki(&user_account, &tiki_to_grant));
|
||||
|
||||
// Event'in doğru atıldığını kontrol et
|
||||
// Verify the event was emitted correctly
|
||||
System::assert_has_event(
|
||||
Event::TikiGranted { who: user_account, tiki: tiki_to_grant }.into(),
|
||||
);
|
||||
@@ -75,10 +75,10 @@ fn cannot_grant_elected_role_through_admin() {
|
||||
let user_account = 2;
|
||||
let elected_role = TikiEnum::Parlementer; // Elected role
|
||||
|
||||
// Vatandaşlık NFT'si bas
|
||||
// Mint citizenship NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
|
||||
// Seçilen rolü admin ile vermeye çalış - başarısız olmalı
|
||||
// Attempt to grant the elected role via admin - should fail
|
||||
assert_noop!(
|
||||
TikiPallet::grant_tiki(RuntimeOrigin::root(), user_account, elected_role),
|
||||
Error::<Test>::InvalidRoleAssignmentMethod
|
||||
@@ -86,18 +86,18 @@ fn cannot_grant_elected_role_through_admin() {
|
||||
});
|
||||
}
|
||||
|
||||
// === KYC ve Identity Testleri ===
|
||||
// === KYC and Identity Tests ===
|
||||
|
||||
#[test]
|
||||
fn apply_for_citizenship_works_with_kyc() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user_account = 2;
|
||||
|
||||
// Basit KYC test - Identity setup'ını skip edelim, sadece force mint test edelim
|
||||
// Direkt force mint ile test edelim (KYC bypass)
|
||||
// Simple KYC test - skip the Identity setup, just test force mint
|
||||
// Test directly via force mint (KYC bypass)
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
|
||||
// NFT'nin basıldığını kontrol et
|
||||
// Verify the NFT was minted
|
||||
assert!(TikiPallet::citizen_nft(user_account).is_some());
|
||||
assert!(TikiPallet::user_tikis(user_account).contains(&TikiEnum::Welati));
|
||||
assert!(TikiPallet::is_citizen(&user_account));
|
||||
@@ -109,7 +109,7 @@ fn apply_for_citizenship_fails_without_kyc() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user_account = 2;
|
||||
|
||||
// KYC olmadan vatandaşlık başvurusu yap
|
||||
// Apply for citizenship without KYC
|
||||
assert_noop!(
|
||||
TikiPallet::apply_for_citizenship(RuntimeOrigin::signed(user_account)),
|
||||
Error::<Test>::KycNotCompleted
|
||||
@@ -122,16 +122,16 @@ fn auto_grant_citizenship_simplified() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 2;
|
||||
|
||||
// Identity setup complex olduğu için, sadece fonksiyonun çalıştığını test edelim
|
||||
// KYC olmadan çağrıldığında hata vermemeli (sadece hiçbir şey yapmamalı)
|
||||
// Since the Identity setup is complex, just test that the function runs
|
||||
// When called without KYC it should not error (it should simply do nothing)
|
||||
assert_ok!(TikiPallet::auto_grant_citizenship(&user));
|
||||
|
||||
// KYC olmadığı için NFT basılmamalı
|
||||
// No NFT should be minted because there is no KYC
|
||||
assert!(TikiPallet::citizen_nft(user).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
// === Role Assignment Types Testleri ===
|
||||
// === Role Assignment Types Tests ===
|
||||
|
||||
#[test]
|
||||
fn role_assignment_types_work_correctly() {
|
||||
@@ -186,13 +186,13 @@ fn grant_earned_role_works() {
|
||||
let user_account = 2;
|
||||
let earned_role = TikiEnum::Axa; // Earned role
|
||||
|
||||
// Vatandaşlık NFT'si bas
|
||||
// Mint citizenship NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
|
||||
// Earned rolü ver
|
||||
// Grant the earned role
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), user_account, earned_role));
|
||||
|
||||
// Rolün eklendiğini kontrol et
|
||||
// Verify the role was added
|
||||
assert!(TikiPallet::user_tikis(user_account).contains(&earned_role));
|
||||
assert!(TikiPallet::has_tiki(&user_account, &earned_role));
|
||||
});
|
||||
@@ -204,23 +204,23 @@ fn grant_elected_role_works() {
|
||||
let user_account = 2;
|
||||
let elected_role = TikiEnum::Parlementer; // Elected role
|
||||
|
||||
// Vatandaşlık NFT'si bas
|
||||
// Mint citizenship NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
|
||||
// Elected rolü ver (pezpallet-voting tarafından çağrılacak)
|
||||
// Grant the elected role (will be called by pezpallet-voting)
|
||||
assert_ok!(TikiPallet::grant_elected_role(
|
||||
RuntimeOrigin::root(),
|
||||
user_account,
|
||||
elected_role
|
||||
));
|
||||
|
||||
// Rolün eklendiğini kontrol et
|
||||
// Verify the role was added
|
||||
assert!(TikiPallet::user_tikis(user_account).contains(&elected_role));
|
||||
assert!(TikiPallet::has_tiki(&user_account, &elected_role));
|
||||
});
|
||||
}
|
||||
|
||||
// === Unique Roles Testleri ===
|
||||
// === Unique Roles Tests ===
|
||||
|
||||
#[test]
|
||||
fn unique_roles_work_correctly() {
|
||||
@@ -229,20 +229,20 @@ fn unique_roles_work_correctly() {
|
||||
let user2 = 3;
|
||||
let unique_role = TikiEnum::Serok; // Unique role
|
||||
|
||||
// Her iki kullanıcı için NFT bas
|
||||
// Mint NFT for both users
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user1));
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user2));
|
||||
|
||||
// İlk kullanıcıya unique rolü ver (elected role olarak)
|
||||
// Grant the unique role to the first user (as an elected role)
|
||||
assert_ok!(TikiPallet::grant_elected_role(RuntimeOrigin::root(), user1, unique_role));
|
||||
|
||||
// İkinci kullanıcıya aynı rolü vermeye çalış
|
||||
// Attempt to grant the same role to the second user
|
||||
assert_noop!(
|
||||
TikiPallet::grant_elected_role(RuntimeOrigin::root(), user2, unique_role),
|
||||
Error::<Test>::RoleAlreadyTaken
|
||||
);
|
||||
|
||||
// TikiHolder'da doğru şekilde kaydedildiğini kontrol et
|
||||
// Verify it was recorded correctly in TikiHolder
|
||||
assert_eq!(TikiPallet::tiki_holder(unique_role), Some(user1));
|
||||
});
|
||||
}
|
||||
@@ -270,23 +270,23 @@ fn revoke_tiki_works() {
|
||||
let user_account = 2;
|
||||
let tiki_to_revoke = TikiEnum::Wezir;
|
||||
|
||||
// NFT bas ve role ver
|
||||
// Mint NFT and grant the role
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user_account, tiki_to_revoke));
|
||||
|
||||
// Rolün eklendiğini kontrol et
|
||||
// Verify the role was added
|
||||
assert!(TikiPallet::user_tikis(user_account).contains(&tiki_to_revoke));
|
||||
|
||||
// Rolü kaldır
|
||||
// Revoke the role
|
||||
assert_ok!(TikiPallet::revoke_tiki(RuntimeOrigin::root(), user_account, tiki_to_revoke));
|
||||
|
||||
// Rolün kaldırıldığını kontrol et
|
||||
// Verify the role was removed
|
||||
assert!(!TikiPallet::user_tikis(user_account).contains(&tiki_to_revoke));
|
||||
assert!(!TikiPallet::has_tiki(&user_account, &tiki_to_revoke));
|
||||
// Welati rolünün hala durduğunu kontrol et
|
||||
// Verify the Welati role is still present
|
||||
assert!(TikiPallet::user_tikis(user_account).contains(&TikiEnum::Welati));
|
||||
|
||||
// Event kontrol et
|
||||
// Verify the event
|
||||
System::assert_has_event(
|
||||
Event::TikiRevoked { who: user_account, tiki: tiki_to_revoke }.into(),
|
||||
);
|
||||
@@ -298,10 +298,10 @@ fn cannot_revoke_hemwelati_role() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user_account = 2;
|
||||
|
||||
// NFT bas
|
||||
// Mint NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user_account));
|
||||
|
||||
// Welati rolünü kaldırmaya çalış
|
||||
// Attempt to remove the Welati role
|
||||
assert_noop!(
|
||||
TikiPallet::revoke_tiki(RuntimeOrigin::root(), user_account, TikiEnum::Welati),
|
||||
Error::<Test>::RoleNotAssigned
|
||||
@@ -315,43 +315,43 @@ fn revoke_unique_role_clears_holder() {
|
||||
let user = 2;
|
||||
let unique_role = TikiEnum::Serok; // Unique role
|
||||
|
||||
// NFT bas ve unique rolü ver
|
||||
// Mint NFT and grant the unique role
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
assert_ok!(TikiPallet::grant_elected_role(RuntimeOrigin::root(), user, unique_role));
|
||||
|
||||
// TikiHolder'da kayıtlı olduğunu kontrol et
|
||||
// Verify it is recorded in TikiHolder
|
||||
assert_eq!(TikiPallet::tiki_holder(unique_role), Some(user));
|
||||
|
||||
// Rolü kaldır
|
||||
// Revoke the role
|
||||
assert_ok!(TikiPallet::revoke_tiki(RuntimeOrigin::root(), user, unique_role));
|
||||
|
||||
// TikiHolder'dan temizlendiğini kontrol et
|
||||
// Verify it was cleared from TikiHolder
|
||||
assert_eq!(TikiPallet::tiki_holder(unique_role), None);
|
||||
assert!(!TikiPallet::user_tikis(user).contains(&unique_role));
|
||||
});
|
||||
}
|
||||
|
||||
// === Scoring System Testleri ===
|
||||
// === Scoring System Tests ===
|
||||
|
||||
#[test]
|
||||
fn tiki_scoring_works_correctly() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 2;
|
||||
|
||||
// NFT bas (Welati otomatik eklenir - 10 puan)
|
||||
// Mint NFT (Welati is added automatically - 10 points)
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user), 10);
|
||||
|
||||
// Yüksek puanlı rol ekle
|
||||
assert_ok!(TikiPallet::grant_elected_role(RuntimeOrigin::root(), user, TikiEnum::Serok)); // 200 puan
|
||||
// Add a high-scoring role
|
||||
assert_ok!(TikiPallet::grant_elected_role(RuntimeOrigin::root(), user, TikiEnum::Serok)); // 200 points
|
||||
|
||||
// Toplam puanı kontrol et (10 + 200 = 210)
|
||||
// Verify the total score (10 + 200 = 210)
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user), 210);
|
||||
|
||||
// Başka bir rol ekle
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), user, TikiEnum::Axa)); // 250 puan
|
||||
// Add another role
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), user, TikiEnum::Axa)); // 250 points
|
||||
|
||||
// Toplam puan (10 + 200 + 250 = 460)
|
||||
// Total score (10 + 200 + 250 = 460)
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user), 460);
|
||||
});
|
||||
}
|
||||
@@ -359,7 +359,7 @@ fn tiki_scoring_works_correctly() {
|
||||
#[test]
|
||||
fn scoring_system_comprehensive() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Test individual scores - Anayasa v5.0'a göre
|
||||
// Test individual scores - according to Anayasa v5.0
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Axa), 250);
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::RêveberêProjeyê), 250);
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Serok), 200);
|
||||
@@ -373,7 +373,7 @@ fn scoring_system_comprehensive() {
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Parlementer), 100);
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Xezinedar), 100);
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::PisporêEwlehiyaSîber), 100);
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Bazargan), 60); // Yeni eklenen
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Bazargan), 60); // Newly added
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Mela), 50);
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Feqî), 50);
|
||||
assert_eq!(TikiPallet::get_bonus_for_tiki(&TikiEnum::Welati), 10);
|
||||
@@ -387,25 +387,25 @@ fn scoring_updates_after_role_changes() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 2;
|
||||
|
||||
// NFT bas
|
||||
// Mint NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// İki rol ekle
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir)); // 100 puan
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger)); // 150 puan
|
||||
// Add two roles
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir)); // 100 points
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger)); // 150 points
|
||||
|
||||
// Toplam: 10 + 100 + 150 = 260
|
||||
// Total: 10 + 100 + 150 = 260
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user), 260);
|
||||
|
||||
// Bir rolü kaldır
|
||||
// Revoke one role
|
||||
assert_ok!(TikiPallet::revoke_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir));
|
||||
|
||||
// Puan güncellenmeli: 10 + 150 = 160
|
||||
// The score should be updated: 10 + 150 = 160
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user), 160);
|
||||
});
|
||||
}
|
||||
|
||||
// === Multiple Users ve Isolation Testleri ===
|
||||
// === Multiple Users and Isolation Tests ===
|
||||
|
||||
#[test]
|
||||
fn multiple_users_work_independently() {
|
||||
@@ -413,40 +413,40 @@ fn multiple_users_work_independently() {
|
||||
let user1 = 2;
|
||||
let user2 = 3;
|
||||
|
||||
// Her iki kullanıcı için NFT bas
|
||||
// Mint NFT for both users
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user1));
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user2));
|
||||
|
||||
// Farklı roller ver
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), user1, TikiEnum::Axa)); // 250 puan
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user2, TikiEnum::Wezir)); // 100 puan
|
||||
// Grant different roles
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), user1, TikiEnum::Axa)); // 250 points
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user2, TikiEnum::Wezir)); // 100 points
|
||||
|
||||
// Puanları kontrol et
|
||||
// Verify the scores
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user1), 260); // 10 + 250
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user2), 110); // 10 + 100
|
||||
|
||||
// Rollerin doğru dağıldığını kontrol et
|
||||
// Verify the roles are distributed correctly
|
||||
assert!(TikiPallet::user_tikis(user1).contains(&TikiEnum::Axa));
|
||||
assert!(!TikiPallet::user_tikis(user1).contains(&TikiEnum::Wezir));
|
||||
|
||||
assert!(TikiPallet::user_tikis(user2).contains(&TikiEnum::Wezir));
|
||||
assert!(!TikiPallet::user_tikis(user2).contains(&TikiEnum::Axa));
|
||||
|
||||
// TikiProvider trait testleri
|
||||
// TikiProvider trait tests
|
||||
assert!(TikiPallet::has_tiki(&user1, &TikiEnum::Axa));
|
||||
assert!(!TikiPallet::has_tiki(&user1, &TikiEnum::Wezir));
|
||||
assert_eq!(TikiPallet::get_user_tikis(&user1).len(), 2); // Welati + Axa
|
||||
});
|
||||
}
|
||||
|
||||
// === Edge Cases ve Error Handling ===
|
||||
// === Edge Cases and Error Handling ===
|
||||
|
||||
#[test]
|
||||
fn cannot_grant_role_without_citizen_nft() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user_account = 2;
|
||||
|
||||
// NFT olmadan rol vermeye çalış
|
||||
// Attempt to grant a role without an NFT
|
||||
assert_noop!(
|
||||
TikiPallet::grant_tiki(RuntimeOrigin::root(), user_account, TikiEnum::Wezir),
|
||||
Error::<Test>::CitizenNftNotFound
|
||||
@@ -464,7 +464,7 @@ fn nft_id_increments_correctly() {
|
||||
assert_eq!(TikiPallet::citizen_nft(*user), Some(i as u32));
|
||||
}
|
||||
|
||||
// Next ID'nin doğru arttığını kontrol et
|
||||
// Verify the next ID increments correctly
|
||||
assert_eq!(TikiPallet::next_item_id(), users.len() as u32);
|
||||
});
|
||||
}
|
||||
@@ -475,11 +475,11 @@ fn duplicate_roles_not_allowed() {
|
||||
let user = 2;
|
||||
let role = TikiEnum::Mamoste;
|
||||
|
||||
// NFT bas ve rol ver
|
||||
// Mint NFT and grant the role
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), user, role));
|
||||
|
||||
// Aynı rolü tekrar vermeye çalış
|
||||
// Attempt to grant the same role again
|
||||
assert_noop!(
|
||||
TikiPallet::grant_earned_role(RuntimeOrigin::root(), user, role),
|
||||
Error::<Test>::UserAlreadyHasRole
|
||||
@@ -492,10 +492,10 @@ fn citizen_nft_already_exists_error() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 2;
|
||||
|
||||
// İlk NFT'yi bas
|
||||
// Mint the first NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Aynı kullanıcıya tekrar NFT basmaya çalış
|
||||
// Attempt to mint an NFT again for the same user
|
||||
assert_noop!(
|
||||
TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user),
|
||||
Error::<Test>::CitizenNftAlreadyExists
|
||||
@@ -509,10 +509,10 @@ fn cannot_revoke_role_user_does_not_have() {
|
||||
let user = 2;
|
||||
let role = TikiEnum::Wezir;
|
||||
|
||||
// NFT bas ama rol verme
|
||||
// Mint NFT but do not grant a role
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Sahip olmadığı rolü kaldırmaya çalış
|
||||
// Attempt to revoke a role the user does not have
|
||||
assert_noop!(
|
||||
TikiPallet::revoke_tiki(RuntimeOrigin::root(), user, role),
|
||||
Error::<Test>::RoleNotAssigned
|
||||
@@ -530,10 +530,10 @@ fn nft_transfer_protection_works() {
|
||||
let collection_id = 0; // TikiCollectionId
|
||||
let item_id = 0;
|
||||
|
||||
// NFT bas
|
||||
// Mint NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user1));
|
||||
|
||||
// Transfer korumasını test et
|
||||
// Test the transfer protection
|
||||
assert_noop!(
|
||||
TikiPallet::check_transfer_permission(
|
||||
RuntimeOrigin::signed(user1),
|
||||
@@ -552,10 +552,10 @@ fn non_tiki_nft_transfer_allowed() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user1 = 2;
|
||||
let user2 = 3;
|
||||
let other_collection_id = 1; // Farklı koleksiyon
|
||||
let other_collection_id = 1; // Different collection
|
||||
let item_id = 0;
|
||||
|
||||
// Diğer koleksiyonlar için transfer izni olmalı
|
||||
// Transfers should be permitted for other collections
|
||||
assert_ok!(TikiPallet::check_transfer_permission(
|
||||
RuntimeOrigin::signed(user1),
|
||||
other_collection_id,
|
||||
@@ -573,11 +573,11 @@ fn tiki_provider_trait_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 2;
|
||||
|
||||
// NFT bas
|
||||
// Mint NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir));
|
||||
|
||||
// TikiProvider trait fonksiyonlarını test et
|
||||
// Test the TikiProvider trait functions
|
||||
assert!(TikiPallet::is_citizen(&user));
|
||||
assert!(TikiPallet::has_tiki(&user, &TikiEnum::Welati));
|
||||
assert!(TikiPallet::has_tiki(&user, &TikiEnum::Wezir));
|
||||
@@ -595,10 +595,10 @@ fn complex_multi_role_scenario() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 2;
|
||||
|
||||
// NFT bas
|
||||
// Mint NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Çeşitli tipte roller ekle
|
||||
// Add roles of various types
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir)); // Appointed
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), user, TikiEnum::Mamoste)); // Earned
|
||||
assert_ok!(TikiPallet::grant_elected_role(
|
||||
@@ -607,17 +607,17 @@ fn complex_multi_role_scenario() {
|
||||
TikiEnum::Parlementer
|
||||
)); // Elected
|
||||
|
||||
// Tüm rollerin eklendiğini kontrol et
|
||||
// Verify all roles were added
|
||||
let user_tikis = TikiPallet::user_tikis(user);
|
||||
assert!(user_tikis.contains(&TikiEnum::Welati)); // 10 puan
|
||||
assert!(user_tikis.contains(&TikiEnum::Wezir)); // 100 puan
|
||||
assert!(user_tikis.contains(&TikiEnum::Mamoste)); // 70 puan
|
||||
assert!(user_tikis.contains(&TikiEnum::Parlementer)); // 100 puan
|
||||
assert!(user_tikis.contains(&TikiEnum::Welati)); // 10 points
|
||||
assert!(user_tikis.contains(&TikiEnum::Wezir)); // 100 points
|
||||
assert!(user_tikis.contains(&TikiEnum::Mamoste)); // 70 points
|
||||
assert!(user_tikis.contains(&TikiEnum::Parlementer)); // 100 points
|
||||
|
||||
// Toplam puanı kontrol et (10 + 100 + 70 + 100 = 280)
|
||||
// Verify the total score (10 + 100 + 70 + 100 = 280)
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user), 280);
|
||||
|
||||
// Bir rolü kaldır ve puanın güncellendiğini kontrol et
|
||||
// Revoke one role and verify the score is updated
|
||||
assert_ok!(TikiPallet::revoke_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir));
|
||||
assert_eq!(TikiPallet::get_tiki_score(&user), 180); // 280 - 100 = 180
|
||||
});
|
||||
@@ -646,7 +646,7 @@ fn role_assignment_type_logic_comprehensive() {
|
||||
RoleAssignmentType::Elected
|
||||
);
|
||||
|
||||
// Earned roles (Sosyal roller + bazı uzman roller)
|
||||
// Earned roles (social roles + some expert roles)
|
||||
assert_eq!(
|
||||
TikiPallet::get_role_assignment_type(&TikiEnum::Axa),
|
||||
RoleAssignmentType::Earned
|
||||
@@ -668,7 +668,7 @@ fn role_assignment_type_logic_comprehensive() {
|
||||
RoleAssignmentType::Earned
|
||||
);
|
||||
|
||||
// Appointed roles (Memur rolleri - default)
|
||||
// Appointed roles (officer roles - default)
|
||||
assert_eq!(
|
||||
TikiPallet::get_role_assignment_type(&TikiEnum::Wezir),
|
||||
RoleAssignmentType::Appointed
|
||||
@@ -688,19 +688,19 @@ fn role_assignment_type_logic_comprehensive() {
|
||||
});
|
||||
}
|
||||
|
||||
// === Performance ve Stress Tests ===
|
||||
// === Performance and Stress Tests ===
|
||||
|
||||
#[test]
|
||||
fn stress_test_multiple_users_roles() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let users = vec![2, 3, 4, 5];
|
||||
|
||||
// Tüm kullanıcılar için NFT bas
|
||||
// Mint NFT for all users
|
||||
for user in &users {
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), *user));
|
||||
}
|
||||
|
||||
// Her kullanıcıya farklı rol kombinasyonları ver
|
||||
// Grant different role combinations to each user
|
||||
|
||||
// User 2: High-level elected roles
|
||||
assert_ok!(TikiPallet::grant_elected_role(RuntimeOrigin::root(), 2, TikiEnum::Serok)); // Unique
|
||||
@@ -726,17 +726,17 @@ fn stress_test_multiple_users_roles() {
|
||||
assert_ok!(TikiPallet::grant_earned_role(RuntimeOrigin::root(), 5, TikiEnum::Axa));
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), 5, TikiEnum::Dadger));
|
||||
|
||||
// Puanları kontrol et
|
||||
// Verify the scores
|
||||
assert_eq!(TikiPallet::get_tiki_score(&2), 310); // 10 + 200 + 100
|
||||
assert_eq!(TikiPallet::get_tiki_score(&3), 180); // 10 + 70 + 100
|
||||
assert_eq!(TikiPallet::get_tiki_score(&4), 260); // 10 + 100 + 150
|
||||
assert_eq!(TikiPallet::get_tiki_score(&5), 410); // 10 + 250 + 150
|
||||
|
||||
// Unique rollerin doğru atandığını kontrol et
|
||||
// Verify the unique roles are assigned correctly
|
||||
assert_eq!(TikiPallet::tiki_holder(TikiEnum::Serok), Some(2));
|
||||
assert_eq!(TikiPallet::tiki_holder(TikiEnum::SerokiMeclise), Some(4));
|
||||
|
||||
// Toplam vatandaş sayısını kontrol et
|
||||
// Verify the total citizen count
|
||||
let mut citizen_count = 0;
|
||||
for user in &users {
|
||||
if TikiPallet::is_citizen(user) {
|
||||
@@ -752,10 +752,10 @@ fn maximum_roles_per_user_limit() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 2;
|
||||
|
||||
// NFT bas
|
||||
// Mint NFT
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Test amaçlı sadece birkaç rol ekle (metadata uzunluk limitini aşmamak için)
|
||||
// For testing purposes add only a few roles (to avoid exceeding the metadata length limit)
|
||||
let roles_to_add = vec![
|
||||
TikiEnum::Wezir,
|
||||
TikiEnum::Dadger,
|
||||
@@ -765,19 +765,19 @@ fn maximum_roles_per_user_limit() {
|
||||
TikiEnum::Berdevk,
|
||||
];
|
||||
|
||||
// Rolleri ekle
|
||||
// Add the roles
|
||||
for role in roles_to_add {
|
||||
if TikiPallet::can_grant_role_type(&role, &RoleAssignmentType::Appointed) {
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, role));
|
||||
}
|
||||
}
|
||||
|
||||
// Kullanıcının pek çok role sahip olduğunu kontrol et
|
||||
// Verify the user holds many roles
|
||||
let final_tikis = TikiPallet::user_tikis(user);
|
||||
assert!(final_tikis.len() >= 5); // En az 5 rol olmalı (Welati + 4+ diğer)
|
||||
assert!(final_tikis.len() <= 100); // Max limit'i aşmamalı
|
||||
assert!(final_tikis.len() >= 5); // Should be at least 5 roles (Welati + 4 or more others)
|
||||
assert!(final_tikis.len() <= 100); // Should not exceed the max limit
|
||||
|
||||
// Toplam puanın makul olduğunu kontrol et
|
||||
// Verify the total score is reasonable
|
||||
assert!(TikiPallet::get_tiki_score(&user) > 200);
|
||||
});
|
||||
}
|
||||
@@ -791,20 +791,20 @@ fn apply_for_citizenship_twice_same_user() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 5;
|
||||
|
||||
// İlk başvuru - use force_mint to bypass KYC
|
||||
// First application - use force_mint to bypass KYC
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
let first_score = TikiPallet::get_tiki_score(&user);
|
||||
assert_eq!(first_score, 10);
|
||||
|
||||
// İkinci kez mint etmeye çalış (başarısız olmalı - zaten NFT var)
|
||||
// Attempt to mint a second time (should fail - the NFT already exists)
|
||||
assert_noop!(
|
||||
TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user),
|
||||
Error::<Test>::CitizenNftAlreadyExists
|
||||
);
|
||||
|
||||
let second_score = TikiPallet::get_tiki_score(&user);
|
||||
assert_eq!(second_score, 10); // Skor değişmemeli
|
||||
assert_eq!(second_score, 10); // The score should not change
|
||||
});
|
||||
}
|
||||
|
||||
@@ -815,7 +815,7 @@ fn apply_for_citizenship_adds_hemwelati() {
|
||||
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Welati rolü var
|
||||
// The Welati role is present
|
||||
let tikis = TikiPallet::user_tikis(user);
|
||||
assert!(tikis.contains(&TikiEnum::Welati));
|
||||
});
|
||||
@@ -828,7 +828,7 @@ fn apply_for_citizenship_initial_score() {
|
||||
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Welati puanı 10
|
||||
// Welati score is 10
|
||||
let score = TikiPallet::get_tiki_score(&user);
|
||||
assert_eq!(score, 10);
|
||||
});
|
||||
@@ -843,7 +843,7 @@ fn apply_for_citizenship_multiple_users_independent() {
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), *user));
|
||||
}
|
||||
|
||||
// Hepsi 10 puana sahip olmalı
|
||||
// All should have a score of 10
|
||||
for user in &users {
|
||||
assert_eq!(TikiPallet::get_tiki_score(user), 10);
|
||||
}
|
||||
@@ -859,21 +859,21 @@ fn revoke_tiki_reduces_score() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 13;
|
||||
|
||||
// NFT bas ve rol ekle
|
||||
// Mint NFT and add a role
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger));
|
||||
|
||||
let initial_score = TikiPallet::get_tiki_score(&user);
|
||||
assert!(initial_score > 10);
|
||||
|
||||
// Rolü geri al
|
||||
// Revoke the role
|
||||
assert_ok!(TikiPallet::revoke_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger));
|
||||
|
||||
// Skor düştü
|
||||
// The score has decreased
|
||||
let final_score = TikiPallet::get_tiki_score(&user);
|
||||
assert!(final_score < initial_score);
|
||||
|
||||
// Rol listesinde yok
|
||||
// It is not in the role list
|
||||
let tikis = TikiPallet::user_tikis(user);
|
||||
assert!(!tikis.contains(&TikiEnum::Dadger));
|
||||
});
|
||||
@@ -902,7 +902,7 @@ fn revoke_tiki_nonexistent_role() {
|
||||
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Kullanıcı bu role sahip değil
|
||||
// The user does not have this role
|
||||
assert_noop!(
|
||||
TikiPallet::revoke_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir),
|
||||
Error::<Test>::RoleNotAssigned
|
||||
@@ -931,16 +931,16 @@ fn get_tiki_score_role_accumulation() {
|
||||
|
||||
assert_ok!(TikiPallet::force_mint_citizen_nft(RuntimeOrigin::root(), user));
|
||||
|
||||
// Başlangıç: Welati = 10
|
||||
// Initial: Welati = 10
|
||||
let score1 = TikiPallet::get_tiki_score(&user);
|
||||
assert_eq!(score1, 10);
|
||||
|
||||
// Dadger ekle (+150)
|
||||
// Add Dadger (+150)
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger));
|
||||
let score2 = TikiPallet::get_tiki_score(&user);
|
||||
assert_eq!(score2, 160); // 10 + 150
|
||||
|
||||
// Wezir ekle (+100)
|
||||
// Add Wezir (+100)
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir));
|
||||
let score3 = TikiPallet::get_tiki_score(&user);
|
||||
assert_eq!(score3, 260); // 10 + 150 + 100
|
||||
@@ -959,7 +959,7 @@ fn get_tiki_score_revoke_decreases() {
|
||||
let score_before = TikiPallet::get_tiki_score(&user);
|
||||
assert_eq!(score_before, 280); // 10 + 150 + 120
|
||||
|
||||
// Bir rolü geri al
|
||||
// Revoke one role
|
||||
assert_ok!(TikiPallet::revoke_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger));
|
||||
|
||||
let score_after = TikiPallet::get_tiki_score(&user);
|
||||
@@ -981,10 +981,10 @@ fn user_tikis_updated_after_grant() {
|
||||
let tikis_before = TikiPallet::user_tikis(user);
|
||||
assert_eq!(tikis_before.len(), 1); // Only Welati
|
||||
|
||||
// Rol ekle
|
||||
// Add a role
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger));
|
||||
|
||||
// UserTikis güncellendi
|
||||
// UserTikis was updated
|
||||
let tikis_after = TikiPallet::user_tikis(user);
|
||||
assert_eq!(tikis_after.len(), 2);
|
||||
assert!(tikis_after.contains(&TikiEnum::Dadger));
|
||||
@@ -1000,7 +1000,7 @@ fn user_tikis_consistent_with_score() {
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Dadger));
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user, TikiEnum::Wezir));
|
||||
|
||||
// UserTikis sayısı ile score tutarlı olmalı
|
||||
// The UserTikis count should be consistent with the score
|
||||
let user_tikis = TikiPallet::user_tikis(user);
|
||||
let score = TikiPallet::get_tiki_score(&user);
|
||||
|
||||
@@ -1021,7 +1021,7 @@ fn multiple_users_independent_roles() {
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user1, TikiEnum::Dadger));
|
||||
assert_ok!(TikiPallet::grant_tiki(RuntimeOrigin::root(), user2, TikiEnum::Wezir));
|
||||
|
||||
// Roller bağımsız
|
||||
// The roles are independent
|
||||
let tikis1 = TikiPallet::user_tikis(user1);
|
||||
let tikis2 = TikiPallet::user_tikis(user2);
|
||||
|
||||
|
||||
@@ -32,13 +32,18 @@
|
||||
//! ## Score Calculation
|
||||
//!
|
||||
//! ```text
|
||||
//! trust_score = (staking_score + referral_score + perwerde_score + tiki_score) * multiplier
|
||||
//! weighted_sum = 100 * staking_score
|
||||
//! + 300 * (referral_score + perwerde_score + tiki_score)
|
||||
//! trust_score = staking_score * weighted_sum / base
|
||||
//! ```
|
||||
//!
|
||||
//! Where:
|
||||
//! - Each component score is normalized and weighted
|
||||
//! - The multiplier is configurable via `ScoreMultiplierBase`
|
||||
//! - Citizenship status is required (KYC approved)
|
||||
//! - `base` is configurable via `ScoreMultiplierBase`.
|
||||
//! - Staking is both a gate and a multiplier: a citizen with zero staking_score
|
||||
//! has a trust_score of zero, and staking scales the entire weighted sum.
|
||||
//! - Within weighted_sum, referral/perwerde/tiki each carry weight 300 versus
|
||||
//! staking's 100 (3x per unit), rewarding contribution over capital alone.
|
||||
//! - Citizenship status is required (KYC approved).
|
||||
//!
|
||||
//! ## Update Mechanisms
|
||||
//!
|
||||
@@ -361,19 +366,19 @@ pub mod pezpallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Periyodik güncellemeyi başlatan function
|
||||
/// Function that starts the periodic update
|
||||
#[pezpallet::call_index(2)]
|
||||
#[pezpallet::weight(<T as Config>::WeightInfo::periodic_trust_score_update())]
|
||||
pub fn periodic_trust_score_update(origin: OriginFor<T>) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
|
||||
// Eğer önceki update devam ediyorsa bekle
|
||||
// If a previous update is still in progress, wait
|
||||
ensure!(!BatchUpdateInProgress::<T>::get(), Error::<T>::UpdateInProgress);
|
||||
|
||||
// Yeni periyodik güncellemeyi başlat
|
||||
// Start the new periodic update
|
||||
Self::update_all_trust_scores(OriginFor::<T>::root())?;
|
||||
|
||||
// Bir sonraki periyodik güncellemeyi schedule et
|
||||
// Schedule the next periodic update
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
let next_update_block = current_block + T::UpdateInterval::get();
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ impl pezpallet_identity_kyc::Config for Test {
|
||||
|
||||
parameter_types! {
|
||||
pub const ScoreMultiplierBase: u128 = 1000;
|
||||
pub const TrustUpdateInterval: u64 = 100; // Test için kısa interval
|
||||
pub const TrustUpdateInterval: u64 = 100; // Short interval for tests
|
||||
pub const MaxBatchSizeValue: u32 = 100; // Max users per batch
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ impl pezpallet_trust::TikiScoreProvider<u64> for MockTikiScoreProvider {
|
||||
pub struct MockCitizenshipStatusProvider;
|
||||
impl pezpallet_trust::CitizenshipStatusProvider<u64> for MockCitizenshipStatusProvider {
|
||||
fn is_citizen(who: &u64) -> bool {
|
||||
// Test için: 1-100 arası hesaplar vatandaş, 999 değil
|
||||
// For tests: accounts 1-100 are citizens, 999 is not
|
||||
*who >= 1 && *who <= 100 && *who != 999
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +107,13 @@ fn force_recalculate_trust_score_requires_root() {
|
||||
#[test]
|
||||
fn update_all_trust_scores_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Event'leri yakalamak için block number set et
|
||||
// Set block number to capture events
|
||||
System::set_block_number(1);
|
||||
|
||||
assert_ok!(TrustPallet::update_all_trust_scores(RuntimeOrigin::root()));
|
||||
|
||||
// Mock implementation boş account listesi kullandığı için
|
||||
// AllTrustScoresUpdated event'i yayınlanır (count: 0 ile)
|
||||
// Because the mock implementation uses an empty account list,
|
||||
// the AllTrustScoresUpdated event is emitted (with count: 0)
|
||||
let events = System::events();
|
||||
assert!(events.iter().any(|event| {
|
||||
matches!(
|
||||
@@ -134,18 +134,18 @@ fn update_all_trust_scores_requires_root() {
|
||||
#[test]
|
||||
fn periodic_trust_score_update_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Event'leri yakalamak için block number set et
|
||||
// Set block number to capture events
|
||||
System::set_block_number(1);
|
||||
|
||||
assert_ok!(TrustPallet::periodic_trust_score_update(RuntimeOrigin::root()));
|
||||
|
||||
// Periyodik güncelleme event'inin yayınlandığını kontrol et
|
||||
// Verify the periodic update event was emitted
|
||||
let events = System::events();
|
||||
assert!(events.iter().any(|event| {
|
||||
matches!(event.event, RuntimeEvent::TrustPallet(Event::PeriodicUpdateScheduled { .. }))
|
||||
}));
|
||||
|
||||
// Ayrıca AllTrustScoresUpdated event'i de yayınlanmalı
|
||||
// The AllTrustScoresUpdated event should also be emitted
|
||||
assert!(events.iter().any(|event| {
|
||||
matches!(event.event, RuntimeEvent::TrustPallet(Event::AllTrustScoresUpdated { .. }))
|
||||
}));
|
||||
@@ -155,10 +155,10 @@ fn periodic_trust_score_update_works() {
|
||||
#[test]
|
||||
fn periodic_update_fails_when_batch_in_progress() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Batch update'i başlat
|
||||
// Start the batch update
|
||||
crate::BatchUpdateInProgress::<Test>::put(true);
|
||||
|
||||
// Periyodik update'in başarısız olmasını bekle
|
||||
// Expect the periodic update to fail
|
||||
assert_noop!(
|
||||
TrustPallet::periodic_trust_score_update(RuntimeOrigin::root()),
|
||||
Error::<Test>::UpdateInProgress
|
||||
@@ -211,18 +211,18 @@ fn trust_score_updater_trait_works() {
|
||||
#[test]
|
||||
fn batch_update_storage_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Başlangıçta batch update aktif değil
|
||||
// Initially the batch update is not active
|
||||
assert!(!crate::BatchUpdateInProgress::<Test>::get());
|
||||
assert!(crate::LastProcessedAccount::<Test>::get().is_none());
|
||||
|
||||
// Batch update'i simüle et
|
||||
// Simulate the batch update
|
||||
crate::BatchUpdateInProgress::<Test>::put(true);
|
||||
crate::LastProcessedAccount::<Test>::put(42u64);
|
||||
|
||||
assert!(crate::BatchUpdateInProgress::<Test>::get());
|
||||
assert_eq!(crate::LastProcessedAccount::<Test>::get(), Some(42u64));
|
||||
|
||||
// Temizle
|
||||
// Clean up
|
||||
crate::BatchUpdateInProgress::<Test>::put(false);
|
||||
crate::LastProcessedAccount::<Test>::kill();
|
||||
|
||||
@@ -238,7 +238,7 @@ fn periodic_update_scheduling_works() {
|
||||
|
||||
assert_ok!(TrustPallet::periodic_trust_score_update(RuntimeOrigin::root()));
|
||||
|
||||
// Event'te next_block'un doğru hesaplandığını kontrol et
|
||||
// Verify that next_block is calculated correctly in the event
|
||||
let events = System::events();
|
||||
let scheduled_event = events.iter().find(|event| {
|
||||
matches!(event.event, RuntimeEvent::TrustPallet(Event::PeriodicUpdateScheduled { .. }))
|
||||
|
||||
@@ -910,9 +910,14 @@ pub mod pezpallet {
|
||||
#[pezpallet::call_index(10)]
|
||||
#[pezpallet::weight(<T as pezpallet::Config>::WeightInfo::nominate_official())]
|
||||
#[pezpallet::feeless_if(|origin: &OriginFor<T>, _nominee: &T::AccountId, _role: &OfficialRole, _justification: &BoundedVec<u8, ConstU32<1000>>| -> bool {
|
||||
// Governance members are exempt from fees when performing official duties
|
||||
// Fee exemption must mirror the in-body authorization check exactly
|
||||
// (Serok or a Minister) -- NOT the broader is_governance_member set,
|
||||
// otherwise unauthorized governance-adjacent accounts (Parliament/Diwan
|
||||
// members) could spam this call for free and always fail authorization.
|
||||
match ensure_signed(origin.clone()) {
|
||||
Ok(who) => Pezpallet::<T>::is_governance_member(&who),
|
||||
Ok(who) => {
|
||||
Pezpallet::<T>::is_serok(&who) || Pezpallet::<T>::is_minister(&who)
|
||||
},
|
||||
Err(_) => false,
|
||||
}
|
||||
})]
|
||||
@@ -1012,6 +1017,15 @@ pub mod pezpallet {
|
||||
Error::<T>::AppointmentAlreadyProcessed
|
||||
);
|
||||
|
||||
// Re-validate that the role is still unfilled. Two competing
|
||||
// AppointmentProcess entries (different nominees, same role) can both
|
||||
// reach WaitingPresidentialApproval; without this check, approving a
|
||||
// second stale process would silently overwrite an already-appointed
|
||||
// official with no removal event and no error.
|
||||
if let Some(current_holder) = AppointedOfficials::<T>::get(process.position) {
|
||||
ensure!(current_holder == process.nominee, Error::<T>::RoleAlreadyFilled);
|
||||
}
|
||||
|
||||
// Get nomination
|
||||
let mut nomination = PendingNominations::<T>::get(process.position, &process.nominee)
|
||||
.ok_or(Error::<T>::NominationNotFound)?;
|
||||
@@ -1045,10 +1059,13 @@ pub mod pezpallet {
|
||||
|
||||
#[pezpallet::call_index(20)]
|
||||
#[pezpallet::weight(<T as pezpallet::Config>::WeightInfo::submit_proposal())]
|
||||
#[pezpallet::feeless_if(|origin: &OriginFor<T>, _title: &BoundedVec<u8, ConstU32<100>>, _description: &BoundedVec<u8, ConstU32<1000>>, _decision_type: &CollectiveDecisionType, _priority: &ProposalPriority, _call: &Option<Box<<T as pezframe_system::Config>::RuntimeCall>>| -> bool {
|
||||
// Governance members are exempt from fees when submitting proposals
|
||||
#[pezpallet::feeless_if(|origin: &OriginFor<T>, _title: &BoundedVec<u8, ConstU32<100>>, _description: &BoundedVec<u8, ConstU32<1000>>, decision_type: &CollectiveDecisionType, _priority: &ProposalPriority, _call: &Option<Box<<T as pezframe_system::Config>::RuntimeCall>>| -> bool {
|
||||
// Fee exemption must mirror can_propose() exactly (Serok, or Parliament
|
||||
// member/Serok for everything else) -- NOT the broader is_governance_member
|
||||
// set, otherwise Diwan members/Ministers could spam this call for free and
|
||||
// always fail authorization.
|
||||
match ensure_signed(origin.clone()) {
|
||||
Ok(who) => Pezpallet::<T>::is_governance_member(&who),
|
||||
Ok(who) => Pezpallet::<T>::can_propose(&who, decision_type).unwrap_or(false),
|
||||
Err(_) => false,
|
||||
}
|
||||
})]
|
||||
@@ -1106,10 +1123,16 @@ pub mod pezpallet {
|
||||
|
||||
#[pezpallet::call_index(21)]
|
||||
#[pezpallet::weight(<T as pezpallet::Config>::WeightInfo::vote_on_proposal())]
|
||||
#[pezpallet::feeless_if(|origin: &OriginFor<T>, _proposal_id: &u32, _vote: &VoteChoice, _rationale: &Option<BoundedVec<u8, ConstU32<500>>>| -> bool {
|
||||
// Governance members are exempt from fees when voting
|
||||
#[pezpallet::feeless_if(|origin: &OriginFor<T>, proposal_id: &u32, _vote: &VoteChoice, _rationale: &Option<BoundedVec<u8, ConstU32<500>>>| -> bool {
|
||||
// Fee exemption must mirror the in-body per-decision-type authorization
|
||||
// check exactly -- NOT the broader is_governance_member set, otherwise
|
||||
// accounts unauthorized for this specific proposal's decision type
|
||||
// (e.g. Ministers, who are never authorized to vote) could spam this
|
||||
// call for free and always fail authorization.
|
||||
match ensure_signed(origin.clone()) {
|
||||
Ok(who) => Pezpallet::<T>::is_governance_member(&who),
|
||||
Ok(who) => ActiveProposals::<T>::get(proposal_id)
|
||||
.map(|proposal| Pezpallet::<T>::is_authorized_to_vote(&proposal.decision_type, &who))
|
||||
.unwrap_or(false),
|
||||
Err(_) => false,
|
||||
}
|
||||
})]
|
||||
@@ -1132,36 +1155,12 @@ pub mod pezpallet {
|
||||
let proposal =
|
||||
ActiveProposals::<T>::get(proposal_id).ok_or(Error::<T>::ProposalNotFound)?;
|
||||
|
||||
// Enforce access control based on decision type
|
||||
match proposal.decision_type {
|
||||
CollectiveDecisionType::ParliamentSimpleMajority
|
||||
| CollectiveDecisionType::ParliamentSuperMajority
|
||||
| CollectiveDecisionType::ParliamentAbsoluteMajority
|
||||
| CollectiveDecisionType::VetoOverride => {
|
||||
// Parliament members only
|
||||
let members = ParliamentMembers::<T>::get();
|
||||
let is_member = members.iter().any(|m| m.account == voter);
|
||||
ensure!(is_member, Error::<T>::NotAuthorizedToVote);
|
||||
},
|
||||
CollectiveDecisionType::ConstitutionalReview
|
||||
| CollectiveDecisionType::ConstitutionalUnanimous => {
|
||||
// Diwan members only
|
||||
ensure!(Self::is_diwan_member(&voter), Error::<T>::NotAuthorizedToVote,);
|
||||
},
|
||||
CollectiveDecisionType::ExecutiveDecision => {
|
||||
// Serok (President) only
|
||||
let serok = CurrentOfficials::<T>::get(GovernmentPosition::Serok);
|
||||
ensure!(serok.as_ref() == Some(&voter), Error::<T>::NotAuthorizedToVote,);
|
||||
},
|
||||
CollectiveDecisionType::HybridDecision => {
|
||||
// Parliament members OR Serok
|
||||
let members = ParliamentMembers::<T>::get();
|
||||
let is_parliament = members.iter().any(|m| m.account == voter);
|
||||
let serok = CurrentOfficials::<T>::get(GovernmentPosition::Serok);
|
||||
let is_serok = serok.as_ref() == Some(&voter);
|
||||
ensure!(is_parliament || is_serok, Error::<T>::NotAuthorizedToVote,);
|
||||
},
|
||||
}
|
||||
// Enforce access control based on decision type (shared with feeless_if
|
||||
// via is_authorized_to_vote so the two checks cannot drift apart)
|
||||
ensure!(
|
||||
Self::is_authorized_to_vote(&proposal.decision_type, &voter),
|
||||
Error::<T>::NotAuthorizedToVote
|
||||
);
|
||||
|
||||
// Record the vote
|
||||
let vote_info = CollectiveVote {
|
||||
@@ -1328,6 +1327,16 @@ pub mod pezpallet {
|
||||
election_id: u32,
|
||||
election: &ElectionInfo<T>,
|
||||
) -> Result<ElectionOutcome<T::AccountId>, Error<T>> {
|
||||
// Enforce the minimum turnout quorum recorded on the election at initiation
|
||||
// time. Without this check, elections could be finalized (and officeholders
|
||||
// seated) with arbitrarily low citizen participation.
|
||||
let total_citizen_count = Self::get_total_citizen_count();
|
||||
if total_citizen_count > 0 {
|
||||
let turnout_percentage = ((election.total_votes as u64).saturating_mul(100)
|
||||
/ total_citizen_count as u64) as u8;
|
||||
ensure!(turnout_percentage >= election.minimum_turnout, Error::<T>::QuorumNotMet);
|
||||
}
|
||||
|
||||
let mut candidates_with_votes: Vec<(T::AccountId, u32)> = election
|
||||
.candidates
|
||||
.iter()
|
||||
@@ -1348,7 +1357,9 @@ pub mod pezpallet {
|
||||
candidates_with_votes.iter().map(|(_, v)| *v).sum::<u32>().max(1);
|
||||
let (top_winner, top_vote_count) = candidates_with_votes[0].clone();
|
||||
|
||||
if (top_vote_count * 100) / total_valid_votes >= 50 {
|
||||
if ((top_vote_count as u64).saturating_mul(100)) / (total_valid_votes as u64)
|
||||
>= 50
|
||||
{
|
||||
let winners_vec: BoundedVec<_, _> = vec![top_winner]
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T>::CalculationOverflow)?;
|
||||
@@ -1410,39 +1421,76 @@ pub mod pezpallet {
|
||||
}
|
||||
},
|
||||
ElectionType::Parliamentary => {
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
let term_end = current_block
|
||||
+ BlockNumberFor::<T>::from(4u32 * 365u32 * 24u32 * 60u32 * 10u32);
|
||||
// Guard against wiping the sitting Parliament when an election produces
|
||||
// no winners (e.g. zero candidates registered). Leave the previous
|
||||
// Parliament untouched in that case, mirroring the Presidential and
|
||||
// SpeakerElection arms below.
|
||||
if !winners.is_empty() {
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
let term_end = current_block
|
||||
+ BlockNumberFor::<T>::from(4u32 * 365u32 * 24u32 * 60u32 * 10u32);
|
||||
|
||||
let parliament_members: Result<BoundedVec<_, _>, _> = winners
|
||||
.iter()
|
||||
.map(|winner| ParliamentMember {
|
||||
account: winner.clone(),
|
||||
elected_at: current_block,
|
||||
term_ends_at: term_end,
|
||||
votes_participated: 0,
|
||||
total_votes_eligible: 0,
|
||||
participation_rate: 100,
|
||||
committees: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into();
|
||||
let parliament_members: Result<BoundedVec<_, _>, _> = winners
|
||||
.iter()
|
||||
.map(|winner| ParliamentMember {
|
||||
account: winner.clone(),
|
||||
elected_at: current_block,
|
||||
term_ends_at: term_end,
|
||||
votes_participated: 0,
|
||||
total_votes_eligible: 0,
|
||||
participation_rate: 100,
|
||||
committees: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into();
|
||||
|
||||
ParliamentMembers::<T>::put(
|
||||
parliament_members.map_err(|_| Error::<T>::ParliamentFull)?,
|
||||
);
|
||||
ParliamentMembers::<T>::put(
|
||||
parliament_members.map_err(|_| Error::<T>::ParliamentFull)?,
|
||||
);
|
||||
|
||||
Self::deposit_event(Event::ParliamentUpdated {
|
||||
new_members: winners.to_vec(),
|
||||
term_start: current_block,
|
||||
});
|
||||
Self::deposit_event(Event::ParliamentUpdated {
|
||||
new_members: winners.to_vec(),
|
||||
term_start: current_block,
|
||||
});
|
||||
}
|
||||
},
|
||||
ElectionType::SpeakerElection => {
|
||||
if let Some(winner) = winners.first() {
|
||||
CurrentOfficials::<T>::insert(GovernmentPosition::MeclisBaskanı, winner);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
ElectionType::ConstitutionalCourt => {
|
||||
// Same non-destructive guard as Parliamentary: an empty winners list
|
||||
// leaves the sitting Diwan untouched instead of wiping it.
|
||||
if !winners.is_empty() {
|
||||
let current_block = pezframe_system::Pezpallet::<T>::block_number();
|
||||
let term_end = current_block
|
||||
+ BlockNumberFor::<T>::from(9u32 * 365u32 * 24u32 * 60u32 * 10u32);
|
||||
|
||||
let diwan_members: Result<BoundedVec<_, _>, _> = winners
|
||||
.iter()
|
||||
.map(|winner| DiwanMember {
|
||||
account: winner.clone(),
|
||||
appointed_at: current_block,
|
||||
term_ends_at: term_end,
|
||||
appointed_by: AppointmentAuthority::Parliament,
|
||||
specialization: ConstitutionalSpecialization::FundamentalRights,
|
||||
decisions_made: 0,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into();
|
||||
|
||||
DiwanMembers::<T>::put(diwan_members.map_err(|_| Error::<T>::DiwanFull)?);
|
||||
|
||||
for winner in winners {
|
||||
Self::deposit_event(Event::DiwanMemberAppointed {
|
||||
member: winner.clone(),
|
||||
appointed_by: AppointmentAuthority::Parliament,
|
||||
specialization: ConstitutionalSpecialization::FundamentalRights,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1469,6 +1517,27 @@ pub mod pezpallet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check voting authority for a given decision type. Shared by
|
||||
/// `vote_on_proposal`'s in-body enforcement and its `feeless_if` fee
|
||||
/// exemption gate so the two checks can never drift apart.
|
||||
pub fn is_authorized_to_vote(
|
||||
decision_type: &CollectiveDecisionType,
|
||||
voter: &T::AccountId,
|
||||
) -> bool {
|
||||
match decision_type {
|
||||
CollectiveDecisionType::ParliamentSimpleMajority
|
||||
| CollectiveDecisionType::ParliamentSuperMajority
|
||||
| CollectiveDecisionType::ParliamentAbsoluteMajority
|
||||
| CollectiveDecisionType::VetoOverride => Self::is_parliament_member(voter),
|
||||
CollectiveDecisionType::ConstitutionalReview
|
||||
| CollectiveDecisionType::ConstitutionalUnanimous => Self::is_diwan_member(voter),
|
||||
CollectiveDecisionType::ExecutiveDecision => Self::is_serok(voter),
|
||||
CollectiveDecisionType::HybridDecision => {
|
||||
Self::is_parliament_member(voter) || Self::is_serok(voter)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate voting threshold
|
||||
fn get_voting_threshold(decision_type: &CollectiveDecisionType) -> u32 {
|
||||
match decision_type {
|
||||
|
||||
@@ -103,7 +103,7 @@ impl pezpallet_timestamp::Config for Test {
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
// Mock Randomness - SADECE BİR KEZ TANIMLA
|
||||
// Mock Randomness - DEFINE ONLY ONCE
|
||||
pub struct MockRandomness;
|
||||
impl Randomness<H256, u64> for MockRandomness {
|
||||
fn random(_subject: &[u8]) -> (H256, u64) {
|
||||
@@ -288,11 +288,23 @@ impl pezpallet_identity_kyc::Config for Test {
|
||||
}
|
||||
|
||||
// Staking Score Configuration
|
||||
parameter_types! {
|
||||
pub const StakingScoreNoterBondAmount: Balance = 1_000;
|
||||
pub const StakingScoreDisputeWindow: u64 = 10;
|
||||
pub const StakingScoreSlashDestination: AccountId = 999;
|
||||
}
|
||||
|
||||
impl pezpallet_staking_score::Config for Test {
|
||||
type Balance = Balance;
|
||||
type OnStakingUpdate = ();
|
||||
type WeightInfo = ();
|
||||
type NoterChecker = ();
|
||||
type Currency = Balances;
|
||||
type NoterBondAmount = StakingScoreNoterBondAmount;
|
||||
type DisputeWindow = StakingScoreDisputeWindow;
|
||||
type DisputeOrigin = pezframe_system::EnsureSigned<AccountId>;
|
||||
type SlashOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type SlashDestination = StakingScoreSlashDestination;
|
||||
}
|
||||
|
||||
// Referral Configuration
|
||||
@@ -316,6 +328,8 @@ parameter_types! {
|
||||
|
||||
impl pezpallet_tiki::Config for Test {
|
||||
type AdminOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type ElectedRoleOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type EarnedRoleOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
type WeightInfo = ();
|
||||
type MaxTikisPerUser = MaxTikisPerUser;
|
||||
type Tiki = pezpallet_tiki::Tiki;
|
||||
@@ -323,56 +337,56 @@ impl pezpallet_tiki::Config for Test {
|
||||
type TrustScoreUpdater = ();
|
||||
}
|
||||
|
||||
// Mock implementations for required traits - YÜKSEK SKORLAR VER
|
||||
// Mock implementations for required traits - PROVIDE HIGH SCORES
|
||||
pub struct MockStakingScoreProvider;
|
||||
impl pezpallet_staking_score::StakingScoreProvider<AccountId, u64> for MockStakingScoreProvider {
|
||||
fn get_staking_score(_account: &AccountId) -> (u32, u64) {
|
||||
(1000, 0) // Yüksek skor
|
||||
(1000, 0) // High score
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockReferralScoreProvider;
|
||||
impl pezpallet_trust::ReferralScoreProvider<AccountId> for MockReferralScoreProvider {
|
||||
fn get_referral_score(_account: &AccountId) -> u32 {
|
||||
500 // Yüksek skor
|
||||
500 // High score
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockPerwerdeScoreProvider;
|
||||
impl pezpallet_trust::PerwerdeScoreProvider<AccountId> for MockPerwerdeScoreProvider {
|
||||
fn get_perwerde_score(_account: &AccountId) -> u32 {
|
||||
750 // Yüksek skor
|
||||
750 // High score
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockTikiScoreProvider;
|
||||
|
||||
// `pezpallet_trust` için implementasyon
|
||||
// Implementation for `pezpallet_trust`
|
||||
impl pezpallet_trust::TikiScoreProvider<AccountId> for MockTikiScoreProvider {
|
||||
fn get_tiki_score(_account: &AccountId) -> u32 {
|
||||
100
|
||||
}
|
||||
}
|
||||
|
||||
// `pezpallet_welati`'nin ihtiyaç duyduğu `pezpallet_tiki` için implementasyon
|
||||
// Implementation for `pezpallet_tiki`, which `pezpallet_welati` requires
|
||||
impl pezpallet_tiki::TikiScoreProvider<AccountId> for MockTikiScoreProvider {
|
||||
fn get_tiki_score(_account: &AccountId) -> u32 {
|
||||
1000 // Yüksek Tiki score - tüm kontrolleri geçer
|
||||
1000 // High Tiki score - passes all checks
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockCitizenshipStatusProvider;
|
||||
impl pezpallet_trust::CitizenshipStatusProvider<AccountId> for MockCitizenshipStatusProvider {
|
||||
fn is_citizen(_account: &AccountId) -> bool {
|
||||
true // Herkes vatandaş
|
||||
true // Everyone is a citizen
|
||||
}
|
||||
}
|
||||
|
||||
// MOCK TRUST PROVIDER - HERKES İÇİN YÜKSEK SKOR
|
||||
// MOCK TRUST PROVIDER - HIGH SCORE FOR EVERYONE
|
||||
pub struct MockTrustProvider;
|
||||
impl pezpallet_trust::TrustScoreProvider<AccountId> for MockTrustProvider {
|
||||
fn trust_score_of(_account: &AccountId) -> u128 {
|
||||
1000u128 // Herkes için yüksek trust score
|
||||
1000u128 // High trust score for everyone
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +417,7 @@ impl pezpallet_trust::Config for Test {
|
||||
type CitizenshipSource = MockCitizenshipStatusProvider;
|
||||
}
|
||||
|
||||
// Welati Configuration - SADECE BİR KEZ TANIMLA
|
||||
// Welati Configuration - DEFINE ONLY ONCE
|
||||
parameter_types! {
|
||||
pub const ParliamentSize: u32 = 201;
|
||||
pub const DiwanSize: u32 = 11;
|
||||
@@ -421,9 +435,9 @@ impl pezpallet_welati::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type Randomness = MockRandomness;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type TrustScoreSource = MockTrustProvider; // Mock provider kullan
|
||||
type TikiSource = MockTikiScoreProvider; // Mock Tiki provider kullan
|
||||
type CitizenSource = MockTrustProvider; // Mock provider kullan
|
||||
type TrustScoreSource = MockTrustProvider; // Use the mock provider
|
||||
type TikiSource = MockTikiScoreProvider; // Use the mock Tiki provider
|
||||
type CitizenSource = MockTrustProvider; // Use the mock provider
|
||||
type KycSource = IdentityKyc;
|
||||
type ParliamentSize = ParliamentSize;
|
||||
type DiwanSize = DiwanSize;
|
||||
@@ -438,7 +452,7 @@ impl pezpallet_welati::Config for Test {
|
||||
type MaxEndorsers = MaxEndorsers;
|
||||
}
|
||||
|
||||
// CRITICAL: CitizenInfo trait implementation - SADECE BİR KEZ TANIMLA
|
||||
// CRITICAL: CitizenInfo trait implementation - DEFINE ONLY ONCE
|
||||
impl CitizenInfo for Trust {
|
||||
fn citizen_count() -> u32 {
|
||||
110
|
||||
@@ -476,14 +490,14 @@ impl ExtBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// SIMPLIFIED TEST USER SETUP - BOŞ BIRAK, MOCK PROVIDERS YETERLI
|
||||
// SIMPLIFIED TEST USER SETUP - LEAVE EMPTY, MOCK PROVIDERS ARE SUFFICIENT
|
||||
pub fn setup_test_users() {
|
||||
// Mock provider'lar zaten herkesin yüksek trust score'u olmasını sağlıyor
|
||||
// ve TikiScoreProvider da herkesin Tiki'ye sahip olduğunu söylüyor
|
||||
// Bu sayede pezpallet-tiki ile uğraşmak zorunda kalmıyoruz
|
||||
// The mock providers already ensure everyone has a high trust score,
|
||||
// and the TikiScoreProvider also reports that everyone owns a Tiki.
|
||||
// This way we do not have to deal with pezpallet-tiki.
|
||||
|
||||
// Sadece NFTs collection'ı oluşturuldu, bu yeterli
|
||||
// Testlerde KYC kontrolü zaten bypass ediliyor
|
||||
// Only the NFTs collection was created, and that is sufficient.
|
||||
// The KYC check is already bypassed in tests.
|
||||
}
|
||||
|
||||
// CRITICAL HELPER FUNCTION FOR TESTS
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
use pezframe_support::{assert_noop, assert_ok, BoundedVec};
|
||||
use pezsp_runtime::traits::BadOrigin;
|
||||
|
||||
// ===== SEÇİM SİSTEMİ TESTLERİ =====
|
||||
// ===== ELECTION SYSTEM TESTS =====
|
||||
|
||||
#[test]
|
||||
fn initiate_election_works() {
|
||||
@@ -155,7 +155,7 @@ fn register_candidate_fails_already_candidate() {
|
||||
#[test]
|
||||
fn cast_vote_works() {
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
// 1. Seçimi başlat
|
||||
// 1. Start the election
|
||||
assert_ok!(Welati::initiate_election(
|
||||
RuntimeOrigin::root(),
|
||||
ElectionType::Parliamentary,
|
||||
@@ -163,28 +163,28 @@ fn cast_vote_works() {
|
||||
None,
|
||||
));
|
||||
|
||||
// 2. Bir aday kaydet (hesap 1)
|
||||
let endorsers: Vec<u64> = (3..=52).collect(); // 50 destekçi
|
||||
// 2. Register a candidate (account 1)
|
||||
let endorsers: Vec<u64> = (3..=52).collect(); // 50 endorsers
|
||||
assert_ok!(Welati::register_candidate(
|
||||
RuntimeOrigin::signed(1), // Aday
|
||||
0, // Seçim ID'si
|
||||
None, // Bölge ID'si
|
||||
RuntimeOrigin::signed(1), // Candidate
|
||||
0, // Election ID
|
||||
None, // District ID
|
||||
endorsers,
|
||||
));
|
||||
|
||||
// 3. Oy verme periyoduna ilerle
|
||||
// 3. Advance to the voting period
|
||||
run_to_block(86_400 + 259_200 + 1);
|
||||
|
||||
// 4. Oy kullan (hesap 2, aday 1'e oy veriyor)
|
||||
// 4. Cast a vote (account 2 votes for candidate 1)
|
||||
let candidates_to_vote_for = vec![1];
|
||||
assert_ok!(Welati::cast_vote(
|
||||
RuntimeOrigin::signed(2), // Seçmen
|
||||
0, // Seçim ID'si
|
||||
candidates_to_vote_for.clone(), // Oy verilen aday(lar)
|
||||
None, // Bölge ID'si
|
||||
RuntimeOrigin::signed(2), // Voter
|
||||
0, // Election ID
|
||||
candidates_to_vote_for.clone(), // Candidate(s) voted for
|
||||
None, // District ID
|
||||
));
|
||||
|
||||
// 5. Event'i ve depolama durumunu doğrula
|
||||
// 5. Verify the event and storage state
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::Welati(WelatiEvent::VoteCast {
|
||||
@@ -253,9 +253,19 @@ fn finalize_election_works() {
|
||||
None,
|
||||
));
|
||||
|
||||
// Seçimin bitiş tarihinden sonrasına geç
|
||||
// A candidate and enough voters to clear the 40% Parliamentary quorum
|
||||
// (MockTrustProvider's fixed citizen_count() is 110, so 44+ votes).
|
||||
let endorsers: Vec<u64> = (200..=249).collect();
|
||||
assert_ok!(Welati::register_candidate(RuntimeOrigin::signed(1), 0, None, endorsers,));
|
||||
|
||||
run_to_block(86_400 + 259_200 + 1);
|
||||
for voter in 300..=343u64 {
|
||||
assert_ok!(Welati::cast_vote(RuntimeOrigin::signed(voter), 0, vec![1], None,));
|
||||
}
|
||||
|
||||
// Move past the election end date
|
||||
// candidacy (86_400) + campaign (259_200) + voting (432_000) + 1
|
||||
run_to_block(86_400 + 259_200 + 432_000 + 10); // Ekstra güvenlik için +10
|
||||
run_to_block(86_400 + 259_200 + 432_000 + 10); // +10 for extra safety
|
||||
|
||||
assert_ok!(Welati::finalize_election(RuntimeOrigin::root(), 0,));
|
||||
|
||||
@@ -265,7 +275,7 @@ fn finalize_election_works() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== ATAMA SİSTEMİ TESTLERİ =====
|
||||
// ===== APPOINTMENT SYSTEM TESTS =====
|
||||
|
||||
#[test]
|
||||
fn nominate_official_works() {
|
||||
@@ -305,7 +315,7 @@ fn approve_appointment_works() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== KOLLEKTİF KARAR TESTLERİ =====
|
||||
// ===== COLLECTIVE DECISION TESTS =====
|
||||
|
||||
#[test]
|
||||
fn submit_proposal_works() {
|
||||
@@ -313,7 +323,7 @@ fn submit_proposal_works() {
|
||||
let title = b"Test Proposal".to_vec().try_into().unwrap();
|
||||
let description = b"Test proposal description".to_vec().try_into().unwrap();
|
||||
|
||||
// CRITICAL FIX: Helper fonksiyonu kullan
|
||||
// CRITICAL FIX: Use the helper function
|
||||
add_parliament_member(1);
|
||||
|
||||
assert_ok!(Welati::submit_proposal(
|
||||
@@ -336,7 +346,7 @@ fn vote_on_proposal_works() {
|
||||
let title = b"Test Proposal".to_vec().try_into().unwrap();
|
||||
let description = b"Test proposal description".to_vec().try_into().unwrap();
|
||||
|
||||
// CRITICAL FIX: Helper fonksiyonları kullan
|
||||
// CRITICAL FIX: Use the helper functions
|
||||
add_parliament_member(1);
|
||||
add_parliament_member(2);
|
||||
|
||||
@@ -365,7 +375,7 @@ fn vote_on_proposal_works() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== HELPER FUNCTION TESTLERİ =====
|
||||
// ===== HELPER FUNCTION TESTS =====
|
||||
|
||||
#[test]
|
||||
fn get_required_trust_score_works() {
|
||||
@@ -412,7 +422,7 @@ fn calculate_vote_weight_works() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== ERROR CASE TESTLERİ =====
|
||||
// ===== ERROR CASE TESTS =====
|
||||
|
||||
#[test]
|
||||
fn election_not_found_error_works() {
|
||||
@@ -434,12 +444,12 @@ fn proposal_not_found_error_works() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== INTEGRATION TESTLERİ =====
|
||||
// ===== INTEGRATION TESTS =====
|
||||
|
||||
#[test]
|
||||
fn complete_election_cycle_works() {
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
// 1. Seçim başlat
|
||||
// 1. Start the election
|
||||
assert_ok!(Welati::initiate_election(
|
||||
RuntimeOrigin::root(),
|
||||
ElectionType::Parliamentary,
|
||||
@@ -447,7 +457,7 @@ fn complete_election_cycle_works() {
|
||||
None,
|
||||
));
|
||||
|
||||
// 2. Adaylar kaydolsun
|
||||
// 2. Register candidates
|
||||
let endorsers1: Vec<u64> = (10..=59).collect();
|
||||
let endorsers2: Vec<u64> = (60..=109).collect();
|
||||
|
||||
@@ -455,15 +465,20 @@ fn complete_election_cycle_works() {
|
||||
|
||||
assert_ok!(Welati::register_candidate(RuntimeOrigin::signed(2), 0, None, endorsers2,));
|
||||
|
||||
// 3. Voting period'a geç
|
||||
// 3. Move to the voting period
|
||||
run_to_block(86_400 + 259_200 + 1);
|
||||
|
||||
// 4. Oylar kullanılsın
|
||||
// 4. Cast votes — enough to clear the 40% Parliamentary quorum
|
||||
// (MockTrustProvider's fixed citizen_count() is 110, so 44+ votes).
|
||||
assert_ok!(Welati::cast_vote(RuntimeOrigin::signed(3), 0, vec![1], None,));
|
||||
|
||||
assert_ok!(Welati::cast_vote(RuntimeOrigin::signed(4), 0, vec![2], None,));
|
||||
|
||||
// 5. Seçimi sonlandır
|
||||
for voter in 500..=541u64 {
|
||||
assert_ok!(Welati::cast_vote(RuntimeOrigin::signed(voter), 0, vec![1], None,));
|
||||
}
|
||||
|
||||
// 5. Finalize the election
|
||||
run_to_block(86_400 + 259_200 + 432_000 + 2);
|
||||
|
||||
assert_ok!(Welati::finalize_election(RuntimeOrigin::root(), 0,));
|
||||
@@ -501,7 +516,7 @@ fn complete_proposal_cycle_works() {
|
||||
let title = b"Budget Amendment".to_vec().try_into().unwrap();
|
||||
let description = b"Increase education budget by 10%".to_vec().try_into().unwrap();
|
||||
|
||||
// CRITICAL FIX: Helper fonksiyonları kullan
|
||||
// CRITICAL FIX: Use the helper functions
|
||||
add_parliament_member(1);
|
||||
add_parliament_member(2);
|
||||
add_parliament_member(3);
|
||||
@@ -528,7 +543,7 @@ fn complete_proposal_cycle_works() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== RUNOFF ELECTION TESTLERİ =====
|
||||
// ===== RUNOFF ELECTION TESTS =====
|
||||
|
||||
#[test]
|
||||
fn initiate_runoff_election_works() {
|
||||
@@ -787,7 +802,12 @@ fn finalize_election_updates_election_status() {
|
||||
|
||||
run_to_block(86_400 + 259_200 + 100);
|
||||
|
||||
// Enough votes to clear the 40% Parliamentary quorum (MockTrustProvider's
|
||||
// fixed citizen_count() is 110, so 44+ votes).
|
||||
assert_ok!(Welati::cast_vote(RuntimeOrigin::signed(100), 0, vec![1], None,));
|
||||
for voter in 600..=642u64 {
|
||||
assert_ok!(Welati::cast_vote(RuntimeOrigin::signed(voter), 0, vec![1], None,));
|
||||
}
|
||||
|
||||
run_to_block(86_400 + 259_200 + 432_000 + 100);
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -21,7 +21,7 @@ fn main() {
|
||||
#[cfg(all(feature = "metadata-hash", feature = "std"))]
|
||||
fn main() {
|
||||
bizinikiwi_wasm_builder::WasmBuilder::init_with_defaults()
|
||||
.enable_metadata_hash("TYR", 12)
|
||||
.enable_metadata_hash("HEZ", 12)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
+7
-9
@@ -31,9 +31,7 @@ use pezframe_support::build_struct_json_patch;
|
||||
use pezsp_core::crypto::UncheckedInto;
|
||||
use pezsp_genesis_builder::PresetId;
|
||||
use pezsp_keyring::Sr25519Keyring;
|
||||
use testnet_teyrchains_constants::pezkuwichain::{
|
||||
currency::UNITS as TYR, xcm_version::SAFE_XCM_VERSION,
|
||||
};
|
||||
use testnet_teyrchains_constants::pezkuwichain::{currency::UNITS, xcm_version::SAFE_XCM_VERSION};
|
||||
use teyrchains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_builder::GlobalConsensusConvertsFor;
|
||||
@@ -62,16 +60,16 @@ pub const PEZ_DECIMALS: u8 = 12;
|
||||
pub const WUSDT_DECIMALS: u8 = 6;
|
||||
|
||||
/// Treasury allocation: 20.25% = 1,012,500,000 PEZ
|
||||
pub const PEZ_TREASURY_ALLOCATION: Balance = 1_012_500_000 * TYR;
|
||||
pub const PEZ_TREASURY_ALLOCATION: Balance = 1_012_500_000 * UNITS;
|
||||
|
||||
/// Founder allocation: 1.875% = 93,750,000 PEZ
|
||||
pub const PEZ_FOUNDER_ALLOCATION: Balance = 93_750_000 * TYR;
|
||||
pub const PEZ_FOUNDER_ALLOCATION: Balance = 93_750_000 * UNITS;
|
||||
|
||||
/// Presale allocation: 1.875% = 93,750,000 PEZ
|
||||
pub const PEZ_PRESALE_ALLOCATION: Balance = 93_750_000 * TYR;
|
||||
pub const PEZ_PRESALE_ALLOCATION: Balance = 93_750_000 * UNITS;
|
||||
|
||||
/// Rewards pool: 76% = 3,800,000,000 PEZ (distributed via sentetik halving)
|
||||
pub const PEZ_REWARDS_POOL: Balance = 3_800_000_000 * TYR;
|
||||
pub const PEZ_REWARDS_POOL: Balance = 3_800_000_000 * UNITS;
|
||||
|
||||
/// Total PEZ supply: 5 Billion (derived from allocations)
|
||||
pub const PEZ_TOTAL_SUPPLY: Balance =
|
||||
@@ -79,7 +77,7 @@ pub const PEZ_TOTAL_SUPPLY: Balance =
|
||||
|
||||
// Compile-time verification that total equals expected 5 billion
|
||||
const _: () = assert!(
|
||||
PEZ_TOTAL_SUPPLY == 5_000_000_000 * TYR,
|
||||
PEZ_TOTAL_SUPPLY == 5_000_000_000 * UNITS,
|
||||
"PEZ allocations must sum to exactly 5 billion tokens"
|
||||
);
|
||||
|
||||
@@ -322,7 +320,7 @@ pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
|
||||
Sr25519Keyring::AliceStash.to_account_id(),
|
||||
Sr25519Keyring::BobStash.to_account_id(),
|
||||
],
|
||||
TYR * 1_000_000,
|
||||
UNITS * 1_000_000,
|
||||
1000.into(),
|
||||
treasury_account,
|
||||
founder_account,
|
||||
|
||||
@@ -138,7 +138,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: alloc::borrow::Cow::Borrowed("asset-hub-pezkuwichain"),
|
||||
impl_name: alloc::borrow::Cow::Borrowed("asset-hub-pezkuwichain"),
|
||||
authoring_version: 1,
|
||||
spec_version: 1_020_007,
|
||||
spec_version: 1_020_011,
|
||||
impl_version: 0,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
transaction_version: 16,
|
||||
|
||||
@@ -278,7 +278,14 @@ impl pezpallet_staking_async::Config for Runtime {
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type CurrencyToVote = pezsp_staking::currency_to_vote::SaturatingCurrencyToVote;
|
||||
// U128, not Saturating: HEZ has 12 decimals and total issuance (~204M HEZ = ~2.04e20 planck)
|
||||
// far exceeds u64::MAX (~1.8446e19). SaturatingCurrencyToVote casts balance->u64 saturating, so
|
||||
// any stash bonding more than ~18.45M HEZ collapses to u64::MAX. Two large nominators (~41M and
|
||||
// ~40M HEZ) both saturated to u64::MAX, becoming tied voters; the resulting tied edge weights hit
|
||||
// the `reduce_4` "duplicate/corrupt input" panic in the offchain election miner, so no solution
|
||||
// was ever produced and no era exposures were written. U128CurrencyToVote scales by
|
||||
// (total_issuance / u64::MAX) instead, keeping every stake distinct and inside u64 range.
|
||||
type CurrencyToVote = pezsp_staking::currency_to_vote::U128CurrencyToVote;
|
||||
type RewardRemainder = ResolveTo<xcm_config::TreasuryAccount, Balances>;
|
||||
type Slash = ResolveTo<xcm_config::TreasuryAccount, Balances>;
|
||||
type Reward = ();
|
||||
|
||||
@@ -321,7 +321,7 @@ pub type TrustedReserves = (
|
||||
|
||||
/// Cases where a remote origin is accepted as trusted Teleporter for a given asset:
|
||||
///
|
||||
/// - TYR with the parent Relay Chain and sibling system teyrchains; and
|
||||
/// - HEZ with the parent Relay Chain and sibling system teyrchains; and
|
||||
/// - Sibling teyrchains' assets from where they originate (as `ForeignCreators`).
|
||||
pub type TrustedTeleporters = (
|
||||
ConcreteAssetFromSystem<TokenLocation>,
|
||||
@@ -518,7 +518,7 @@ pub mod bridging {
|
||||
/// 1) an approximate cost of XCM execution (`ExportMessage` and surroundings) at Pezkuwichain bridge hub;
|
||||
///
|
||||
/// 2) the approximate cost of Pezkuwichain -> Zagros message delivery transaction on Zagros Bridge Hub,
|
||||
/// converted into TYRs using 1:1 conversion rate;
|
||||
/// converted into HEZ using 1:1 conversion rate;
|
||||
///
|
||||
/// 3) the approximate cost of Pezkuwichain -> Zagros message confirmation transaction on Pezkuwichain Bridge Hub.
|
||||
pub storage XcmBridgeHubRouterBaseFee: Balance =
|
||||
@@ -616,7 +616,7 @@ pub mod bridging {
|
||||
|
||||
parameter_types! {
|
||||
/// User fee for ERC20 token transfer back to Ethereum.
|
||||
/// (initially was calculated by test `OutboundQueue::calculate_fees` - ETH/TYR 1/400 and fee_per_gas 20 GWEI = 2200698000000 + *25%)
|
||||
/// (initially was calculated by test `OutboundQueue::calculate_fees` - ETH/HEZ 1/400 and fee_per_gas 20 GWEI = 2200698000000 + *25%)
|
||||
/// Needs to be more than fee calculated from DefaultFeeConfig FeeConfigRecord in snowbridge:teyrchain/pallets/outbound-queue/src/lib.rs
|
||||
/// Pezkuwi uses 10 decimals, Dicle and Pezkuwichain 12 decimals.
|
||||
pub const DefaultBridgeHubEthereumBaseFee: Balance = 3_833_568_200_000;
|
||||
|
||||
@@ -829,7 +829,7 @@ mod asset_hub_pezkuwichain_tests {
|
||||
);
|
||||
},
|
||||
|| {
|
||||
// `SwapFirstAssetTrader` - staking pot receives xcm fees in TYRs
|
||||
// `SwapFirstAssetTrader` - staking pot receives xcm fees in HEZ
|
||||
assert!(
|
||||
Balances::free_balance(&staking_pot) > ExistentialDeposit::get()
|
||||
);
|
||||
|
||||
+2
-2
@@ -148,7 +148,7 @@ pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
|
||||
dev_stakers,
|
||||
1000.into(),
|
||||
vec![
|
||||
// bridged TYR
|
||||
// bridged HEZ
|
||||
(
|
||||
Location::new(2, [GlobalConsensus(PezkuwichainNetwork::get())]),
|
||||
GlobalConsensusConvertsFor::<UniversalLocation, AccountId>::convert_location(
|
||||
@@ -159,7 +159,7 @@ pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
|
||||
),
|
||||
],
|
||||
vec![
|
||||
// bridged TYR to Bob
|
||||
// bridged HEZ to Bob
|
||||
(
|
||||
Location::new(2, [GlobalConsensus(PezkuwichainNetwork::get())]),
|
||||
Sr25519Keyring::Bob.to_account_id(),
|
||||
|
||||
@@ -145,13 +145,15 @@ impl_opaque_keys! {
|
||||
|
||||
#[pezsp_version::runtime_version]
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
// Note: "westmint" is the legacy name for this chain. It has been renamed to
|
||||
// "asset-hub-zagros". Many wallets/tools depend on the `spec_name`, so it remains "westmint"
|
||||
// for the time being. Wallets/tools should update to treat "asset-hub-zagros" equally.
|
||||
spec_name: alloc::borrow::Cow::Borrowed("westmint"),
|
||||
impl_name: alloc::borrow::Cow::Borrowed("westmint"),
|
||||
// spec_name is "asset-hub-zagros", consistent with the already-rebranded mainnet
|
||||
// Asset Hub ("asset-hub-pezkuwichain"). The upstream legacy name was "westmint";
|
||||
// it is dropped here for full independence. This is a testnet identity change that
|
||||
// takes effect via a runtime upgrade — tooling keyed on the old "westmint" name
|
||||
// for the Zagros Asset Hub must update.
|
||||
spec_name: alloc::borrow::Cow::Borrowed("asset-hub-zagros"),
|
||||
impl_name: alloc::borrow::Cow::Borrowed("asset-hub-zagros"),
|
||||
authoring_version: 1,
|
||||
spec_version: 1_020_004,
|
||||
spec_version: 1_020_005,
|
||||
impl_version: 0,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
transaction_version: 16,
|
||||
@@ -2469,7 +2471,7 @@ pezpallet_revive::impl_runtime_apis_plus_revive_traits!(
|
||||
&account,
|
||||
<Balances as Inspect<_>>::minimum_balance(),
|
||||
));
|
||||
// register foreign TYRs
|
||||
// register foreign HEZ
|
||||
assert_ok!(ForeignAssets::force_create(
|
||||
RuntimeOrigin::root(),
|
||||
roc_id.clone().into(),
|
||||
|
||||
@@ -277,7 +277,9 @@ impl pezpallet_staking_async::Config for Runtime {
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type CurrencyToVote = pezsp_staking::currency_to_vote::SaturatingCurrencyToVote;
|
||||
// U128, not Saturating — see asset-hub-pezkuwichain: Saturating clips any stake above
|
||||
// ~u64::MAX planck to u64::MAX, producing tied voters that panic the election miner's reduce_4.
|
||||
type CurrencyToVote = pezsp_staking::currency_to_vote::U128CurrencyToVote;
|
||||
type RewardRemainder = ();
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
|
||||
@@ -1102,7 +1102,7 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_pezkuwichain_fees_paid_by_
|
||||
ExistentialDeposit::get(),
|
||||
AccountId::from([73; 32]),
|
||||
block_author_account.clone(),
|
||||
// receiving TYRs
|
||||
// receiving HEZ
|
||||
foreign_asset_create_params,
|
||||
1000000000000,
|
||||
|| {
|
||||
@@ -1131,7 +1131,7 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_pezkuwichain_fees_paid_by_
|
||||
);
|
||||
},
|
||||
|| {
|
||||
// `SwapFirstAssetTrader` - staking pot receives xcm fees in TYRs
|
||||
// `SwapFirstAssetTrader` - staking pot receives xcm fees in HEZ
|
||||
assert!(
|
||||
Balances::free_balance(&staking_pot) > ExistentialDeposit::get()
|
||||
);
|
||||
@@ -1182,7 +1182,7 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_pezkuwichain_fees_paid_by_
|
||||
ExistentialDeposit::get(),
|
||||
AccountId::from([73; 32]),
|
||||
block_author_account.clone(),
|
||||
// receiving TYRs
|
||||
// receiving HEZ
|
||||
foreign_asset_create_params,
|
||||
1000000000000,
|
||||
|| {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
- [Init bridge and run relayer between BridgeHubpezkuwichain and
|
||||
BridgeHubzagros](#init-bridge-and-run-relayer-between-bridgehubpezkuwichain-and-bridgehubzagros)
|
||||
- [Initialize configuration for transfer asset over bridge
|
||||
(TYRs/WNDs)](#initialize-configuration-for-transfer-asset-over-bridge-rocswnds)
|
||||
- [Send messages - transfer asset over bridge (TYRs/WNDs)](#send-messages---transfer-asset-over-bridge-rocswnds)
|
||||
(HEZ/WNDs)](#initialize-configuration-for-transfer-asset-over-bridge-rocswnds)
|
||||
- [Send messages - transfer asset over bridge (HEZ/WNDs)](#send-messages---transfer-asset-over-bridge-rocswnds)
|
||||
- [Claim relayer's rewards on BridgeHubpezkuwichain and
|
||||
BridgeHubzagros](#claim-relayers-rewards-on-bridgehubpezkuwichain-and-bridgehubzagros)
|
||||
- [How to test local BridgeHubDicle/BridgeHubPezkuwiChain](#how-to-test-local-bridgehubdiclebridgehubpezkuwi)
|
||||
@@ -127,10 +127,10 @@ cd <pezkuwi-sdk-git-repo-dir>
|
||||
- zagros teyrchain: - https://pezkuwichain.io/?rpc=ws%3A%2F%2F127.0.0.1%3A8945#/chainstate - Pezpallet:
|
||||
**bridgepezkuwichainTeyrchains** - Keys: **parasInfo(None)**
|
||||
|
||||
### Initialize configuration for transfer asset over bridge (TYRs/WNDs)
|
||||
### Initialize configuration for transfer asset over bridge (HEZ/WNDs)
|
||||
|
||||
This initialization does several things:
|
||||
- creates `ForeignAssets` for wrappedTYRs/wrappedWNDs
|
||||
- creates `ForeignAssets` for wrappedHEZ/wrappedWNDs
|
||||
- drips SA for AssetHubpezkuwichain on AssetHubzagros (and vice versa) which holds reserved assets on source chains
|
||||
```
|
||||
cd <pezkuwi-sdk-git-repo-dir>
|
||||
@@ -141,13 +141,13 @@ cd <pezkuwi-sdk-git-repo-dir>
|
||||
./bridges/testing/environments/pezkuwichain-zagros/bridges_pezkuwichain_zagros.sh init-bridge-hub-zagros-local
|
||||
```
|
||||
|
||||
### Send messages - transfer asset over bridge (TYRs/WNDs)
|
||||
### Send messages - transfer asset over bridge (HEZ/WNDs)
|
||||
|
||||
Do reserve-backed transfers:
|
||||
```
|
||||
cd <pezkuwi-sdk-git-repo-dir>
|
||||
|
||||
# TYRs from pezkuwichain's Asset Hub to zagros's.
|
||||
# HEZ from pezkuwichain's Asset Hub to zagros's.
|
||||
./bridges/testing/environments/pezkuwichain-zagros/bridges_pezkuwichain_zagros.sh reserve-transfer-assets-from-asset-hub-pezkuwichain-local
|
||||
```
|
||||
```
|
||||
@@ -174,7 +174,7 @@ cd <pezkuwi-sdk-git-repo-dir>
|
||||
```
|
||||
cd <pezkuwi-sdk-git-repo-dir>
|
||||
|
||||
# wrappedTYRs from zagros's Asset Hub to pezkuwichain's.
|
||||
# wrappedHEZ from zagros's Asset Hub to pezkuwichain's.
|
||||
./bridges/testing/environments/pezkuwichain-zagros/bridges_pezkuwichain_zagros.sh withdraw-reserve-assets-from-asset-hub-zagros-local
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ use pezpallet_bridge_relayers::extension::{
|
||||
BridgeRelayersTransactionExtension, WithMessagesExtensionConfig,
|
||||
};
|
||||
use pezpallet_xcm_bridge_hub::XcmAsPlainPayload;
|
||||
use testnet_teyrchains_constants::pezkuwichain::currency::UNITS as TYR;
|
||||
use testnet_teyrchains_constants::pezkuwichain::currency::UNITS as HEZ;
|
||||
use xcm::{
|
||||
latest::prelude::*,
|
||||
prelude::{InteriorLocation, NetworkId},
|
||||
@@ -75,7 +75,7 @@ parameter_types! {
|
||||
/// PeoplePezkuwichain location
|
||||
pub PeoplePezkuwichainLocation: Location = Location::new(1, [Teyrchain(pezkuwichain_runtime_constants::system_teyrchain::PEOPLE_ID)]);
|
||||
|
||||
pub storage BridgeDeposit: Balance = 5 * TYR;
|
||||
pub storage BridgeDeposit: Balance = 5 * HEZ;
|
||||
}
|
||||
|
||||
/// Proof of messages, coming from Pezkuwichain Bulletin chain.
|
||||
|
||||
+5
-5
@@ -40,7 +40,7 @@ use pezpallet_bridge_messages::LaneIdOf;
|
||||
use pezpallet_bridge_relayers::extension::{
|
||||
BridgeRelayersTransactionExtension, WithMessagesExtensionConfig,
|
||||
};
|
||||
use testnet_teyrchains_constants::pezkuwichain::currency::UNITS as TYR;
|
||||
use testnet_teyrchains_constants::pezkuwichain::currency::UNITS as HEZ;
|
||||
use teyrchains_common::xcm_config::{AllSiblingSystemTeyrchains, RelayOrOtherSystemTeyrchains};
|
||||
use xcm::{
|
||||
latest::{prelude::*, ZAGROS_GENESIS_HASH},
|
||||
@@ -70,7 +70,7 @@ parameter_types! {
|
||||
]
|
||||
);
|
||||
|
||||
pub storage BridgeDeposit: Balance = 5 * TYR;
|
||||
pub storage BridgeDeposit: Balance = 5 * HEZ;
|
||||
}
|
||||
|
||||
/// Proof of messages, coming from Zagros.
|
||||
@@ -270,12 +270,12 @@ mod tests {
|
||||
///
|
||||
/// We want this tip to be large enough (delivery transactions with more messages = less
|
||||
/// operational costs and a faster bridge), so this value should be significant.
|
||||
const FEE_BOOST_PER_MESSAGE: Balance = 2 * TYR;
|
||||
const FEE_BOOST_PER_MESSAGE: Balance = 2 * HEZ;
|
||||
|
||||
// see `FEE_BOOST_PER_MESSAGE` comment
|
||||
const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * TYR;
|
||||
const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * HEZ;
|
||||
// see `FEE_BOOST_PER_MESSAGE` comment
|
||||
const FEE_BOOST_PER_TEYRCHAIN_HEADER: Balance = 2 * TYR;
|
||||
const FEE_BOOST_PER_TEYRCHAIN_HEADER: Balance = 2 * HEZ;
|
||||
|
||||
#[test]
|
||||
fn ensure_bridge_hub_pezkuwichain_message_lane_weights_are_correct() {
|
||||
|
||||
@@ -26,13 +26,13 @@ fn main() {
|
||||
#[cfg(all(feature = "metadata-hash", feature = "std"))]
|
||||
fn main() {
|
||||
bizinikiwi_wasm_builder::WasmBuilder::init_with_defaults()
|
||||
.enable_metadata_hash("TYR", 12)
|
||||
.enable_metadata_hash("HEZ", 12)
|
||||
.build();
|
||||
|
||||
bizinikiwi_wasm_builder::WasmBuilder::init_with_defaults()
|
||||
.set_file_name("fast_runtime_binary.rs")
|
||||
.enable_feature("fast-runtime")
|
||||
.enable_metadata_hash("TYR", 12)
|
||||
.enable_metadata_hash("HEZ", 12)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ use pezframe_support::build_struct_json_patch;
|
||||
use pezsp_genesis_builder::PresetId;
|
||||
use pezsp_keyring::Sr25519Keyring;
|
||||
use testnet_teyrchains_constants::pezkuwichain::{
|
||||
currency::UNITS as TYR, xcm_version::SAFE_XCM_VERSION,
|
||||
currency::UNITS as HEZ, xcm_version::SAFE_XCM_VERSION,
|
||||
};
|
||||
use teyrchains_common::{AccountId, AuraId};
|
||||
|
||||
@@ -71,7 +71,7 @@ pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
|
||||
(Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()),
|
||||
],
|
||||
Sr25519Keyring::well_known().map(|x| x.to_account_id()).collect(),
|
||||
TYR * 1_000_000,
|
||||
HEZ * 1_000_000,
|
||||
CORETIME_PARA_ID,
|
||||
),
|
||||
pezsp_genesis_builder::DEV_RUNTIME_PRESET => coretime_pezkuwichain_genesis(
|
||||
@@ -83,7 +83,7 @@ pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
|
||||
Sr25519Keyring::AliceStash.to_account_id(),
|
||||
Sr25519Keyring::BobStash.to_account_id(),
|
||||
],
|
||||
TYR * 1_000_000,
|
||||
HEZ * 1_000_000,
|
||||
CORETIME_PARA_ID,
|
||||
),
|
||||
_ => return None,
|
||||
|
||||
@@ -186,7 +186,7 @@ pub type WaivedLocations = (
|
||||
);
|
||||
|
||||
/// Cases where a remote origin is accepted as trusted Teleporter for a given asset:
|
||||
/// - TYR with the parent Relay Chain and sibling teyrchains.
|
||||
/// - HEZ with the parent Relay Chain and sibling teyrchains.
|
||||
pub type TrustedTeleporters = ConcreteAssetFromSystem<RocRelayLocation>;
|
||||
|
||||
pub struct XcmConfig;
|
||||
@@ -196,7 +196,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type XcmEventEmitter = PezkuwiXcm;
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = XcmOriginToTransactDispatchOrigin;
|
||||
// Coretime chain does not recognize a reserve location for any asset. Users must teleport TYR
|
||||
// Coretime chain does not recognize a reserve location for any asset. Users must teleport HEZ
|
||||
// where allowed (e.g. with the Relay Chain).
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = TrustedTeleporters;
|
||||
|
||||
@@ -232,7 +232,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type XcmEventEmitter = PezkuwiXcm;
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = XcmOriginToTransactDispatchOrigin;
|
||||
// Coretime chain does not recognize a reserve location for any asset. Users must teleport TYR
|
||||
// Coretime chain does not recognize a reserve location for any asset. Users must teleport ZGR
|
||||
// where allowed (e.g. with the Relay Chain).
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = TrustedTeleporters;
|
||||
|
||||
+3
-3
@@ -29,7 +29,7 @@ use pezsp_core::{crypto::UncheckedInto, H256};
|
||||
use pezsp_genesis_builder::PresetId;
|
||||
use pezsp_keyring::Sr25519Keyring;
|
||||
use testnet_teyrchains_constants::pezkuwichain::{
|
||||
currency::UNITS as TYR, xcm_version::SAFE_XCM_VERSION,
|
||||
currency::UNITS as HEZ, xcm_version::SAFE_XCM_VERSION,
|
||||
};
|
||||
use teyrchains_common::{AccountId, AuraId};
|
||||
|
||||
@@ -162,7 +162,7 @@ pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
|
||||
(Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()),
|
||||
],
|
||||
Sr25519Keyring::well_known().map(|x| x.to_account_id()).collect(),
|
||||
TYR * 1_000_000,
|
||||
HEZ * 1_000_000,
|
||||
PEOPLE_PARA_ID,
|
||||
// Founding citizens: Alice and Bob are founding citizens for testing
|
||||
vec![
|
||||
@@ -185,7 +185,7 @@ pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
|
||||
Sr25519Keyring::AliceStash.to_account_id(),
|
||||
Sr25519Keyring::BobStash.to_account_id(),
|
||||
],
|
||||
TYR * 1_000_000,
|
||||
HEZ * 1_000_000,
|
||||
PEOPLE_PARA_ID,
|
||||
// Founding citizen: Alice is the founding citizen for dev
|
||||
vec![(Sr25519Keyring::Alice.to_account_id(), default_founding_citizen_identity_hash())],
|
||||
|
||||
@@ -157,7 +157,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: alloc::borrow::Cow::Borrowed("people-pezkuwichain"),
|
||||
impl_name: alloc::borrow::Cow::Borrowed("people-pezkuwichain"),
|
||||
authoring_version: 1,
|
||||
spec_version: 1_020_009,
|
||||
spec_version: 1_020_011,
|
||||
impl_version: 0,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
transaction_version: 1,
|
||||
|
||||
@@ -312,6 +312,12 @@ parameter_types! {
|
||||
pub const MaxStudentsPerCourse: u32 = 1000;
|
||||
pub const MaxCoursesPerStudent: u32 = 50;
|
||||
pub const MaxPointsPerCourse: u32 = 1000;
|
||||
/// Pezpallet ID used to derive a keyless sovereign "admin" account for courses created
|
||||
/// via Root or Council (i.e. no natural signer/AccountId exists for those origins).
|
||||
/// This is NOT an sr25519 keyring account: nobody holds (or can hold) a private key for
|
||||
/// a `PalletId`-derived account, so it can never be used to forge `complete_course` /
|
||||
/// `archive_course` calls the way a well-known dev seed (e.g. `//Alice`) could.
|
||||
pub const PerwerdeAdminPotId: pezframe_support::PalletId = pezframe_support::PalletId(*b"pez/prwd");
|
||||
}
|
||||
|
||||
/// Admin origin for Perwerde pezpallet that supports progressive decentralization
|
||||
@@ -321,15 +327,27 @@ parameter_types! {
|
||||
/// 2. Council (1/2 çoğunluk) - Seçimler sonrası
|
||||
/// 3. Serok atayabilir - Cumhurbaşkanlığı yetkisi
|
||||
///
|
||||
/// Bu origin AccountId döndürür (kurs sahibi olarak kullanılır)
|
||||
/// Bu origin AccountId döndürür (kurs sahibi olarak kullanılır).
|
||||
///
|
||||
/// SECURITY: Root and Council origins carry no real signer/AccountId of their own. This
|
||||
/// origin previously stood in with the well-known dev keypair `//Alice`, whose private key
|
||||
/// is public knowledge — anyone could derive it and then sign `complete_course` as the
|
||||
/// "owner" of any Root/Council-created course, forging arbitrary trust-affecting course
|
||||
/// completions. It now resolves to a `PalletId`-derived sovereign account instead, which has
|
||||
/// no private key at all, so it can authenticate privileged *creation/archival* (which also
|
||||
/// goes through `AdminOrigin`) but can never be used to sign a `complete_course` extrinsic.
|
||||
/// Only Serok-originated courses (which have a genuine on-chain account) can currently be
|
||||
/// completed via the owner-signature path; enabling completions for Root/Council-created
|
||||
/// courses requires a follow-up that lets the privileged caller nominate a real owner
|
||||
/// account explicitly (e.g. an added `owner` parameter on `create_course`).
|
||||
pub struct PerwerdeAdminOrigin;
|
||||
impl pezframe_support::traits::EnsureOrigin<RuntimeOrigin> for PerwerdeAdminOrigin {
|
||||
type Success = AccountId;
|
||||
fn try_origin(o: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
|
||||
// 1. Root origin kontrolü
|
||||
if let Ok(_) = pezframe_system::ensure_root(o.clone()) {
|
||||
// Root için varsayılan admin hesabı
|
||||
return Ok(pezsp_keyring::Sr25519Keyring::Alice.to_account_id());
|
||||
// Root için keyless (private key'i olmayan) sovereign hesap
|
||||
return Ok(PerwerdeAdminPotId::get().into_account_truncating());
|
||||
}
|
||||
|
||||
// 2. Council kontrolü (1/2'den fazla oy)
|
||||
@@ -340,8 +358,8 @@ impl pezframe_support::traits::EnsureOrigin<RuntimeOrigin> for PerwerdeAdminOrig
|
||||
2,
|
||||
>::try_origin(o.clone())
|
||||
{
|
||||
// Komisyon için varsayılan admin hesabı
|
||||
return Ok(pezsp_keyring::Sr25519Keyring::Alice.to_account_id());
|
||||
// Komisyon için keyless (private key'i olmayan) sovereign hesap
|
||||
return Ok(PerwerdeAdminPotId::get().into_account_truncating());
|
||||
}
|
||||
|
||||
// 3. Serok (Cumhurbaşkanı) kontrolü
|
||||
@@ -455,6 +473,13 @@ impl pezpallet_tiki::Config for Runtime {
|
||||
// Kademeli yetki devri: Root → Teknik Komisyon
|
||||
// NFT/Rol yönetimi için Teknik Komisyon yetkili
|
||||
type AdminOrigin = crate::RootOrTechnicalCommittee;
|
||||
// Elected/Earned roles (Serok, SerokiMeclise, Parlementer, Axa, ...) are meant to
|
||||
// carry evidence of a real election/exam, not just committee say-so. Until a
|
||||
// dedicated voting/exam pezpallet exists to escalate to Root on their behalf, require
|
||||
// full Root here — deliberately stricter than the Technical Committee threshold used
|
||||
// for ordinary admin appointment via `AdminOrigin`.
|
||||
type ElectedRoleOrigin = EnsureRoot<AccountId>;
|
||||
type EarnedRoleOrigin = EnsureRoot<AccountId>;
|
||||
type WeightInfo = pezpallet_tiki::weights::BizinikiwiWeight<Runtime>;
|
||||
type TikiCollectionId = TikiCollectionId;
|
||||
type MaxTikisPerUser = MaxTikisPerUser;
|
||||
@@ -478,6 +503,32 @@ impl pezpallet_staking_score::NoterCheck<AccountId> for TikiNoterChecker {
|
||||
fn is_noter(who: &AccountId) -> bool {
|
||||
pezpallet_tiki::Pezpallet::<Runtime>::has_tiki(who, &pezpallet_tiki::Tiki::Noter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn make_noter(who: &AccountId) {
|
||||
pezpallet_tiki::UserTikis::<Runtime>::mutate(who, |tikis| {
|
||||
let _ = tikis.try_push(pezpallet_tiki::Tiki::Noter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
// Real-world analogy: a notary's bond/insurance. An account must hold the
|
||||
// Noter tiki *and* post this bond via `register_as_noter` before its
|
||||
// submissions are accepted. Slashable by `RootOrDiwanOrTechnical` if a
|
||||
// disputed submission is confirmed fraudulent. The tiki role itself
|
||||
// supports any number of independently registered accounts — this is not
|
||||
// a single hardcoded noter, the same way a state can authorize any number
|
||||
// of notaries.
|
||||
pub const NoterBondAmount: Balance = 50_000 * UNITS;
|
||||
// Real-world analogy: a notarized document's recording/contestability
|
||||
// period — a noter-signed submission only takes effect after this many
|
||||
// blocks unchallenged. Root/XCM-Transact submissions (chain-authenticated,
|
||||
// not a personal key) are exempt. One real hour, using this runtime's
|
||||
// actual `HOURS` constant (`testnet_teyrchains_constants::pezkuwichain`,
|
||||
// derived from the real 6s slot duration this runtime is configured
|
||||
// with) — matches staking-score's own internal `HOUR_IN_BLOCKS`.
|
||||
pub const StakingNoterDisputeWindow: BlockNumber = HOURS;
|
||||
}
|
||||
|
||||
impl pezpallet_staking_score::Config for Runtime {
|
||||
@@ -485,6 +536,16 @@ impl pezpallet_staking_score::Config for Runtime {
|
||||
type Balance = Balance;
|
||||
type OnStakingUpdate = Trust;
|
||||
type NoterChecker = TikiNoterChecker;
|
||||
type Currency = Balances;
|
||||
type NoterBondAmount = NoterBondAmount;
|
||||
type DisputeWindow = StakingNoterDisputeWindow;
|
||||
// Lightweight: any single Council member can freeze a suspicious pending
|
||||
// submission for review — mirrors the existing VetoOrigin pattern below.
|
||||
type DisputeOrigin = pezpallet_collective::EnsureMember<AccountId, CouncilCollective>;
|
||||
// Deliberately stronger: slashing a noter's bond needs an actual
|
||||
// governance decision, not one member's word.
|
||||
type SlashOrigin = crate::RootOrDiwanOrTechnical;
|
||||
type SlashDestination = RelayTreasuryAccount;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -779,7 +840,12 @@ impl pezpallet_democracy::Config for Runtime {
|
||||
type CancellationOrigin = EnsureRoot<AccountId>;
|
||||
type BlacklistOrigin = EnsureRoot<AccountId>;
|
||||
type CancelProposalOrigin = EnsureRoot<AccountId>;
|
||||
type VetoOrigin = pezframe_system::EnsureSigned<AccountId>;
|
||||
// VetoOrigin must be a small privileged body, not any signed account (upstream contract:
|
||||
// see bizinikiwi/bin/node/runtime's `EnsureMember<AccountId, TechnicalCollective>`). This
|
||||
// runtime has no separate technical collective, so require membership in the Council
|
||||
// instead — any single Council member may veto/blacklist an external proposal, but an
|
||||
// arbitrary unprivileged account can no longer do so.
|
||||
type VetoOrigin = pezpallet_collective::EnsureMember<AccountId, CouncilCollective>;
|
||||
type Slash = ();
|
||||
type Scheduler = Scheduler;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
@@ -918,8 +984,6 @@ parameter_types! {
|
||||
pub const PezAssetId: u32 = 1;
|
||||
/// Incentive Pot Pezpallet ID
|
||||
pub const IncentivePotId: pezframe_support::PalletId = pezframe_support::PalletId(*b"pez/incv");
|
||||
/// Clawback recipient (QaziMuhammed account - placeholder)
|
||||
pub ClawbackRecipient: AccountId = pezsp_keyring::Sr25519Keyring::Bob.to_account_id();
|
||||
}
|
||||
|
||||
/// Trust score source for PEZ Rewards
|
||||
@@ -936,7 +1000,11 @@ impl pezpallet_pez_rewards::Config for Runtime {
|
||||
type WeightInfo = pezpallet_pez_rewards::weights::BizinikiwiWeight<Runtime>;
|
||||
type TrustScoreSource = PezRewardsTrustScoreSource;
|
||||
type IncentivePotId = IncentivePotId;
|
||||
type ClawbackRecipient = ClawbackRecipient;
|
||||
// Clawback recipient: relay chain Treasury pallet sovereign account (governance-controlled,
|
||||
// no private key), reusing the same `RelayTreasuryAccount` already used above for slashed
|
||||
// identity deposits. This is NOT a placeholder dev key: an sr25519 keyring account (e.g.
|
||||
// Bob) must never be used here since its private key is publicly known.
|
||||
type ClawbackRecipient = RelayTreasuryAccount;
|
||||
// Kademeli yetki devri: Root → Hazine Komisyonu
|
||||
// PEZ ödül dağıtımı için Hazine Komisyonu yetkili
|
||||
type ForceOrigin = crate::RootOrTreasuryCommittee;
|
||||
|
||||
@@ -61,7 +61,7 @@ parameter_types! {
|
||||
pub const MaxAssetsIntoHolding: u32 = 64;
|
||||
pub const GovernanceLocation: Location = Location::parent();
|
||||
pub const FellowshipLocation: Location = Location::parent();
|
||||
/// The asset ID for the asset that we use to pay for message delivery fees. Just TYR.
|
||||
/// The asset ID for the asset that we use to pay for message delivery fees. Just HEZ.
|
||||
pub FeeAssetId: AssetId = AssetId(RelayLocation::get());
|
||||
/// The base fee for the message delivery fees.
|
||||
pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3);
|
||||
@@ -225,10 +225,10 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type XcmEventEmitter = PezkuwiXcm;
|
||||
type AssetTransactor = FungibleTransactor;
|
||||
type OriginConverter = XcmOriginToTransactDispatchOrigin;
|
||||
// People chain does not recognize a reserve location for any asset. Users must teleport TYR
|
||||
// People chain does not recognize a reserve location for any asset. Users must teleport HEZ
|
||||
// where allowed (e.g. with the Relay Chain).
|
||||
type IsReserve = ();
|
||||
/// Only allow teleportation of TYR.
|
||||
/// Only allow teleportation of HEZ.
|
||||
type IsTeleporter = ConcreteAssetFromSystem<RelayLocation>;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = Barrier;
|
||||
|
||||
@@ -129,7 +129,7 @@ pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
|
||||
pub const HOURS: BlockNumber = MINUTES * 60;
|
||||
pub const DAYS: BlockNumber = HOURS * 24;
|
||||
|
||||
pub const TYR: Balance = 1_000_000_000_000;
|
||||
pub const HEZ: Balance = 1_000_000_000_000;
|
||||
pub const MILLIROC: Balance = 1_000_000_000;
|
||||
pub const MICROROC: Balance = 1_000_000;
|
||||
|
||||
@@ -426,8 +426,8 @@ pub type XcmOriginToTransactDispatchOrigin = (
|
||||
parameter_types! {
|
||||
// One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate.
|
||||
pub UnitWeightCost: Weight = Weight::from_parts(1_000_000_000, 64 * 1024);
|
||||
// One TYR buys 1 second of weight.
|
||||
pub const WeightPrice: (Location, u128) = (Location::parent(), TYR);
|
||||
// One HEZ buys 1 second of weight.
|
||||
pub const WeightPrice: (Location, u128) = (Location::parent(), HEZ);
|
||||
pub const MaxInstructions: u32 = 100;
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = XcmOriginToTransactDispatchOrigin;
|
||||
type IsReserve = Reserves;
|
||||
type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of TYR
|
||||
type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of HEZ
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = Barrier;
|
||||
type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
|
||||
@@ -578,11 +578,11 @@ impl pezcumulus_ping::Config for Runtime {
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const AssetDeposit: Balance = TYR;
|
||||
pub const AssetAccountDeposit: Balance = TYR;
|
||||
pub const AssetDeposit: Balance = HEZ;
|
||||
pub const AssetAccountDeposit: Balance = HEZ;
|
||||
pub const ApprovalDeposit: Balance = 100 * MILLIROC;
|
||||
pub const AssetsStringLimit: u32 = 50;
|
||||
pub const MetadataDepositBase: Balance = TYR;
|
||||
pub const MetadataDepositBase: Balance = HEZ;
|
||||
pub const MetadataDepositPerByte: Balance = 10 * MILLIROC;
|
||||
pub const UnitBody: BodyId = BodyId::Unit;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user