git.delta.rocks / unique-network / refs/commits / 797651782deb

difftreelog

Add new helpers for new unstaking api

Max Andreev2023-02-13parent: #9935321.patch.diff
in: master

3 files changed

modifiedtests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth
--- a/tests/src/sub/appPromotion/appPromotion.test.ts
+++ b/tests/src/sub/appPromotion/appPromotion.test.ts
@@ -86,7 +86,7 @@
       await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');
 
       // After unstake can stake again
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
       await helper.staking.stake(staker, 100n * nominal);
       expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);
     });
@@ -109,7 +109,7 @@
       expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(1199n * nominal);
 
       // staker can unstake
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
       expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.eq(1199n * nominal);
       const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
       await helper.wait.forParachainBlockNumber(pendingUnstake.block);
@@ -152,7 +152,7 @@
       const [staker, recepient] = [accounts.pop()!, accounts.pop()!];
       const totalStakedBefore = await helper.staking.getTotalStaked();
       await helper.staking.stake(staker, 900n * nominal);
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
 
       // Right after unstake tokens are still locked
       expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'appstake', amount: 900n * nominal, reasons: 'All'}]);
@@ -167,7 +167,7 @@
     itSub('should unlock balance after unlocking period ends and remove it from "pendingUnstake"', async ({helper}) => {
       const staker = accounts.pop()!;
       await helper.staking.stake(staker, 100n * nominal);
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
       const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
 
       // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n
@@ -197,7 +197,7 @@
       expect(stakes[2].amount).to.equal(300n * nominal);
 
       // Can unstake multiple stakes
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
       pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
       totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});
       stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
@@ -216,7 +216,7 @@
       const staker = accounts.pop()!;
 
       // unstake has no effect if no stakes at all
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
       expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);
       expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper
 
@@ -224,8 +224,8 @@
 
       // can't unstake if there are only pendingUnstakes
       await helper.staking.stake(staker, 100n * nominal);
-      await helper.staking.unstake(staker);
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
+      await helper.staking.unstakeAll(staker);
 
       expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);
       expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);
@@ -234,9 +234,9 @@
     itSub('should keep different unlocking block for each unlocking stake', async ({helper}) => {
       const staker = accounts.pop()!;
       await helper.staking.stake(staker, 100n * nominal);
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
       await helper.staking.stake(staker, 120n * nominal);
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
 
       const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
       expect(unstakingPerBlock).has.length(2);
@@ -248,7 +248,7 @@
       const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];
 
       await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
-      await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));
+      await Promise.all(stakers.map(staker => helper.staking.unstakeAll(staker)));
 
       await Promise.all(stakers.map(async (staker) => {
         expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);
@@ -261,7 +261,7 @@
         const stakers = await helper.arrange.createAccounts([200n,200n,200n,200n,200n,200n,200n,200n,200n,200n], donor);
 
         await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
-        const unstakingResults = await Promise.allSettled(stakers.map(staker => helper.staking.unstake(staker)));
+        const unstakingResults = await Promise.allSettled(stakers.map(staker => helper.staking.unstakeAll(staker)));
 
         const successfulUnstakes = unstakingResults.filter(result => result.status === 'fulfilled');
         expect(successfulUnstakes).to.have.length(3);
@@ -597,7 +597,7 @@
       const totalStakedAfter = await helper.staking.getTotalStaked();
       expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);
       // staker can unstake
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
       expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal));
     });
 
@@ -651,7 +651,7 @@
       await helper.staking.stake(staker, 100n * nominal);
       const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
       await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);
-      await helper.staking.unstake(staker);
+      await helper.staking.unstakeAll(staker);
 
       // so he did not receive any rewards
       const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
