Add create bounty script for easier manual testing (#4416)

This commit is contained in:
MiZiet 2021-01-14 18:02:39 +01:00 committed by GitHub
parent d307eb728e
commit 5e218f4fa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 402 additions and 6 deletions

View File

@ -10,5 +10,9 @@
"dependencies": {
"@testing-library/react": "^11.2.3",
"testcontainers": "^6.4.1"
},
"devDependencies": {
"ts-node": "^9.1.1",
"tsconfig-paths": "^3.9.0"
}
}

View File

@ -0,0 +1,48 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { createApi } from '@polkadot/test-support/api';
import { aliceSigner,
bobSigner,
charlieSigner,
daveSigner,
eveSigner,
ferdieSigner } from '@polkadot/test-support/keyring';
import { multiAcceptCurator,
multiApproveBounty,
multiAwardBounty,
multiClaimBounty,
multiProposeBounty,
multiProposeCurator,
multiWaitForBountyFunded,
multiWaitForClaim } from './lib/multiFunctions';
(async () => {
const api = await createApi(9944);
const SIGNERS = [aliceSigner(), bobSigner(), charlieSigner(), daveSigner(), eveSigner(), ferdieSigner()];
const indexes = await multiProposeBounty(api, 6);
indexes.pop();
await multiApproveBounty(api, indexes);
await multiWaitForBountyFunded(api, indexes);
indexes.pop();
await multiProposeCurator(api, indexes, SIGNERS);
indexes.pop();
await multiAcceptCurator(api, indexes, SIGNERS);
indexes.pop();
await multiAwardBounty(api, indexes, SIGNERS);
await multiWaitForClaim(api, indexes);
indexes.pop();
await multiClaimBounty(api, indexes, SIGNERS);
await api.disconnect();
})().catch((err) => console.error(err));

View File

@ -0,0 +1,34 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ApiPromise } from '@polkadot/api';
import { DeriveBounty } from '@polkadot/api-derive/types';
import { waitFor, WaitOptions } from '@polkadot/test-support/utils/waitFor';
type bStatus = 'isFunded' | 'isActive';
async function getBounty (api: ApiPromise, bountyIndex: number): Promise<DeriveBounty> {
const bounties = await api.derive.bounties.bounties();
return bounties.find((bounty) => (bounty.index.toNumber() === bountyIndex)) as DeriveBounty;
}
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 });
}

View File

@ -0,0 +1,47 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeyringPair } from '@polkadot/keyring/types';
import BN from 'bn.js';
import { ApiPromise } from '@polkadot/api';
import { execute } from '@polkadot/test-support/transaction';
import { acceptMotion, fillTreasury, getMotion, proposeMotion } from './helpers';
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);
}

View File

@ -0,0 +1,10 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import BN from 'bn.js';
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;

View File

@ -0,0 +1,42 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import BN from 'bn.js';
import { ApiPromise } from '@polkadot/api';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
import { KeyringPair } from '@polkadot/keyring/types';
import { charlieSigner,
daveSigner,
eveSigner,
ferdieSigner } from '@polkadot/test-support/keyring';
import { execute } from '@polkadot/test-support/transaction';
import { Hash } from '@polkadot/types/interfaces';
import { LENGTH_BOUND, TREASURY_ADDRESS, WEIGHT_BOUND } from './constants';
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, WEIGHT_BOUND, LENGTH_BOUND), charlieSigner());
}
export async function fillTreasury (api: ApiPromise, signer: KeyringPair): Promise<void> {
await execute(api.tx.balances.transfer(TREASURY_ADDRESS, new BN(5_000_000_000_000_000)), 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 as DeriveCollectiveProposal[];
return bountyProposals[0];
}

View File

@ -0,0 +1,68 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import BN from 'bn.js';
import { ApiPromise } from '@polkadot/api';
import { KeyringPair } from '@polkadot/keyring/types';
import { aliceSigner } from '@polkadot/test-support/keyring';
import { waitForBountyState, waitForClaim } from './bountyWaitFunctions';
import { acceptCurator, approveBounty, awardBounty, claimBounty, proposeBounty, proposeCurator } from './changeBountyStateFunctions';
import { FUNDING_TIME, PAYOUT_TIME } from './constants';
export async function multiProposeBounty (api: ApiPromise, numberOfBounties: number): Promise<number[]> {
const indexes = [];
for (let i = 0; i < numberOfBounties; i++) {
indexes.push(await proposeBounty(api, new BN(500_000_000_000_000), `new bounty no ${i}`, aliceSigner()));
}
return indexes;
}
export async function multiApproveBounty (api: ApiPromise, bountyIndexes: number[]): Promise<void> {
for (const bountyIndex of bountyIndexes) {
await approveBounty(api, bountyIndex, aliceSigner());
}
}
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[], signers: KeyringPair[]): Promise<void> {
for (let i = 0; i < bountyIndexes.length; i++) {
await proposeCurator(api, bountyIndexes[i], signers[i]);
}
}
export async function multiAcceptCurator (api: ApiPromise, bountyIndexes: number[], signers: KeyringPair[]): Promise<void> {
const acceptFunctions = bountyIndexes.map((bountyIndex, index) =>
acceptCurator(api, bountyIndex, signers[index]));
await Promise.all(acceptFunctions);
}
export async function multiAwardBounty (api: ApiPromise, bountyIndexes: number[], signers: KeyringPair[]): Promise<void> {
const awardFunctions = bountyIndexes.map((bountyIndex, index) =>
awardBounty(api, bountyIndex, signers[index]));
await Promise.all(awardFunctions);
}
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[], signers: KeyringPair[]): Promise<void> {
const awardFunctions = bountyIndexes.map((bountyIndex, index) =>
claimBounty(api, bountyIndex, signers[index]));
await Promise.all(awardFunctions);
}

