From 9efe15d9b711485a94a81c86cbfa920f5d311521 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Thu, 24 Nov 2022 17:45:25 +0000 Subject: [PATCH] Add vesting e2e test and helpers --- --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -2146,6 +2146,15 @@ } /** + * Get latest relay block + * @returns {number} relay block + */ + async getRelayBlockNumber(): Promise { + const blockNumber = (await this.helper.callRpc('api.query.parachainSystem.validationData')).toJSON().relayParentNumber; + return BigInt(blockNumber); + } + + /** * Get account nonce * @param address substrate address * @example getNonce("5GrwvaEF5zXb26Fz..."); @@ -2326,6 +2335,52 @@ isSuccess = isSuccess && BigInt(amount) === transfer.amount; return isSuccess; } + + /** + * Transfer tokens with the unlock period + * @param signer signers Keyring + * @param address Substrate address of recipient + * @param schedule Schedule params + * @example vestedTransfer(signer, recepient.address, 20000, 100, 10, 50 * nominal); // total amount of vested tokens will be 100 * 50 = 5000 + */ + async vestedTransfer(signer: TSigner, address: TSubstrateAccount, schedule: {startRelayBlock: bigint, periodBlocks: bigint, periodCount: bigint, perPeriod: bigint}): Promise { + const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.vestedTransfer', [address, {start: schedule.startRelayBlock, period: schedule.periodBlocks, periodCount: schedule.periodCount, perPeriod: schedule.perPeriod}]); + const event = result.result.events + .find(e => e.event.section === 'vesting' && + e.event.method === 'VestingScheduleAdded' && + e.event.data[0].toHuman() === signer.address); + if (!event) throw Error('Cannot find transfer in events'); + } + + /** + * Get schedule for recepient of vested transfer + * @param address Substrate address of recipient + * @returns + */ + async getVestingSchedules(address: TSubstrateAccount): Promise<{startRelayBlock: bigint, periodBlocks: bigint, periodCount: bigint, perPeriod: bigint}[]> { + const schedule = (await this.helper.callRpc('api.query.vesting.vestingSchedules', [address])).toJSON(); + return schedule.map((schedule: any) => { + return { + startRelayBlock: BigInt(schedule.start), + periodBlocks: BigInt(schedule.period), + periodCount: BigInt(schedule.periodCount), + perPeriod: BigInt(schedule.perPeriod), + }; + }); + } + + /** + * Claim vested tokens + * @param signer signers Keyring + */ + async claim(signer: TSigner) { + const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.claim', []); + const event = result.result.events + .find(e => e.event.section === 'vesting' && + e.event.method === 'Claimed' && + e.event.data[0].toHuman() === signer.address); + if (!event) throw Error('Cannot find claim in events'); + } } class AddressGroup extends HelperGroup { --- /dev/null +++ b/tests/src/vesting.test.ts @@ -0,0 +1,104 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {itSub, usingPlaygrounds, expect} from './util'; + +describe('Vesting', () => { + let donor: IKeyringPair; + let nominal: bigint; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + donor = await privateKey({filename: __filename}); + nominal = helper.balance.getOneTokenNominal(); + }); + }); + + itSub.only('can perform vestedTransfer and claim tokens', async ({helper}) => { + // arrange + const [sender, recepient] = await helper.arrange.createAccounts([1000n, 1n], donor); + const currentRelayBlock = await helper.chain.getRelayBlockNumber(); + const schedule1 = {startRelayBlock: currentRelayBlock + 4n, periodBlocks: 4n, periodCount: 2n, perPeriod: 50n * nominal}; + const schedule2 = {startRelayBlock: currentRelayBlock + 8n, periodBlocks: 8n, periodCount: 2n, perPeriod: 100n * nominal}; + + // act + await helper.balance.vestedTransfer(sender, recepient.address, schedule1); + await helper.balance.vestedTransfer(sender, recepient.address, schedule2); + let schedule = await helper.balance.getVestingSchedules(recepient.address); + + // check senders balance after vesting: + let balanceSender = await helper.balance.getSubstrateFull(sender.address); + expect(balanceSender.free / nominal).to.eq(699n); + expect(balanceSender.feeFrozen).to.eq(0n); + expect(balanceSender.miscFrozen).to.eq(0n); + expect(balanceSender.reserved).to.eq(0n); + + // check recepient balance after vesting: + let balanceRecepient = await helper.balance.getSubstrateFull(recepient.address); + expect(balanceRecepient.free).to.eq(301n * nominal); + expect(balanceRecepient.feeFrozen).to.eq(300n * nominal); + expect(balanceRecepient.miscFrozen).to.eq(300n * nominal); + expect(balanceRecepient.reserved).to.eq(0n); + + // Schedules list correct: + expect(schedule).to.has.length(2); + expect(schedule[0]).to.deep.eq(schedule1); + expect(schedule[1]).to.deep.eq(schedule2); + + await helper.wait.forRelayBlockNumber(currentRelayBlock + 8n); + await helper.balance.claim(recepient); + + // check recepient balance after claim (50 tokens claimed): + balanceRecepient = await helper.balance.getSubstrateFull(recepient.address); + expect(balanceRecepient.free / nominal).to.eq(300n); + expect(balanceRecepient.feeFrozen).to.eq(250n * nominal); + expect(balanceRecepient.miscFrozen).to.eq(250n * nominal); + expect(balanceRecepient.reserved).to.eq(0n); + + await helper.wait.forRelayBlockNumber(currentRelayBlock + 16n); + await helper.balance.claim(recepient); + + // check recepient balance after second claim (150 tokens claimed): + balanceRecepient = await helper.balance.getSubstrateFull(recepient.address); + expect(balanceRecepient.free / nominal).to.eq(300n); + expect(balanceRecepient.feeFrozen).to.eq(100n * nominal); + expect(balanceRecepient.miscFrozen).to.eq(100n * nominal); + expect(balanceRecepient.reserved).to.eq(0n); + + // Schedules list contain 1 vesting: + schedule = await helper.balance.getVestingSchedules(recepient.address); + expect(schedule).to.has.length(1); + expect(schedule[0]).to.deep.eq(schedule2); + + await helper.wait.forRelayBlockNumber(currentRelayBlock + 24n); + await helper.balance.claim(recepient); + + // check recepient balance after second claim (100 tokens claimed): + balanceRecepient = await helper.balance.getSubstrateFull(recepient.address); + expect(balanceRecepient.free / nominal).to.eq(300n); + expect(balanceRecepient.feeFrozen).to.eq(0n); + expect(balanceRecepient.miscFrozen).to.eq(0n); + expect(balanceRecepient.reserved).to.eq(0n); + + // check sender balance does not changed: + balanceSender = await helper.balance.getSubstrateFull(sender.address); + expect(balanceSender.free / nominal).to.eq(699n); + expect(balanceSender.feeFrozen).to.eq(0n); + expect(balanceSender.miscFrozen).to.eq(0n); + expect(balanceSender.reserved).to.eq(0n); + }); +}); -- gitstuff