before · tests/src/util/playgrounds/types.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {IKeyringPair} from '@polkadot/types/types';56export interface IEvent {7  section: string;8  method: string;9  index: [number, number] | string;10  data: any[];11  phase: {applyExtrinsic: number} | 'Initialization',12}1314export interface ITransactionResult {15  status: 'Fail' | 'Success';16  result: {17      dispatchError: any,18      events: {19        phase: any, // {ApplyExtrinsic: number} | 'Initialization',20        event: IEvent;21      }[];22  },23  moduleError?: string;24}2526export interface ISubscribeBlockEventsData {27  number: number;28  hash: string;29  timestamp: number;30  events: IEvent[];31}3233export interface ILogger {34  log: (msg: any, level?: string) => void;35  level: {36    ERROR: 'ERROR';37    WARNING: 'WARNING';38    INFO: 'INFO';39    [key: string]: string;40  }41}4243export interface IUniqueHelperLog {44  executedAt: number;45  executionTime: number;46  type: 'extrinsic' | 'rpc';47  status: 'Fail' | 'Success';48  call: string;49  params: any[];50  moduleError?: string;51  dispatchError?: any;52  events?: any;53}5455export interface IApiListeners {56  connected?: (...args: any[]) => any;57  disconnected?: (...args: any[]) => any;58  error?: (...args: any[]) => any;59  ready?: (...args: any[]) => any;60  decorated?: (...args: any[]) => any;61}6263export interface ICrossAccountId {64  Substrate?: TSubstrateAccount;65  Ethereum?: TEthereumAccount;66}6768export interface ICrossAccountIdLower {69  substrate?: TSubstrateAccount;70  ethereum?: TEthereumAccount;71}7273export interface IEthCrossAccountId {74  0: TEthereumAccount;75  1: TSubstrateAccount;76  eth: TEthereumAccount;77  sub: TSubstrateAccount;78}7980export interface ICollectionLimits {81  accountTokenOwnershipLimit?: number | null;82  sponsoredDataSize?: number | null;83  sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null;84  tokenLimit?: number | null;85  sponsorTransferTimeout?: number | null;86  sponsorApproveTimeout?: number | null;87  ownerCanTransfer?: boolean | null;88  ownerCanDestroy?: boolean | null;89  transfersEnabled?: boolean | null;90}9192export interface INestingPermissions {93  tokenOwner?: boolean;94  collectionAdmin?: boolean;95  restricted?: number[] | null;96}9798export interface ICollectionPermissions {99  access?: 'Normal' | 'AllowList';100  mintMode?: boolean;101  nesting?: INestingPermissions;102}103104export interface IProperty {105  key: string;106  value?: string;107}108109export interface ITokenPropertyPermission {110  key: string;111  permission: {112    mutable?: boolean;113    tokenOwner?: boolean;114    collectionAdmin?: boolean;115  }116}117118export interface IToken {119  collectionId: number;120  tokenId: number;121}122123export interface IBlock {124  extrinsics: IExtrinsic[]125  header: {126    parentHash: string,127    number: number,128  };129}130131export interface IExtrinsic {132  isSigned: boolean,133  method: {134    method: string,135    section: string,136    args: any[]137  }138}139140export interface ICollectionCreationOptions {141  name?: string | number[];142  description?: string | number[];143  tokenPrefix?: string | number[];144  mode?: {145    nft?: null;146    refungible?: null;147    fungible?: number;148  }149  permissions?: ICollectionPermissions;150  properties?: IProperty[];151  tokenPropertyPermissions?: ITokenPropertyPermission[];152  limits?: ICollectionLimits;153  pendingSponsor?: TSubstrateAccount;154}155156export interface IChainProperties {157  ss58Format: number;158  tokenDecimals: number[];159  tokenSymbol: string[]160}161162export interface ISubstrateBalance {163  free: bigint,164  reserved: bigint,165  miscFrozen: bigint,166  feeFrozen: bigint167}168169export interface IStakingInfo {170  block: bigint,171  amount: bigint,172}173174export interface IPovInfo {175  proofSize: number,176  compactProofSize: number,177  compressedProofSize: number,178  results: any[],179  kv: any,180}181182export interface ISchedulerOptions {183  scheduledId?: string,184  priority?: number,185  periodic?: {186    period: number,187    repetitions: number,188  },189}190191export interface IForeignAssetMetadata {192  name?: number | Uint8Array,193  symbol?: string,194  decimals?: number,195  minimalBalance?: bigint,196}197198export interface MoonbeamAssetInfo {199  location: any,200  metadata: {201    name: string,202    symbol: string,203    decimals: number,204    isFrozen: boolean,205    minimalBalance: bigint,206  },207  existentialDeposit: bigint,208  isSufficient: boolean,209  unitsPerSecond: bigint,210  numAssetsWeightHint: number,211}212213export interface AcalaAssetMetadata {214  name: string,215  symbol: string,216  decimals: number,217  minimalBalance: bigint,218}219220export interface DemocracyStandardAccountVote {221  balance: bigint,222  vote: {223    aye: boolean,224    conviction: number,225  },226}227228export type TSubstrateAccount = string;229export type TEthereumAccount = string;230export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';231export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';232export type TSiblingNetworkds = 'moonbeam' | 'moonriver' | 'acala' | 'karura' | 'westmint';233export type TRelayNetworks = 'rococo' | 'westend';234export type TNetworks = TUniqueNetworks | TSiblingNetworkds | TRelayNetworks;235export type TSigner = IKeyringPair; // | 'string'236export type TCollectionMode = 'nft' | 'rft' | 'ft';
after · tests/src/util/playgrounds/types.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {IKeyringPair} from '@polkadot/types/types';56export interface IEvent {7  section: string;8  method: string;9  index: [number, number] | string;10  data: any[];11  phase: {applyExtrinsic: number} | 'Initialization',12}1314export interface ITransactionResult {15  status: 'Fail' | 'Success';16  result: {17      dispatchError: any,18      events: {19        phase: any, // {ApplyExtrinsic: number} | 'Initialization',20        event: IEvent;21      }[];22  },23  blockHash: string,24  moduleError?: string;25}2627export interface ISubscribeBlockEventsData {28  number: number;29  hash: string;30  timestamp: number;31  events: IEvent[];32}3334export interface ILogger {35  log: (msg: any, level?: string) => void;36  level: {37    ERROR: 'ERROR';38    WARNING: 'WARNING';39    INFO: 'INFO';40    [key: string]: string;41  }42}4344export interface IUniqueHelperLog {45  executedAt: number;46  executionTime: number;47  type: 'extrinsic' | 'rpc';48  status: 'Fail' | 'Success';49  call: string;50  params: any[];51  moduleError?: string;52  dispatchError?: any;53  events?: any;54}5556export interface IApiListeners {57  connected?: (...args: any[]) => any;58  disconnected?: (...args: any[]) => any;59  error?: (...args: any[]) => any;60  ready?: (...args: any[]) => any;61  decorated?: (...args: any[]) => any;62}6364export interface ICrossAccountId {65  Substrate?: TSubstrateAccount;66  Ethereum?: TEthereumAccount;67}6869export interface ICrossAccountIdLower {70  substrate?: TSubstrateAccount;71  ethereum?: TEthereumAccount;72}7374export interface IEthCrossAccountId {75  0: TEthereumAccount;76  1: TSubstrateAccount;77  eth: TEthereumAccount;78  sub: TSubstrateAccount;79}8081export interface ICollectionLimits {82  accountTokenOwnershipLimit?: number | null;83  sponsoredDataSize?: number | null;84  sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null;85  tokenLimit?: number | null;86  sponsorTransferTimeout?: number | null;87  sponsorApproveTimeout?: number | null;88  ownerCanTransfer?: boolean | null;89  ownerCanDestroy?: boolean | null;90  transfersEnabled?: boolean | null;91}9293export interface INestingPermissions {94  tokenOwner?: boolean;95  collectionAdmin?: boolean;96  restricted?: number[] | null;97}9899export interface ICollectionPermissions {100  access?: 'Normal' | 'AllowList';101  mintMode?: boolean;102  nesting?: INestingPermissions;103}104105export interface IProperty {106  key: string;107  value?: string;108}109110export interface ITokenPropertyPermission {111  key: string;112  permission: {113    mutable?: boolean;114    tokenOwner?: boolean;115    collectionAdmin?: boolean;116  }117}118119export interface IToken {120  collectionId: number;121  tokenId: number;122}123124export interface IBlock {125  extrinsics: IExtrinsic[]126  header: {127    parentHash: string,128    number: number,129  };130}131132export interface IExtrinsic {133  isSigned: boolean,134  method: {135    method: string,136    section: string,137    args: any[]138  }139}140141export interface ICollectionCreationOptions {142  name?: string | number[];143  description?: string | number[];144  tokenPrefix?: string | number[];145  mode?: {146    nft?: null;147    refungible?: null;148    fungible?: number;149  }150  permissions?: ICollectionPermissions;151  properties?: IProperty[];152  tokenPropertyPermissions?: ITokenPropertyPermission[];153  limits?: ICollectionLimits;154  pendingSponsor?: TSubstrateAccount;155}156157export interface IChainProperties {158  ss58Format: number;159  tokenDecimals: number[];160  tokenSymbol: string[]161}162163export interface ISubstrateBalance {164  free: bigint,165  reserved: bigint,166  miscFrozen: bigint,167  feeFrozen: bigint168}169170export interface IStakingInfo {171  block: bigint,172  amount: bigint,173}174175export interface IPovInfo {176  proofSize: number,177  compactProofSize: number,178  compressedProofSize: number,179  results: any[],180  kv: any,181}182183export interface ISchedulerOptions {184  scheduledId?: string,185  priority?: number,186  periodic?: {187    period: number,188    repetitions: number,189  },190}191192export interface IForeignAssetMetadata {193  name?: number | Uint8Array,194  symbol?: string,195  decimals?: number,196  minimalBalance?: bigint,197}198199export interface MoonbeamAssetInfo {200  location: any,201  metadata: {202    name: string,203    symbol: string,204    decimals: number,205    isFrozen: boolean,206    minimalBalance: bigint,207  },208  existentialDeposit: bigint,209  isSufficient: boolean,210  unitsPerSecond: bigint,211  numAssetsWeightHint: number,212}213214export interface AcalaAssetMetadata {215  name: string,216  symbol: string,217  decimals: number,218  minimalBalance: bigint,219}220221export interface DemocracyStandardAccountVote {222  balance: bigint,223  vote: {224    aye: boolean,225    conviction: number,226  },227}228229export type TSubstrateAccount = string;230export type TEthereumAccount = string;231export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';232export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';233export type TSiblingNetworkds = 'moonbeam' | 'moonriver' | 'acala' | 'karura' | 'westmint';234export type TRelayNetworks = 'rococo' | 'westend';235export type TNetworks = TUniqueNetworks | TSiblingNetworkds | TRelayNetworks;236export type TSigner = IKeyringPair; // | 'string'237export type TCollectionMode = 'nft' | 'rft' | 'ft';
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -561,7 +561,7 @@
           if (status === this.transactionStatus.SUCCESS) {
             this.logger.log(`${label} successful`);
             unsub();
-            resolve({result, status});
+            resolve({result, status, blockHash: result.status.asInBlock.toHuman()});
           } else if (status === this.transactionStatus.FAIL) {
             let moduleError = null;
 
@@ -2657,20 +2657,35 @@
   }
 
   /**
-   * Unstake tokens for App Promotion
+   * Unstake all staked tokens
    * @param signer keyring of signer
    * @param amountToUnstake amount of tokens to unstake
    * @param label extra label for log
-   * @returns block number where balances will be unlocked
+   * @returns block hash where unstake happened
    */
-  async unstake(signer: TSigner, label?: string): Promise<number> {
+  async unstakeAll(signer: TSigner, label?: string): Promise<string> {
     if(typeof label === 'undefined') label = `${signer.address}`;
-    const _unstakeResult = await this.helper.executeExtrinsic(
-      signer, 'api.tx.appPromotion.unstake',
+    const unstakeResult = await this.helper.executeExtrinsic(
+      signer, 'api.tx.appPromotion.unstakeAll',
       [], true,
     );
-    // TODO extract block number fron events
-    return 1;
+    return unstakeResult.blockHash;
+  }
+
+  /**
+   * Unstake the part of a staked tokens
+   * @param signer keyring of signer
+   * @param amount amount of tokens to unstake
+   * @param label extra label for log
+   * @returns block hash where unstake happened
+   */
+  async unstakePartial(signer: TSigner, amount: bigint, label?: string): Promise<string> {
+    if(typeof label === 'undefined') label = `${signer.address}`;
+    const unstakeResult = await this.helper.executeExtrinsic(
+      signer, 'api.tx.appPromotion.unstakePartial',
+      [amount], true,
+    );
+    return unstakeResult.blockHash;
   }
 
   /**