View File

@ -0,0 +1,18 @@
## Scripts for bounty testing
The scripts are prepared to run on a local, development version of substrate 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 )

View File

@ -5,10 +5,10 @@ import { ApiPromise } from '@polkadot/api/promise';
import { WsProvider } from '@polkadot/rpc-provider';
import { SUBSTRATE_PORT } from '@polkadot/test-support/substrate';
export async function createApi (): Promise<ApiPromise> {
export async function createApi (port: number = SUBSTRATE_PORT): Promise<ApiPromise> {
process.env.NODE_ENV = 'test';
const provider = new WsProvider(`ws://127.0.0.1:${SUBSTRATE_PORT}`);
const provider = new WsProvider(`ws://127.0.0.1:${port}`);
const api = await ApiPromise.create({ provider });

View File

@ -9,3 +9,33 @@ export function aliceSigner (): KeyringPair {
return keyring.addFromUri('//Alice');
}
export function bobSigner (): KeyringPair {
const keyring = new Keyring({ type: 'sr25519' });
return keyring.addFromUri('//Bob');
}
export function charlieSigner (): KeyringPair {
const keyring = new Keyring({ type: 'sr25519' });
return keyring.addFromUri('//Charlie');
}
export function daveSigner (): KeyringPair {
const keyring = new Keyring({ type: 'sr25519' });
return keyring.addFromUri('//Dave');
}
export function eveSigner (): KeyringPair {
const keyring = new Keyring({ type: 'sr25519' });
return keyring.addFromUri('//Eve');
}
export function ferdieSigner (): KeyringPair {
const keyring = new Keyring({ type: 'sr25519' });
return keyring.addFromUri('//Ferdie');
}

View File

@ -1,12 +1,12 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { waitFor } from '@testing-library/react';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { KeyringPair } from '@polkadot/keyring/types';
import { EventRecord, ExtrinsicStatus } from '@polkadot/types/interfaces';
import { waitFor } from '../utils/waitFor';
export async function execute (extrinsic: SubmittableExtrinsic<'promise'>, singer: KeyringPair, logger = { info: console.log }): Promise<void> {
let currentTxDone = false;
@ -36,5 +36,5 @@ export async function execute (extrinsic: SubmittableExtrinsic<'promise'>, singe
}
await extrinsic.signAndSend(singer, sendStatusCb);
await waitFor(() => expect(currentTxDone).toBeTruthy(), { timeout: 20000 });
await waitFor(() => currentTxDone, { timeout: 20000 });
}

View File

@ -0,0 +1,27 @@
// Copyright 2017-2021 @polkadot/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
export type WaitOptions = { interval?: number, timeout?: number };
export async function waitFor (predicate: () => boolean, { interval, timeout }: WaitOptions): Promise<boolean>;
export async function waitFor (predicate: () => Promise<boolean>, { interval, timeout }: WaitOptions): Promise<boolean>;
export async function waitFor (predicate: () => Promise<boolean> | boolean, { interval = 500, timeout = 10000 } = {}): Promise<boolean> {
const asyncPredicate = () => Promise.resolve(predicate());
let elapsed = 0;
while (!(await asyncPredicate())) {
if (elapsed > timeout) {
throw Error('Timeout');
}
await sleep(interval);
elapsed += interval;
}
return true;
}
export const sleep = (ms: number):Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms));

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"target": "es2018"
},
"ts-node": {
"require": ["tsconfig-paths/register"]
}
}

