mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-22 10:07:57 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// Copyright 2017-2025 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { createApi } from '@pezkuwi/test-support/api';
|
||||
import { aliceSigner } from '@pezkuwi/test-support/keyring';
|
||||
|
||||
import { multiAcceptCurator, multiApproveBounty, multiAwardBounty, multiClaimBounty, multiProposeBounty, multiProposeCurator, multiWaitForBountyFunded, multiWaitForClaim } from './lib/multiFunctions.js';
|
||||
|
||||
(async () => {
|
||||
const api = await createApi(9944);
|
||||
|
||||
const indexes = await multiProposeBounty(api, 6, aliceSigner());
|
||||
|
||||
indexes.pop();
|
||||
await multiApproveBounty(api, indexes, aliceSigner());
|
||||
|
||||
await multiWaitForBountyFunded(api, indexes);
|
||||
|
||||
indexes.pop();
|
||||
await multiProposeCurator(api, indexes, aliceSigner());
|
||||
|
||||
indexes.pop();
|
||||
await multiAcceptCurator(api, indexes, aliceSigner());
|
||||
|
||||
indexes.pop();
|
||||
await multiAwardBounty(api, indexes, aliceSigner());
|
||||
|
||||
await multiWaitForClaim(api, indexes);
|
||||
|
||||
indexes.pop();
|
||||
await multiClaimBounty(api, indexes, aliceSigner());
|
||||
|
||||
await api.disconnect();
|
||||
})().catch((err) => console.error(err));
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { DeriveBounty } from '@pezkuwi/api-derive/types';
|
||||
import type { WaitOptions } from '@pezkuwi/test-support/types';
|
||||
|
||||
import { waitFor } from '@pezkuwi/test-support/utils';
|
||||
|
||||
type bStatus = 'isFunded' | 'isActive';
|
||||
|
||||
async function getBounty (api: ApiPromise, bountyIndex: number): Promise<DeriveBounty> {
|
||||
const bounties = await api.derive.bounties.bounties();
|
||||
const bounty = bounties.find((bounty) => bounty.index.toNumber() === bountyIndex);
|
||||
|
||||
if (!bounty) {
|
||||
throw new Error('Unable to find bounty');
|
||||
}
|
||||
|
||||
return bounty;
|
||||
}
|
||||
|
||||
export async function waitForBountyState (api: ApiPromise, expectedState: bStatus, index: number, { interval = 500,
|
||||
timeout = 10000 } = {}): Promise<boolean> {
|
||||
return waitFor(async () => {
|
||||
const bounty = await getBounty(api, index);
|
||||
|
||||
return bounty.bounty.status[expectedState];
|
||||
}, { interval, timeout });
|
||||
}
|
||||
|
||||
export async function waitForClaim (api: ApiPromise, index: number, { interval = 500, timeout = 10000 }: WaitOptions): Promise<boolean> {
|
||||
return waitFor(async () => {
|
||||
const bounty = await getBounty(api, index);
|
||||
const unlockAt = bounty.bounty.status.asPendingPayout.unlockAt;
|
||||
|
||||
const bestNumber = await api.derive.chain.bestNumber();
|
||||
|
||||
return unlockAt.lt(bestNumber);
|
||||
}, { interval, timeout });
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2017-2025 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { KeyringPair } from '@pezkuwi/keyring/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import { execute } from '@pezkuwi/test-support/transaction';
|
||||
|
||||
import { acceptMotion, fillTreasury, getMotion, proposeMotion } from './helpers.js';
|
||||
|
||||
export async function acceptCurator (api: ApiPromise, id: number, signer: KeyringPair): Promise<void> {
|
||||
await execute(api.tx.bounties.acceptCurator(id), signer);
|
||||
}
|
||||
|
||||
export async function awardBounty (api: ApiPromise, index: number, signer: KeyringPair): Promise<void> {
|
||||
await execute(api.tx.bounties.awardBounty(index, signer.address), signer);
|
||||
}
|
||||
|
||||
export async function claimBounty (api: ApiPromise, index: number, signer: KeyringPair): Promise<void> {
|
||||
await execute(api.tx.bounties.claimBounty(index), signer);
|
||||
}
|
||||
|
||||
export async function proposeBounty (api: ApiPromise, value: BN, title: string, signer: KeyringPair): Promise<number> {
|
||||
await execute(api.tx.bounties.proposeBounty(value, title), signer);
|
||||
const index = await api.query.bounties.bountyCount();
|
||||
|
||||
return index.toNumber() - 1;
|
||||
}
|
||||
|
||||
export async function proposeCurator (api: ApiPromise, index: number, signer: KeyringPair): Promise<void> {
|
||||
await proposeMotion(api, api.tx.bounties.proposeCurator(index, signer.address, 10), signer);
|
||||
const bountyProposal = await getMotion(api, index);
|
||||
|
||||
await acceptMotion(api, bountyProposal.hash, bountyProposal.votes?.index.toNumber() ?? 0);
|
||||
}
|
||||
|
||||
export async function approveBounty (api: ApiPromise, index: number, signer: KeyringPair): Promise<void> {
|
||||
await proposeMotion(api, api.tx.bounties.approveBounty(index), signer);
|
||||
|
||||
const bountyProposal = await getMotion(api, index);
|
||||
|
||||
await acceptMotion(api, bountyProposal.hash, bountyProposal.votes?.index.toNumber() ?? 0);
|
||||
await fillTreasury(api, signer);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
export const TREASURY_ADDRESS = '13UVJyLnbVp9RBZYFwFGyDvVd1y27Tt8tkntv6Q7JVPhFsTB';
|
||||
export const FUNDING_TIME = 150000;
|
||||
export const PAYOUT_TIME = 150000;
|
||||
export const WEIGHT_BOUND = new BN('10000000000');
|
||||
export const LENGTH_BOUND = 100000;
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2017-2025 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||
import type { DeriveCollectiveProposal } from '@pezkuwi/api-derive/types';
|
||||
import type { KeyringPair } from '@pezkuwi/keyring/types';
|
||||
import type { Hash } from '@pezkuwi/types/interfaces';
|
||||
|
||||
import { charlieSigner, daveSigner, eveSigner, ferdieSigner } from '@pezkuwi/test-support/keyring';
|
||||
import { execute } from '@pezkuwi/test-support/transaction';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { LENGTH_BOUND, TREASURY_ADDRESS, WEIGHT_BOUND } from './constants.js';
|
||||
|
||||
export async function acceptMotion (api: ApiPromise, hash: Hash, index: number): Promise<void> {
|
||||
const charlieVote = execute(api.tx.council.vote(hash, index, true), charlieSigner());
|
||||
const daveVote = execute(api.tx.council.vote(hash, index, true), daveSigner());
|
||||
const eveVote = execute(api.tx.council.vote(hash, index, true), eveSigner());
|
||||
const ferdieVote = execute(api.tx.council.vote(hash, index, true), ferdieSigner());
|
||||
|
||||
await Promise.all([charlieVote, daveVote, eveVote, ferdieVote]);
|
||||
await execute(api.tx.council.close(hash, index, { refTime: WEIGHT_BOUND }, LENGTH_BOUND), charlieSigner());
|
||||
}
|
||||
|
||||
export async function fillTreasury (api: ApiPromise, signer: KeyringPair): Promise<void> {
|
||||
await execute((api.tx.balances.transferAllowDeath || api.tx.balances.transfer)(TREASURY_ADDRESS, new BN('50000000000000000')), signer);
|
||||
}
|
||||
|
||||
export async function proposeMotion (api: ApiPromise, submittableExtrinsic: SubmittableExtrinsic<'promise'>, signer: KeyringPair): Promise<void> {
|
||||
await execute(api.tx.council.propose(4, submittableExtrinsic, LENGTH_BOUND), signer);
|
||||
}
|
||||
|
||||
export async function getMotion (api: ApiPromise, index: number): Promise<DeriveCollectiveProposal> {
|
||||
const bounties = await api.derive.bounties.bounties();
|
||||
const bountyProposals = bounties.find((bounty) => (bounty.index.toNumber() === index))?.proposals;
|
||||
|
||||
if (!bountyProposals) {
|
||||
throw new Error('Unable to find proposal');
|
||||
}
|
||||
|
||||
return bountyProposals[0];
|
||||
}
|
||||
|
||||
export async function multiProposeMotion (api: ApiPromise, submittableExtrinsicArray: SubmittableExtrinsic<'promise'>[], signer: KeyringPair): Promise<void> {
|
||||
const proposeExtrinsicArray =
|
||||
submittableExtrinsicArray.map((extrinsic) =>
|
||||
api.tx.council.propose(4, extrinsic, LENGTH_BOUND));
|
||||
|
||||
await execute(api.tx.utility.batch(proposeExtrinsicArray), signer);
|
||||
}
|
||||
|
||||
export async function multiGetMotion (api: ApiPromise, indexes: number[]): Promise<DeriveCollectiveProposal[]> {
|
||||
const bounties = await api.derive.bounties.bounties();
|
||||
const bountyProposals =
|
||||
indexes.map((index) =>
|
||||
bounties.find((bounty) =>
|
||||
(bounty.index.toNumber() === index)
|
||||
)?.proposals
|
||||
);
|
||||
|
||||
return bountyProposals
|
||||
.map((arr) => arr?.[0])
|
||||
.filter((arr): arr is DeriveCollectiveProposal => !!arr);
|
||||
}
|
||||
|
||||
async function multiVoteAye (acceptMotionSigners: KeyringPair[], api: ApiPromise, indexes: number[], hashes: Hash[]) {
|
||||
await Promise.all(
|
||||
acceptMotionSigners.map((signer) =>
|
||||
execute(
|
||||
api.tx.utility.batch(
|
||||
indexes.map((bountyIndex, i) => api.tx.council.vote(hashes[i], bountyIndex, true))
|
||||
),
|
||||
signer
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function multiCloseMotion (api: ApiPromise, indexes: number[], hashes: Hash[]) {
|
||||
await execute(
|
||||
api.tx.utility.batch(
|
||||
indexes.map((bountyIndex, i) => api.tx.council.close(hashes[i], bountyIndex, { refTime: WEIGHT_BOUND }, LENGTH_BOUND))),
|
||||
charlieSigner()
|
||||
);
|
||||
}
|
||||
|
||||
export async function multiAcceptMotion (api: ApiPromise, hashes: Hash[], indexes: number[]): Promise<void> {
|
||||
const acceptMotionSigners = [charlieSigner(), daveSigner(), eveSigner(), ferdieSigner()];
|
||||
|
||||
await multiVoteAye(acceptMotionSigners, api, indexes, hashes);
|
||||
await multiCloseMotion(api, indexes, hashes);
|
||||
}
|
||||
|
||||
export function extractIndexesFromProposals (bountyProposals: DeriveCollectiveProposal[]): number[] {
|
||||
return bountyProposals.map((proposal) => proposal.votes?.index.toNumber() ?? 0);
|
||||
}
|
||||
|
||||
export function extractHashesFromProposals (bountyProposals: DeriveCollectiveProposal[]): Hash[] {
|
||||
return bountyProposals.map((proposal) => proposal.hash);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2017-2025 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { KeyringPair } from '@pezkuwi/keyring/types';
|
||||
|
||||
import { execute } from '@pezkuwi/test-support/transaction';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { waitForBountyState, waitForClaim } from './bountyWaitFunctions.js';
|
||||
import { FUNDING_TIME, PAYOUT_TIME } from './constants.js';
|
||||
import { extractHashesFromProposals, extractIndexesFromProposals, fillTreasury, multiAcceptMotion, multiGetMotion, multiProposeMotion } from './helpers.js';
|
||||
|
||||
export async function multiProposeBounty (api: ApiPromise, numberOfBounties: number, signer: KeyringPair): Promise<number[]> {
|
||||
const initialIndex = await api.query.bounties.bountyCount();
|
||||
|
||||
const arr = Array.from({ length: numberOfBounties }, (_, i) => api.tx.bounties.proposeBounty(new BN(500_000_000_000_000), `new bounty no ${i}`));
|
||||
|
||||
await execute(
|
||||
api.tx.utility.batch(arr),
|
||||
signer
|
||||
);
|
||||
const endIndex = await api.query.bounties.bountyCount();
|
||||
|
||||
if ((endIndex.sub(initialIndex)).toNumber() !== numberOfBounties) {
|
||||
throw new Error('Multi Propose Failed');
|
||||
}
|
||||
|
||||
return Array.from({ length: numberOfBounties }, (_, i) => i + initialIndex.toNumber());
|
||||
}
|
||||
|
||||
export async function multiApproveBounty (api: ApiPromise, bountyIndexes: number[], signer: KeyringPair): Promise<void> {
|
||||
const extrinsicArray = bountyIndexes.map((index) => api.tx.bounties.approveBounty(index));
|
||||
|
||||
await multiProposeMotion(api, extrinsicArray, signer);
|
||||
|
||||
const bountyProposals = await multiGetMotion(api, bountyIndexes);
|
||||
|
||||
await fillTreasury(api, signer);
|
||||
await multiAcceptMotion(api, extractHashesFromProposals(bountyProposals), extractIndexesFromProposals(bountyProposals));
|
||||
}
|
||||
|
||||
export async function multiWaitForBountyFunded (api: ApiPromise, bountyIndexes: number[]): Promise<void> {
|
||||
const waitFunctions = bountyIndexes.map((bountyIndex) =>
|
||||
waitForBountyState(api, 'isFunded', bountyIndex, { interval: 2000, timeout: FUNDING_TIME }));
|
||||
|
||||
await Promise.all(waitFunctions);
|
||||
}
|
||||
|
||||
export async function multiProposeCurator (api: ApiPromise, bountyIndexes: number[], signer: KeyringPair): Promise<void> {
|
||||
const extrinsicArray = bountyIndexes.map((index) => api.tx.bounties.proposeCurator(index, signer.address, 10));
|
||||
|
||||
await multiProposeMotion(api, extrinsicArray, signer);
|
||||
|
||||
const bountyProposals = await multiGetMotion(api, bountyIndexes);
|
||||
|
||||
await multiAcceptMotion(api, extractHashesFromProposals(bountyProposals), extractIndexesFromProposals(bountyProposals));
|
||||
}
|
||||
|
||||
export async function multiAcceptCurator (api: ApiPromise, bountyIndexes: number[], signer: KeyringPair): Promise<void> {
|
||||
await execute(
|
||||
api.tx.utility.batch(bountyIndexes.map((bountyIndex) => api.tx.bounties.acceptCurator(bountyIndex))),
|
||||
signer
|
||||
);
|
||||
}
|
||||
|
||||
export async function multiAwardBounty (api: ApiPromise, bountyIndexes: number[], signer: KeyringPair): Promise<void> {
|
||||
await execute(
|
||||
api.tx.utility.batch(bountyIndexes.map((bountyIndex) => api.tx.bounties.awardBounty(bountyIndex, signer.address))),
|
||||
signer
|
||||
);
|
||||
}
|
||||
|
||||
export async function multiWaitForClaim (api: ApiPromise, bountyIndexes: number[]): Promise<void> {
|
||||
for (const index of bountyIndexes) {
|
||||
await waitForClaim(api, index, { interval: 2000, timeout: PAYOUT_TIME });
|
||||
}
|
||||
}
|
||||
|
||||
export async function multiClaimBounty (api: ApiPromise, bountyIndexes: number[], signer: KeyringPair): Promise<void> {
|
||||
await execute(
|
||||
api.tx.utility.batch(bountyIndexes.map((bountyIndex) => api.tx.bounties.claimBounty(bountyIndex))),
|
||||
signer
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
## Scripts for bounty testing
|
||||
|
||||
The scripts are prepared to run on a local, development version of bizinikiwi with following changes:
|
||||
- `bin/node/runtime/src/lib.rs`
|
||||
```
|
||||
pub const SpendPeriod: BlockNumber = 1 * MINUTES;
|
||||
pub const BountyDepositPayoutDelay: BlockNumber = 1 * MINUTES
|
||||
```
|
||||
|
||||
To run a script enter the `packages/test-support` directory and run:
|
||||
```
|
||||
ts-node scripts/<script-name>
|
||||
```
|
||||
|
||||
Available scripts:
|
||||
- `createBounties` - creates a list of bounties,
|
||||
one in each status ( Proposed, Funded, Curator Proposed, Active, Pending Payout, Closed )
|
||||
|
||||
Reference in New Issue
Block a user