mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-21 23:47:56 +00:00
212 lines
7.2 KiB
JavaScript
212 lines
7.2 KiB
JavaScript
/**
|
|
* @file: staking-score.live.test.js
|
|
* @description: Live integration tests for the StakingScore pallet (v1020007+).
|
|
*
|
|
* Tests the noter-based staking score system:
|
|
* - start_score_tracking() — no stake requirement, user opt-in
|
|
* - receive_staking_details() — noter/root submits cached staking data
|
|
* - CachedStakingDetails storage — dual-source (RelayChain, AssetHub)
|
|
* - Zero stake cleanup
|
|
*
|
|
* @preconditions:
|
|
* 1. A local Pezkuwi dev node must be running at WS_ENDPOINT.
|
|
* 2. The node must have stakingScore pallet (spec >= 1020007).
|
|
* 3. Sudo account (//Alice) must be available for root-origin calls.
|
|
*/
|
|
|
|
import { ApiPromise, WsProvider, Keyring } from '@pezkuwi/api';
|
|
import { BN } from '@pezkuwi/util';
|
|
import { jest } from '@jest/globals';
|
|
|
|
// ========================================
|
|
// TEST CONFIGURATION
|
|
// ========================================
|
|
|
|
const WS_ENDPOINT = process.env.WS_ENDPOINT || 'ws://127.0.0.1:8082';
|
|
jest.setTimeout(120000);
|
|
|
|
const UNITS = new BN('1000000000000'); // 10^12
|
|
|
|
// ========================================
|
|
// HELPERS
|
|
// ========================================
|
|
|
|
let api;
|
|
let keyring;
|
|
let sudo;
|
|
let user1;
|
|
|
|
const sendAndFinalize = (tx, signer) => {
|
|
return new Promise((resolve, reject) => {
|
|
tx.signAndSend(signer, ({ status, dispatchError }) => {
|
|
if (status.isFinalized) {
|
|
if (dispatchError) {
|
|
if (dispatchError.isModule) {
|
|
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
|
reject(new Error(`${decoded.section}.${decoded.name}`));
|
|
} else {
|
|
reject(new Error(dispatchError.toString()));
|
|
}
|
|
} else {
|
|
resolve();
|
|
}
|
|
}
|
|
}).catch(reject);
|
|
});
|
|
};
|
|
|
|
const sendSudoAndFinalize = (call) => {
|
|
const sudoTx = api.tx.sudo.sudo(call);
|
|
return sendAndFinalize(sudoTx, sudo);
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
const wsProvider = new WsProvider(WS_ENDPOINT);
|
|
api = await ApiPromise.create({ provider: wsProvider });
|
|
keyring = new Keyring({ type: 'sr25519' });
|
|
|
|
sudo = keyring.addFromUri('//Alice');
|
|
user1 = keyring.addFromUri(`//StakingScoreUser${Date.now()}`);
|
|
|
|
// Fund test account
|
|
const transferTx = api.tx.balances.transferKeepAlive(user1.address, UNITS.mul(new BN(100)));
|
|
await sendAndFinalize(transferTx, sudo);
|
|
|
|
console.log('Setup complete. User1:', user1.address);
|
|
}, 60000);
|
|
|
|
afterAll(async () => {
|
|
if (api) await api.disconnect();
|
|
});
|
|
|
|
// ========================================
|
|
// TESTS
|
|
// ========================================
|
|
|
|
describe('StakingScore Pallet (v1020007+)', () => {
|
|
|
|
it('start_score_tracking() succeeds without any stake', async () => {
|
|
// User1 has no staking ledger — should still succeed (no NoStakeFound error)
|
|
const tx = api.tx.stakingScore.startScoreTracking();
|
|
await sendAndFinalize(tx, user1);
|
|
|
|
// Verify StakingStartBlock is set
|
|
const startBlock = await api.query.stakingScore.stakingStartBlock(user1.address);
|
|
expect(startBlock.isSome).toBe(true);
|
|
console.log('start_score_tracking succeeded, startBlock:', startBlock.unwrap().toNumber());
|
|
});
|
|
|
|
it('start_score_tracking() fails with TrackingAlreadyStarted', async () => {
|
|
const tx = api.tx.stakingScore.startScoreTracking();
|
|
await expect(sendAndFinalize(tx, user1))
|
|
.rejects.toThrow('stakingScore.TrackingAlreadyStarted');
|
|
});
|
|
|
|
it('receive_staking_details() fails without noter authority', async () => {
|
|
// User1 is not a noter — should fail with NotAuthorized
|
|
const tx = api.tx.stakingScore.receiveStakingDetails(
|
|
user1.address,
|
|
'RelayChain',
|
|
UNITS.mul(new BN(500)).toString(), // 500 HEZ
|
|
3, // nominations_count
|
|
0, // unlocking_chunks_count
|
|
);
|
|
await expect(sendAndFinalize(tx, user1))
|
|
.rejects.toThrow('stakingScore.NotAuthorized');
|
|
});
|
|
|
|
it('receive_staking_details() succeeds via sudo (root origin)', async () => {
|
|
const call = api.tx.stakingScore.receiveStakingDetails(
|
|
user1.address,
|
|
'RelayChain',
|
|
UNITS.mul(new BN(500)).toString(), // 500 HEZ
|
|
3, // nominations_count
|
|
1, // unlocking_chunks_count
|
|
);
|
|
await sendSudoAndFinalize(call);
|
|
|
|
// Verify CachedStakingDetails is set
|
|
const cached = await api.query.stakingScore.cachedStakingDetails(
|
|
user1.address, 'RelayChain'
|
|
);
|
|
expect(cached.isSome).toBe(true);
|
|
|
|
const details = cached.unwrap().toJSON();
|
|
const stakedAmount = BigInt(details.stakedAmount ?? details.staked_amount ?? '0');
|
|
expect(stakedAmount).toBe(UNITS.mul(new BN(500)).toBigInt());
|
|
expect(details.nominationsCount ?? details.nominations_count).toBe(3);
|
|
expect(details.unlockingChunksCount ?? details.unlocking_chunks_count).toBe(1);
|
|
|
|
console.log('CachedStakingDetails verified:', details);
|
|
});
|
|
|
|
it('receive_staking_details() supports dual-source (AssetHub)', async () => {
|
|
const call = api.tx.stakingScore.receiveStakingDetails(
|
|
user1.address,
|
|
'AssetHub',
|
|
UNITS.mul(new BN(200)).toString(), // 200 HEZ from Asset Hub pool
|
|
0,
|
|
0,
|
|
);
|
|
await sendSudoAndFinalize(call);
|
|
|
|
// Both sources should exist
|
|
const relay = await api.query.stakingScore.cachedStakingDetails(user1.address, 'RelayChain');
|
|
const assetHub = await api.query.stakingScore.cachedStakingDetails(user1.address, 'AssetHub');
|
|
expect(relay.isSome).toBe(true);
|
|
expect(assetHub.isSome).toBe(true);
|
|
|
|
console.log('Dual-source staking data verified');
|
|
});
|
|
|
|
it('zero stake cleans up CachedStakingDetails for a source', async () => {
|
|
// Send zero stake for AssetHub — should remove that entry
|
|
const call = api.tx.stakingScore.receiveStakingDetails(
|
|
user1.address,
|
|
'AssetHub',
|
|
'0', // zero stake
|
|
0,
|
|
0,
|
|
);
|
|
await sendSudoAndFinalize(call);
|
|
|
|
// AssetHub entry should be gone
|
|
const assetHub = await api.query.stakingScore.cachedStakingDetails(user1.address, 'AssetHub');
|
|
expect(assetHub.isNone || assetHub.isEmpty).toBe(true);
|
|
|
|
// RelayChain entry should still exist
|
|
const relay = await api.query.stakingScore.cachedStakingDetails(user1.address, 'RelayChain');
|
|
expect(relay.isSome).toBe(true);
|
|
|
|
// StakingStartBlock should still exist (still has relay stake)
|
|
const startBlock = await api.query.stakingScore.stakingStartBlock(user1.address);
|
|
expect(startBlock.isSome).toBe(true);
|
|
|
|
console.log('Zero stake cleanup verified (AssetHub removed, RelayChain kept)');
|
|
});
|
|
|
|
it('zero stake on all sources removes StakingStartBlock', async () => {
|
|
// Remove relay chain stake too
|
|
const call = api.tx.stakingScore.receiveStakingDetails(
|
|
user1.address,
|
|
'RelayChain',
|
|
'0',
|
|
0,
|
|
0,
|
|
);
|
|
await sendSudoAndFinalize(call);
|
|
|
|
// Both sources should be empty
|
|
const relay = await api.query.stakingScore.cachedStakingDetails(user1.address, 'RelayChain');
|
|
const assetHub = await api.query.stakingScore.cachedStakingDetails(user1.address, 'AssetHub');
|
|
expect(relay.isNone || relay.isEmpty).toBe(true);
|
|
expect(assetHub.isNone || assetHub.isEmpty).toBe(true);
|
|
|
|
// StakingStartBlock should also be cleaned up
|
|
const startBlock = await api.query.stakingScore.stakingStartBlock(user1.address);
|
|
expect(startBlock.isNone || startBlock.isEmpty).toBe(true);
|
|
|
|
console.log('Full cleanup verified (all sources + StakingStartBlock removed)');
|
|
});
|
|
});
|