View File

@ -2622,6 +2622,8 @@ __metadata:
dependencies:
"@testing-library/react": ^11.2.3
testcontainers: ^6.4.1
ts-node: ^9.1.1
tsconfig-paths: ^3.9.0
languageName: unknown
linkType: soft
@ -4388,6 +4390,13 @@ __metadata:
languageName: node
linkType: hard
"arg@npm:^4.1.0":
version: 4.1.3
resolution: "arg@npm:4.1.3"
checksum: 81b3b40b1529c4fbf75b12f7c3e6fb2dcce9e78072063babc169de9b4f40777788f3d2b04380f659ef676a756e03ccfbfe78adf4477353bda906295fa69dab89
languageName: node
linkType: hard
"argparse@npm:^1.0.7":
version: 1.0.10
resolution: "argparse@npm:1.0.10"
@ -6406,6 +6415,13 @@ __metadata:
languageName: node
linkType: hard
"create-require@npm:^1.1.0":
version: 1.1.1
resolution: "create-require@npm:1.1.1"
checksum: babd307893abfb26d77ae11cb9d6b6cfa6d18c9cee435cf70b5a3fb44aa8d90c9ec26ea89cbb16e0a94b8d34f5fcaee164b90ed526cdd3158955673ab9652d01
languageName: node
linkType: hard
"cross-spawn@npm:^6.0.0":
version: 6.0.5
resolution: "cross-spawn@npm:6.0.5"
@ -7062,6 +7078,13 @@ __metadata:
languageName: node
linkType: hard
"diff@npm:^4.0.1":
version: 4.0.2
resolution: "diff@npm:4.0.2"
checksum: 81b5cd7ddde6f0ba2a532d434cfdca365aedd6cc62bb133e851e66e071d40382a30924a07c1034bd3d5a2e332146f64514b73c06fe2ebc0490a67f0c98da79fb
languageName: node
linkType: hard
"diffie-hellman@npm:^5.0.0":
version: 5.0.3
resolution: "diffie-hellman@npm:5.0.3"
@ -12224,6 +12247,13 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"make-error@npm:^1.1.1":
version: 1.3.6
resolution: "make-error@npm:1.3.6"
checksum: 2c780bab8409b865e8ee86697c599a2bf2765ec64d21eb67ccda27050e039f983feacad05a0d43aba3c966ea03d305d2612e94fec45474bcbc61181f57c5bb88
languageName: node
linkType: hard
"makeerror@npm:1.0.x":
version: 1.0.11
resolution: "makeerror@npm:1.0.11"
@ -16473,7 +16503,7 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.19, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.19":
"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.17, source-map-support@npm:^0.5.19, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.19":
version: 0.5.19
resolution: "source-map-support@npm:0.5.19"
dependencies:
@ -17671,6 +17701,27 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"ts-node@npm:^9.1.1":
version: 9.1.1
resolution: "ts-node@npm:9.1.1"
dependencies:
arg: ^4.1.0
create-require: ^1.1.0
diff: ^4.0.1
make-error: ^1.1.1
source-map-support: ^0.5.17
yn: 3.1.1
peerDependencies:
typescript: ">=2.7"
bin:
ts-node: dist/bin.js
ts-node-script: dist/bin-script.js
ts-node-transpile-only: dist/bin-transpile.js
ts-script: dist/bin-script-deprecated.js
checksum: a90db4a342872cd0e7a80babdfcb15d2f7c06e700d735003098f7cc79db575c3380580c58a19ae0d0eaab553af083651d4237060c92170e6f8ac4e64693113ea
languageName: node
linkType: hard
"tsconfig-paths@npm:^3.9.0":
version: 3.9.0
resolution: "tsconfig-paths@npm:3.9.0"
@ -19176,6 +19227,13 @@ fsevents@~2.1.2:
languageName: node
linkType: hard
"yn@npm:3.1.1":
version: 3.1.1
resolution: "yn@npm:3.1.1"
checksum: bff63b80568d80c711670935427494dde47cdf97e8b04196b140ce0af519c81c5ee857eddad0caa8b422dd65aea0157bbfaacbb1546bebba623f0f383d5d9ae5
languageName: node
linkType: hard
"yocto-queue@npm:^0.1.0":
version: 0.1.0
resolution: "yocto-queue@npm:0.1.0"