From 7327dde6c10f15856eec897e8cae89961a03e4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Hjort?= <87635671+shjortConcordium@users.noreply.github.com> Date: Wed, 9 Mar 2022 14:53:44 +0100 Subject: [PATCH] Revert "Add delegation functionality" --- CHANGELOG.md | 14 -- README.md | 20 -- deps/concordium-base | 2 +- deps/concordium-grpc-api | 2 +- package.json | 2 +- src/accountHelpers.ts | 17 -- src/blockSummaryHelpers.ts | 34 --- src/client.ts | 174 +------------- src/index.ts | 1 - src/rewardStatusHelpers.ts | 4 - src/types.ts | 470 ++++--------------------------------- src/util.ts | 4 - test/client.test.ts | 442 ++-------------------------------- test/testHelpers.ts | 4 +- test/util.test.ts | 27 --- 15 files changed, 76 insertions(+), 1141 deletions(-) delete mode 100644 src/accountHelpers.ts delete mode 100644 src/blockSummaryHelpers.ts delete mode 100644 src/rewardStatusHelpers.ts delete mode 100644 test/util.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 447e4a5a1..3fe26e04a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,5 @@ # Changelog -## 0.7.0 2022-03-09 - -### Added - -- Support for getting baker list from node. -- Support for getting status of Baker-/L-pool (required node to have protocol version 4 or later) -- Helper functions for determining the version of `BlockSummary` and nested types. -- Support for initiating and updating contracts with parameters. - -### Changed - -- Updated `BlockSummary` type to include new version, effective from protocol version 4. -- Updated `AccountInfo` type to include new fields related to delegation introduced with protocol version 4. - ## 0.6.0 2022-02-02 ### Added diff --git a/README.md b/README.md index 1031abde2..8c5ddd319 100644 --- a/README.md +++ b/README.md @@ -542,26 +542,6 @@ const peersList = peerListResponse.getPeersList(); ... ``` -## getBakerList -Retrieves the list of ID's for registered bakers on the network at a specific block. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const bakerIds = await client.getBakerList(blockHash); -... -``` - -## getPoolStatus -Retrieves the status of a pool (either L-Pool or specific baker) at a specific block. -If a baker ID is specified, the status of that baker is returned. To get the status of the L-Pool, a baker ID should be left undefined. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const bakerId = BigInt(1); - -const bakerStatus = await client.getPoolStatus(blockHash, bakerId); -const lPoolStatus = await client.getPoolStatus(blockHash); -... -``` - ## Check block for transfers with memo The following example demonstrates how to check and parse a block for transfers with a memo. diff --git a/deps/concordium-base b/deps/concordium-base index 870769b5f..eae29f2a3 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 870769b5faf3a266996842fda2a2f8d4b5dcccaf +Subproject commit eae29f2a3a869738cbcbb7e82cf20bc40c9badb9 diff --git a/deps/concordium-grpc-api b/deps/concordium-grpc-api index 65a388838..89eb52e2e 160000 --- a/deps/concordium-grpc-api +++ b/deps/concordium-grpc-api @@ -1 +1 @@ -Subproject commit 65a388838570b0f2ce8a368426e33f9ae083c4f9 +Subproject commit 89eb52e2e8f75e1337a2193cc3905ece68da6947 diff --git a/package.json b/package.json index 53f5dadd1..ec668d86a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@concordium/node-sdk", - "version": "0.7.0", + "version": "0.6.0", "description": "Helpers for interacting with the Concordium node", "repository": { "type": "git", diff --git a/src/accountHelpers.ts b/src/accountHelpers.ts deleted file mode 100644 index 076eb6662..000000000 --- a/src/accountHelpers.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - AccountInfo, - AccountInfoBaker, - AccountInfoBakerV1, - AccountInfoDelegator, -} from './types'; - -export const isDelegatorAccount = ( - ai: AccountInfo -): ai is AccountInfoDelegator => - (ai as AccountInfoDelegator).accountDelegation !== undefined; - -export const isBakerAccount = (ai: AccountInfo): ai is AccountInfoBaker => - (ai as AccountInfoBaker).accountBaker !== undefined; - -export const isBakerAccountV1 = (ai: AccountInfo): ai is AccountInfoBakerV1 => - (ai as AccountInfoBakerV1).accountBaker?.bakerPoolInfo !== undefined; diff --git a/src/blockSummaryHelpers.ts b/src/blockSummaryHelpers.ts deleted file mode 100644 index 451c173ba..000000000 --- a/src/blockSummaryHelpers.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - Authorizations, - AuthorizationsV1, - BlockSummary, - BlockSummaryV1, - ChainParameters, - ChainParametersV1, - Keys, - KeysV1, - UpdateQueues, - UpdateQueuesV1, - Updates, - UpdatesV1, -} from './types'; - -export const isAuthorizationsV1 = (a: Authorizations): a is AuthorizationsV1 => - (a as AuthorizationsV1).timeParameters !== undefined; - -export const isChainParametersV1 = ( - cp: ChainParameters -): cp is ChainParametersV1 => - (cp as ChainParametersV1).mintPerPayday !== undefined; - -export const isKeysV1 = (k: Keys): k is KeysV1 => - isAuthorizationsV1(k.level2Keys); - -export const isUpdateQueuesV1 = (uq: UpdateQueues): uq is UpdateQueuesV1 => - (uq as UpdateQueuesV1).timeParameters !== undefined; - -export const isUpdatesV1 = (u: Updates): u is UpdatesV1 => - isUpdateQueuesV1(u.updateQueues); - -export const isBlockSummaryV1 = (bs: BlockSummary): bs is BlockSummaryV1 => - (bs as BlockSummaryV1).protocolVersion !== undefined; diff --git a/src/client.ts b/src/client.ts index e0966e8ed..a85d26b90 100644 --- a/src/client.ts +++ b/src/client.ts @@ -8,7 +8,6 @@ import { BlockHeight, Empty, GetAddressInfoRequest, - GetPoolStatusRequest, GetModuleSourceRequest, PeerListResponse, PeersRequest, @@ -27,10 +26,11 @@ import { AccountTransaction, AccountTransactionSignature, ArInfo, - ReduceStakePendingChange, - RemovalPendingChange, + BakerReduceStakePendingChange, + BakerRemovalPendingChange, BlockInfo, BlockSummary, + ChainParameters, ConsensusStatus, ContractAddress, CredentialDeploymentTransaction, @@ -49,24 +49,9 @@ import { Versioned, InstanceInfo, InstanceInfoSerialized, - BakerId, - ChainParametersV0, - ChainParametersV1, - PoolStatus, - BakerPoolStatusDetails, - CurrentPaydayBakerPoolStatus, - LPoolStatusDetails, - KeysMatching, - BakerPoolPendingChangeReduceBakerCapitalDetails, - LPoolStatus, - BakerPoolStatus, - RewardStatusV0, - RewardStatus, - RewardStatusV1, } from './types'; import { buildJsonResponseReviver, - intListToStringList, intToStringTransformer, isValidHash, unwrapBoolResponse, @@ -75,7 +60,6 @@ import { import { GtuAmount } from './types/gtuAmount'; import { ModuleReference } from './types/moduleReference'; import { Buffer as BufferFormater } from 'buffer/'; - /** * A concordium-node specific gRPC client wrapper. * @@ -227,8 +211,8 @@ export default class ConcordiumNodeClient { | keyof AccountReleaseSchedule | keyof ReleaseSchedule | keyof AccountBakerDetails - | keyof ReduceStakePendingChange - | keyof RemovalPendingChange + | keyof BakerReduceStakePendingChange + | keyof BakerRemovalPendingChange )[] = [ 'accountAmount', 'accountNonce', @@ -334,7 +318,7 @@ export default class ConcordiumNodeClient { | keyof PartyInfo | keyof FinalizationData | keyof TransactionSummary - | keyof (ChainParametersV0 & ChainParametersV1) + | keyof ChainParameters | keyof ExchangeRate | keyof UpdateQueue | keyof KeysWithThreshold @@ -348,6 +332,8 @@ export default class ConcordiumNodeClient { 'cost', 'energyCost', 'index', + 'bakerCooldownEpochs', + 'minimumThresholdForBaking', 'foundationAccountIndex', 'numerator', 'denominator', @@ -355,16 +341,6 @@ export default class ConcordiumNodeClient { 'amount', 'index', 'subindex', - - // v0 keys - 'bakerCooldownEpochs', - 'minimumThresholdForBaking', - - // v1 keys - 'rewardPeriodLength', - 'minimumEquityCapital', - 'poolOwnerCooldown', - 'delegatorCooldown', ]; return unwrapJsonResponse( @@ -625,140 +601,6 @@ export default class ConcordiumNodeClient { } } - async getRewardStatus( - blockHash: string - ): Promise { - if (!isValidHash(blockHash)) { - throw new Error('The input was not a valid hash: ' + blockHash); - } - - type DateKey = KeysMatching; - type BigIntKey = KeysMatching; - - const dates: DateKey[] = ['nextPaydayTime']; - const bigInts: BigIntKey[] = [ - 'protocolVersion', - 'gasAccount', - 'totalAmount', - 'totalStakedCapital', - 'bakingRewardAccount', - 'totalEncryptedAmount', - 'finalizationRewardAccount', - 'foundationTransactionRewards', - ]; - - const bh = new BlockHash(); - bh.setBlockHash(blockHash); - - const response = await this.sendRequest( - this.client.getRewardStatus, - bh - ); - - return unwrapJsonResponse( - response, - buildJsonResponseReviver(dates, bigInts), - intToStringTransformer(bigInts) - ); - } - - /** - * Retrieve list of bakers on the network. - * @param blockHash the block hash to get the smart contact instances at - * @returns A JSON list of baker IDs - */ - async getBakerList(blockHash: string): Promise { - if (!isValidHash(blockHash)) { - throw new Error('The input was not a valid hash: ' + blockHash); - } - - const bh = new BlockHash(); - bh.setBlockHash(blockHash); - - const response = await this.sendRequest(this.client.getBakerList, bh); - - return unwrapJsonResponse( - response, - undefined, - intListToStringList - )?.map((v) => BigInt(v)); - } - - /** - * Gets the status the L-pool. - * @param blockHash the block hash the status at - * @returns The status of the L-pool. - */ - async getPoolStatus(blockHash: string): Promise; - /** - * Gets the status a baker. - * @param blockHash the block hash the status at - * @param bakerId the ID of the baker to get the status for. - * @returns The status of the corresponding baker pool. - */ - async getPoolStatus( - blockHash: string, - bakerId: BakerId - ): Promise; - /** - * Gets the status of either a baker, if a baker ID is supplied, or the L-pool if left undefined. - * @param blockHash the block hash the status at - * @param [bakerId] the ID of the baker to get the status for. If left undefined, the status of the L-pool is returned. - * @returns The status of the corresponding pool. - */ - async getPoolStatus( - blockHash: string, - bakerId?: BakerId - ): Promise; - async getPoolStatus( - blockHash: string, - bakerId?: BakerId - ): Promise { - if (!isValidHash(blockHash)) { - throw new Error('The input was not a valid hash: ' + blockHash); - } - - const req = new GetPoolStatusRequest(); - req.setBlockHash(blockHash); - req.setLPool(bakerId === undefined); - - if (bakerId !== undefined) { - req.setBakerId(bakerId.toString()); - } - - type DateKey = KeysMatching< - BakerPoolPendingChangeReduceBakerCapitalDetails, - Date - >; - type BigIntKey = KeysMatching< - BakerPoolStatusDetails & - LPoolStatusDetails & - CurrentPaydayBakerPoolStatus, - bigint - >; - - const dates: DateKey[] = ['effectiveTime']; - const bigInts: BigIntKey[] = [ - 'bakerId', - 'bakerEquityCapital', - 'delegatedCapital', - 'delegatedCapitalCap', - 'currentPaydayTransactionFeesEarned', - 'currentPaydayDelegatedCapital', - 'blocksBaked', - 'transactionFeesEarned', - 'effectiveStake', - ]; - - const response = await this.sendRequest(this.client.getPoolStatus, req); - - return unwrapJsonResponse( - response, - buildJsonResponseReviver(dates, bigInts), - intToStringTransformer(bigInts) - ); - } - async getModuleSource( blockHash: string, moduleReference: ModuleReference diff --git a/src/index.ts b/src/index.ts index 01e8d0621..0c0470a4c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,4 +40,3 @@ export { } from './credentialDeploymentTransactions'; export { isAlias, getAlias } from './alias'; export { deserializeContractState } from './deserialization'; -export * from './blockSummaryHelpers'; diff --git a/src/rewardStatusHelpers.ts b/src/rewardStatusHelpers.ts deleted file mode 100644 index 2fb4f4471..000000000 --- a/src/rewardStatusHelpers.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RewardStatus, RewardStatusV1 } from './types'; - -export const isRewardStatusV1 = (rs: RewardStatus): rs is RewardStatusV1 => - (rs as RewardStatusV1).protocolVersion !== undefined; diff --git a/src/types.ts b/src/types.ts index 4889970cd..a1bc9bf30 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,11 +6,12 @@ import { Buffer } from 'buffer/'; import { ModuleReference } from './types/moduleReference'; /** - * Returns a union of all keys of type T with values matching type V. + * A reward fraction with a resolution of 1/100000, i.e. the + * denominator is implicitly 100000, and the interface therefore + * only contains the numerator value which can be in the interval + * [1, 100000]. */ -export type KeysMatching = { - [K in keyof T]-?: T[K] extends V ? K : never; -}[keyof T]; +export type RewardFraction = number; /* eslint-disable @typescript-eslint/no-explicit-any */ export interface Versioned { @@ -256,143 +257,52 @@ export interface FinalizationData { finalizers: PartyInfo[]; } -export interface Ratio { +export interface ExchangeRate { numerator: bigint; denominator: bigint; } -export type ExchangeRate = Ratio; - -export interface InclusiveRange { - min: N; - max: N; -} - -export type DurationSeconds = bigint; -/** Index of an epoch, or number of epochs. */ -export type Epoch = bigint; - export interface TransactionFeeDistribution { - baker: number; - gasAccount: number; -} - -export interface MintRate { - mantissa: number; - exponent: number; + baker: RewardFraction; + gasAccount: RewardFraction; } -interface MintDistributionCommon { - bakingReward: number; - finalizationReward: number; -} - -export interface MintDistributionV0 extends MintDistributionCommon { +export interface MintDistribution { mintPerSlot: number; + bakingReward: RewardFraction; + finalizationReward: RewardFraction; } -export type MintDistributionV1 = MintDistributionCommon; - -export type MintDistribution = MintDistributionV0 | MintDistributionV1; - export interface GasRewards { - baker: number; - finalizationProof: number; - accountCreation: number; - chainUpdate: number; + baker: RewardFraction; + finalizationProof: RewardFraction; + accountCreation: RewardFraction; + chainUpdate: RewardFraction; } -interface RewardParametersCommon { +export interface RewardParameters { transactionFeeDistribution: TransactionFeeDistribution; + mintDistribution: MintDistribution; gASRewards: GasRewards; } -/** - * Used from protocol version 1-3 - */ -export interface RewardParametersV0 extends RewardParametersCommon { - mintDistribution: MintDistributionV0; -} - -/** - * Used from protocol version 4 - */ -export interface RewardParametersV1 extends RewardParametersCommon { - mintDistribution: MintDistributionV1; -} - -export type RewardParameters = RewardParametersV0 | RewardParametersV1; - -export interface CooldownParametersV0 { - bakerCooldownEpochs: Epoch; -} - -export interface CooldownParametersV1 { - poolOwnerCooldown: DurationSeconds; - delegatorCooldown: DurationSeconds; -} - -export interface PoolParametersV0 { - minimumThresholdForBaking: Amount; -} - -export interface PoolParametersV1 { - finalizationCommissionLPool: number; - bakingCommissionLPool: number; - transactionCommissionLPool: number; - finalizationCommissionRange: InclusiveRange; - bakingCommissionRange: InclusiveRange; - transactionCommissionRange: InclusiveRange; - minimumEquityCapital: Amount; - capitalBound: number; - leverageBound: Ratio; -} - -export interface TimeParametersV1 { - /** - * In epochs - */ - rewardPeriodLength: Epoch; - mintPerPayday: number; -} - -interface ChainParametersCommon { +export interface ChainParameters { electionDifficulty: number; euroPerEnergy: ExchangeRate; microGTUPerEuro: ExchangeRate; accountCreationLimit: number; + bakerCooldownEpochs: bigint; + minimumThresholdForBaking: bigint; + rewardParameters: RewardParameters; foundationAccountIndex: bigint; } -/** - * Used from protocol version 1-3 - */ -export interface ChainParametersV0 - extends ChainParametersCommon, - CooldownParametersV0, - PoolParametersV0 { - rewardParameters: RewardParametersV0; -} - -/** - * Used from protocol version 4 - */ -export interface ChainParametersV1 - extends ChainParametersCommon, - CooldownParametersV1, - PoolParametersV1, - TimeParametersV1 { - rewardParameters: RewardParametersV1; -} - -export type ChainParameters = ChainParametersV0 | ChainParametersV1; - export interface Authorization { threshold: number; authorizedKeys: number[]; } -interface AuthorizationsCommon { +export interface Authorizations { emergency: Authorization; microGTUPerEuro: Authorization; euroPerEnergy: Authorization; @@ -401,9 +311,6 @@ interface AuthorizationsCommon { mintDistribution: Authorization; protocol: Authorization; paramGASRewards: Authorization; - /** - * From protocol version 4 and later, this controls the authorization of the poolParameters update. - */ bakerStakeThreshold: Authorization; electionDifficulty: Authorization; addAnonymityRevoker: Authorization; @@ -411,52 +318,22 @@ interface AuthorizationsCommon { keys: VerifyKey[]; } -/** - * Used from protocol version 1-3 - */ -export type AuthorizationsV0 = AuthorizationsCommon; - -/** - * Used from protocol version 4 - */ -export interface AuthorizationsV1 extends AuthorizationsCommon { - cooldownParameters: Authorization; - timeParameters: Authorization; -} - -export type Authorizations = AuthorizationsV0 | AuthorizationsV1; - export interface KeysWithThreshold { keys: VerifyKey[]; threshold: number; } -interface KeysCommon { +export interface Keys { rootKeys: KeysWithThreshold; level1Keys: KeysWithThreshold; + level2Keys: Authorizations; } -/** - * Used from protocol version 1-3 - */ -export interface KeysV0 extends KeysCommon { - level2Keys: AuthorizationsV0; -} - -/** - * Used from protocol version 4 - */ -export interface KeysV1 extends KeysCommon { - level2Keys: AuthorizationsV1; -} - -export type Keys = KeysV0 | KeysV1; - export interface UpdateQueueQueue { effectiveTime: Date; // TODO Update the type of update to a generic update transaction when // update types have been added. - /** Information about the actual update. */ + // Information about the actual update. update: unknown; } @@ -465,7 +342,7 @@ export interface UpdateQueue { queue: UpdateQueueQueue; } -interface UpdateQueuesCommon { +interface UpdateQueues { microGTUPerEuro: UpdateQueue; euroPerEnergy: UpdateQueue; transactionFeeDistribution: UpdateQueue; @@ -474,6 +351,7 @@ interface UpdateQueuesCommon { mintDistribution: UpdateQueue; protocol: UpdateQueue; gasRewards: UpdateQueue; + bakerStakeThreshold: UpdateQueue; addAnonymityRevoker: UpdateQueue; addIdentityProvider: UpdateQueue; rootKeys: UpdateQueue; @@ -481,97 +359,18 @@ interface UpdateQueuesCommon { level2Keys: UpdateQueue; } -/** - * Used from protocol version 1-3 - */ -export interface UpdateQueuesV0 extends UpdateQueuesCommon { - bakerStakeThreshold: UpdateQueue; +export interface Updates { + chainParameters: ChainParameters; + keys: Keys; + updateQueues: UpdateQueues; } -/** - * Used from protocol version 4 - */ -export interface UpdateQueuesV1 extends UpdateQueuesCommon { - cooldownParameters: UpdateQueue; - timeParameters: UpdateQueue; - poolParameters: UpdateQueue; -} - -export type UpdateQueues = UpdateQueuesV0 | UpdateQueuesV1; - -interface ProtocolUpdate { - message: string; - specificationUrl: string; - specificationHash: string; - specificationAuxiliaryData: string; -} - -interface UpdatesCommon { - protocolUpdate: ProtocolUpdate | undefined; -} - -/** - * Used from protocol version 1-3 - */ -export interface UpdatesV0 extends UpdatesCommon { - chainParameters: ChainParametersV0; - updateQueues: UpdateQueuesV0; - keys: KeysV0; -} - -/** - * Used from protocol version 4 - */ -export interface UpdatesV1 extends UpdatesCommon { - chainParameters: ChainParametersV1; - updateQueues: UpdateQueuesV1; - keys: KeysV1; -} - -export type Updates = UpdatesV0 | UpdatesV1; - -interface BlockSummaryCommon { +export interface BlockSummary { finalizationData: FinalizationData; transactionSummaries: TransactionSummary[]; + updates: Updates; } -/** - * Used from protocol version 1-3 - */ -export interface BlockSummaryV0 extends BlockSummaryCommon { - updates: UpdatesV0; -} - -/** - * Used from protocol version 4 - */ -export interface BlockSummaryV1 extends BlockSummaryCommon { - updates: UpdatesV1; - protocolVersion: bigint; -} - -export type BlockSummary = BlockSummaryV0 | BlockSummaryV1; - -interface RewardStatusCommon { - totalAmount: Amount; - totalEncryptedAmount: Amount; - bakingRewardAccount: Amount; - finalizationRewardAccount: Amount; - gasAccount: Amount; -} - -export type RewardStatusV0 = RewardStatusCommon; - -export interface RewardStatusV1 extends RewardStatusCommon { - foundationTransactionRewards: Amount; - nextPaydayTime: Date; - nextPaydayMintRate: MintRate; - totalStakedCapital: Amount; - protocolVersion: bigint; -} - -export type RewardStatus = RewardStatusV0 | RewardStatusV1; - export interface BlockInfo { blockParent: string; blockHash: string; @@ -599,13 +398,7 @@ export interface ConsensusStatus { currentEraGenesisBlock: string; lastFinalizedBlock: string; - /** - * In milliseconds - */ epochDuration: bigint; - /** - * In milliseconds - */ slotDuration: bigint; bestBlockHeight: bigint; lastFinalizedBlockHeight: bigint; @@ -732,186 +525,32 @@ export interface InitialAccountCredential { contents: InitialCredentialDeploymentValues; } -export interface ReduceStakePendingChange { +export interface BakerReduceStakePendingChange { change: 'ReduceStake'; newStake: bigint; epoch: bigint; } -export interface RemovalPendingChange { - change: 'RemoveStake'; +export interface BakerRemovalPendingChange { + change: 'RemoveBaker'; epoch: bigint; } -export type StakePendingChange = - | ReduceStakePendingChange - | RemovalPendingChange; - -export enum OpenStatus { - OpenForAll = 0, - ClosedForNew = 1, - ClosedForAll = 2, -} - -/** - * How the node translates OpenStatus to JSON. - */ -export enum OpenStatusText { - OpenForAll = 'openForAll', - ClosedForNew = 'closedForNew', - ClosedForAll = 'closedForAll', -} - -export type Amount = bigint; -export type BakerId = bigint; - -export interface BakerPoolInfo { - openStatus: OpenStatusText; - metadataUrl: string; - commissionRates: CommissionRates; -} - -export interface CommissionRates { - transactionCommission: number; - bakingCommission: number; - finalizationCommission: number; -} - -export interface CurrentPaydayBakerPoolStatus { - blocksBaked: bigint; - finalizationLive: boolean; - transactionFeesEarned: Amount; - effectiveStake: Amount; - lotteryPower: number; - bakerEquityCapital: Amount; - delegatedCapital: Amount; -} - -export enum BakerPoolPendingChangeType { - ReduceBakerCapital = 'ReduceBakerCapital', - RemovePool = 'RemovePool', - NoChange = 'NoChange', -} - -type BakerPoolPendingChangeWrapper< - T extends keyof typeof BakerPoolPendingChangeType, - S extends Record -> = S & { - pendingChangeType: T; -}; - -export interface BakerPoolPendingChangeReduceBakerCapitalDetails { - bakerEquityCapital: Amount; - effectiveTime: Date; -} - -export type BakerPoolPendingChangeReduceBakerCapital = - BakerPoolPendingChangeWrapper< - BakerPoolPendingChangeType.ReduceBakerCapital, - BakerPoolPendingChangeReduceBakerCapitalDetails - >; +export type BakerPendingChange = + | BakerReduceStakePendingChange + | BakerRemovalPendingChange; -export interface BakerPoolPendingChangeRemovePoolDetails { - effectiveTime: Date; -} - -export type BakerPoolPendingChangeRemovePool = BakerPoolPendingChangeWrapper< - BakerPoolPendingChangeType.RemovePool, - BakerPoolPendingChangeRemovePoolDetails ->; - -export type BakerPoolPendingChangeNoChange = BakerPoolPendingChangeWrapper< - BakerPoolPendingChangeType.NoChange, - // eslint-disable-next-line @typescript-eslint/ban-types - {} ->; - -export type BakerPoolPendingChange = - | BakerPoolPendingChangeReduceBakerCapital - | BakerPoolPendingChangeRemovePool - | BakerPoolPendingChangeNoChange; - -export enum PoolStatusType { - BakerPool = 'BakerPool', - LPool = 'LPool', -} - -type PoolStatusWrapper = S & { - poolType: T; -}; - -export interface BakerPoolStatusDetails { - bakerId: BakerId; - bakerAddress: string; - bakerEquityCapital: Amount; - delegatedCapital: Amount; - delegatedCapitalCap: Amount; - poolInfo: BakerPoolInfo; - bakerStakePendingChange: BakerPoolPendingChange; - currentPaydayStatus?: CurrentPaydayBakerPoolStatus; -} - -export type BakerPoolStatus = PoolStatusWrapper< - PoolStatusType.BakerPool, - BakerPoolStatusDetails ->; - -export interface LPoolStatusDetails { - delegatedCapital: Amount; - commissionRates: CommissionRates; - currentPaydayTransactionFeesEarned: Amount; - currentPaydayDelegatedCapital: Amount; -} - -export type LPoolStatus = PoolStatusWrapper< - PoolStatusType.LPool, - LPoolStatusDetails ->; - -export type PoolStatus = BakerPoolStatus | LPoolStatus; - -export enum DelegationTargetType { - LPool = 'L-Pool', - Baker = 'Baker', -} - -export interface DelegationTargetLPool { - delegateType: DelegationTargetType.LPool; -} - -export interface DelegationTargetBaker { - delegateType: DelegationTargetType.Baker; - bakerId: BakerId; -} - -export type DelegationTarget = DelegationTargetLPool | DelegationTargetBaker; - -interface AccountBakerDetailsCommon { +export interface AccountBakerDetails { restakeEarnings: boolean; - bakerId: BakerId; + bakerId: bigint; bakerAggregationVerifyKey: string; bakerElectionVerifyKey: string; bakerSignatureVerifyKey: string; stakedAmount: bigint; - pendingChange?: StakePendingChange; -} - -export type AccountBakerDetailsV0 = AccountBakerDetailsCommon; - -export interface AccountBakerDetailsV1 extends AccountBakerDetailsCommon { - bakerPoolInfo: BakerPoolInfo; -} - -export type AccountBakerDetails = AccountBakerDetailsV0 | AccountBakerDetailsV1; - -export interface AccountDelegationDetails { - restakeEarnings: boolean; - stakedAmount: bigint; - delegationTarget: DelegationTarget; - pendingChange?: StakePendingChange; + pendingChange?: BakerPendingChange; } -interface AccountInfoCommon { +export interface AccountInfo { accountNonce: bigint; accountAmount: bigint; accountIndex: bigint; @@ -927,31 +566,10 @@ interface AccountInfoCommon { number, Versioned >; -} - -export type AccountInfoSimple = AccountInfoCommon; - -export interface AccountInfoBakerV0 extends AccountInfoCommon { - accountBaker: AccountBakerDetailsV0; -} -/** Protocol version 4 and later. */ -export interface AccountInfoBakerV1 extends AccountInfoCommon { - accountBaker: AccountBakerDetailsV1; + accountBaker?: AccountBakerDetails; } -export type AccountInfoBaker = AccountInfoBakerV0 | AccountInfoBakerV1; - -/** Protocol version 4 and later. */ -export interface AccountInfoDelegator extends AccountInfoCommon { - accountDelegation: AccountDelegationDetails; -} - -export type AccountInfo = - | AccountInfoSimple - | AccountInfoBaker - | AccountInfoDelegator; - export interface Description { name: string; url: string; diff --git a/src/util.ts b/src/util.ts index 784c4b233..f132d8748 100644 --- a/src/util.ts +++ b/src/util.ts @@ -23,10 +23,6 @@ function intToString(jsonStruct: string, keys: string[]): string { return result; } -export function intListToStringList(jsonStruct: string): string { - return jsonStruct.replace(/(\-?[0-9]+)/g, '"$1"'); -} - /** * A transformer that converts all the values provided as keys to * string values. diff --git a/test/client.test.ts b/test/client.test.ts index 562a39a09..5b0dd5e32 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -1,14 +1,8 @@ import { - ReduceStakePendingChange, + BakerReduceStakePendingChange, instanceOfTransferWithMemoTransactionSummary, NormalAccountCredential, TransferredWithScheduleEvent, - PoolStatusType, - BakerId, - BakerPoolPendingChangeType, - OpenStatusText, - DelegationTargetType, - DelegationTargetBaker, } from '../src/types'; import { AccountAddress } from '../src/types/accountAddress'; import { isValidDate, getNodeClient } from './testHelpers'; @@ -16,13 +10,6 @@ import { bulletProofGenerators } from './resources/bulletproofgenerators'; import { ipVerifyKey1, ipVerifyKey2 } from './resources/ipVerifyKeys'; import { PeerElement } from '../grpc/concordium_p2p_rpc_pb'; import { CredentialRegistrationId } from '../src/types/CredentialRegistrationId'; -import { isBlockSummaryV1 } from '../src/blockSummaryHelpers'; -import { - isBakerAccount, - isBakerAccountV1, - isDelegatorAccount, -} from '../src/accountHelpers'; -import { isRewardStatusV1 } from '../src/rewardStatusHelpers'; const client = getNodeClient(); @@ -115,29 +102,14 @@ test('transferred event is parsed correctly', async () => { } }); -test('block summary for valid block hash retrieves block summary (v0)', async () => { +test('block summary for valid block hash retrieves block summary', async () => { const blockHash = '4b39a13d326f422c76f12e20958a90a4af60a2b7e098b2a59d21d402fff44bfc'; const blockSummary = await client.getBlockSummary(blockHash); if (!blockSummary) { throw new Error('The block could not be found by the test'); } - - if (isBlockSummaryV1(blockSummary)) { - throw new Error('Expected block to adhere to version 0 spec.'); - } - return Promise.all([ - expect( - blockSummary.updates.chainParameters.rewardParameters - .mintDistribution.mintPerSlot - ).toBe(7.555665e-10), - expect( - blockSummary.updates.chainParameters.minimumThresholdForBaking - ).toBe(15000000000n), - expect(blockSummary.updates.chainParameters.bakerCooldownEpochs).toBe( - 166n - ), expect(blockSummary.finalizationData.finalizationIndex).toBe(15436n), expect(blockSummary.finalizationData.finalizationDelay).toBe(0n), expect(blockSummary.finalizationData.finalizationBlockPointer).toBe( @@ -176,6 +148,10 @@ test('block summary for valid block hash retrieves block summary (v0)', async () blockSummary.updates.chainParameters.rewardParameters .mintDistribution.finalizationReward ).toBe(0.3), + expect( + blockSummary.updates.chainParameters.rewardParameters + .mintDistribution.mintPerSlot + ).toBe(7.555665e-10), expect( blockSummary.updates.chainParameters.rewardParameters.gASRewards .chainUpdate @@ -192,6 +168,10 @@ test('block summary for valid block hash retrieves block summary (v0)', async () blockSummary.updates.chainParameters.rewardParameters.gASRewards .finalizationProof ).toBe(0.005), + + expect( + blockSummary.updates.chainParameters.minimumThresholdForBaking + ).toBe(15000000000n), expect( blockSummary.updates.chainParameters.microGTUPerEuro.numerator ).toBe(500000n), @@ -208,6 +188,9 @@ test('block summary for valid block hash retrieves block summary (v0)', async () expect( blockSummary.updates.chainParameters.foundationAccountIndex ).toBe(10n), + expect(blockSummary.updates.chainParameters.bakerCooldownEpochs).toBe( + 166n + ), expect(blockSummary.updates.chainParameters.accountCreationLimit).toBe( 10 ), @@ -283,95 +266,6 @@ test('block summary for valid block hash retrieves block summary (v0)', async () ]); }); -test('block summary for valid block hash retrieves block summary (v1)', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - const blockSummary = await client.getBlockSummary(blockHash); - if (!blockSummary) { - throw new Error('The block could not be found by the test'); - } - - if (!isBlockSummaryV1(blockSummary)) { - throw new Error('Expected block to adhere to version 1 spec.'); - } - - return Promise.all([ - // Chain parameters - expect(blockSummary.updates.chainParameters.mintPerPayday).toBe( - 1.088e-5 - ), - expect(blockSummary.updates.chainParameters.rewardPeriodLength).toBe( - 4n - ), - expect(blockSummary.updates.chainParameters.minimumEquityCapital).toBe( - 14000n - ), - expect(blockSummary.updates.chainParameters.capitalBound).toBe(0.25), - expect(blockSummary.updates.chainParameters.poolOwnerCooldown).toBe( - 10800n - ), - expect(blockSummary.updates.chainParameters.delegatorCooldown).toBe( - 7200n - ), - expect( - blockSummary.updates.chainParameters.transactionCommissionLPool - ).toBe(0.1), - expect( - blockSummary.updates.chainParameters.finalizationCommissionLPool - ).toBe(1.0), - expect(blockSummary.updates.chainParameters.bakingCommissionLPool).toBe( - 0.1 - ), - expect( - blockSummary.updates.chainParameters.leverageBound.numerator - ).toBe(3n), - expect( - blockSummary.updates.chainParameters.leverageBound.denominator - ).toBe(1n), - expect( - blockSummary.updates.chainParameters.transactionCommissionRange.min - ).toBe(0.05), - expect( - blockSummary.updates.chainParameters.transactionCommissionRange.max - ).toBe(0.05), - expect( - blockSummary.updates.chainParameters.bakingCommissionRange.min - ).toBe(0.05), - expect( - blockSummary.updates.chainParameters.bakingCommissionRange.max - ).toBe(0.05), - expect( - blockSummary.updates.chainParameters.finalizationCommissionRange.min - ).toBe(1), - expect( - blockSummary.updates.chainParameters.finalizationCommissionRange.max - ).toBe(1), - - // Update queues - expect( - blockSummary.updates.updateQueues.protocol.nextSequenceNumber - ).toBe(4n), - expect( - blockSummary.updates.updateQueues.cooldownParameters - .nextSequenceNumber - ).toBe(1n), - expect( - blockSummary.updates.updateQueues.timeParameters.nextSequenceNumber - ).toBe(1n), - expect( - blockSummary.updates.updateQueues.poolParameters.nextSequenceNumber - ).toBe(1n), - - // keys - expect( - blockSummary.updates.keys.level2Keys.cooldownParameters.threshold - ).toBe(1), - expect( - blockSummary.updates.keys.level2Keys.timeParameters.threshold - ).toBe(1), - ]); -}); - test('block summary for invalid block hash throws error', async () => { const invalidBlockHash = 'fd4915edca67b4e8f6521641a638a3abdbdd7934e42a9a52d8673861e2ebdd2'; @@ -508,12 +402,12 @@ test('account info with baker details, and with no pending change', async () => throw new Error('Test failed to find account info'); } - if (!isBakerAccount(accountInfo)) { + const bakerDetails = accountInfo.accountBaker; + + if (!bakerDetails) { throw new Error('Account info doesnt contain baker details'); } - const bakerDetails = accountInfo.accountBaker; - expect(bakerDetails.bakerId).toEqual(743n); expect(bakerDetails.stakedAmount).toEqual(15000000000n); expect(bakerDetails.restakeEarnings).toEqual(true); @@ -543,9 +437,7 @@ test('account info with baker details, and with a pending baker removal', async throw new Error('Test failed to find account info'); } - const bakerDetails = isBakerAccount(accountInfo) - ? accountInfo.accountBaker - : undefined; + const bakerDetails = accountInfo.accountBaker; if (!bakerDetails) { throw new Error('Account info doesnt contain baker details'); @@ -587,9 +479,7 @@ test('account info with baker details, and with a pending stake reduction', asyn throw new Error('Test failed to find account info'); } - const bakerDetails = isBakerAccount(accountInfo) - ? accountInfo.accountBaker - : undefined; + const bakerDetails = accountInfo.accountBaker; if (!bakerDetails) { throw new Error('Account info doesnt contain baker details'); @@ -615,7 +505,7 @@ test('account info with baker details, and with a pending stake reduction', asyn } expect(pendingChange.change).toEqual('ReduceStake'); - expect((pendingChange as ReduceStakePendingChange).newStake).toEqual( + expect((pendingChange as BakerReduceStakePendingChange).newStake).toEqual( 14000000000n ); expect(pendingChange.epoch).toEqual(838n); @@ -1108,297 +998,3 @@ test('anonymity revokers are retrieved at the given block', async () => { ), ]); }); - -test('reward status can be accessed at given block', async () => { - const blockHash = - '7f7409679e53875567e2ae812c9fcefe90ced8761d08554756f42bf268a42749'; - - const rewardStatus = await client.getRewardStatus(blockHash); - - if (!rewardStatus) { - throw new Error('Test could not retrieve reward status of block.'); - } - - const { - finalizationRewardAccount, - totalEncryptedAmount, - bakingRewardAccount, - totalAmount, - gasAccount, - } = rewardStatus; - - expect(finalizationRewardAccount).toBe(5n); - expect(totalEncryptedAmount).toBe(0n); - expect(bakingRewardAccount).toBe(3663751591n); - expect(totalAmount).toBe(10014486887211834n); - expect(gasAccount).toBe(3n); -}); - -test('new version of reward status can be accessed at given block', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - - const rewardStatus = await client.getRewardStatus(blockHash); - - if (!rewardStatus) { - throw new Error('Test could not retrieve reward status of block.'); - } - - if (!isRewardStatusV1(rewardStatus)) { - throw new Error( - 'Test expected reward status to be delegation protocol version.' - ); - } - - const { - finalizationRewardAccount, - totalEncryptedAmount, - bakingRewardAccount, - totalAmount, - gasAccount, - nextPaydayTime, - protocolVersion, - nextPaydayMintRate, - totalStakedCapital, - foundationTransactionRewards, - } = rewardStatus; - - expect(finalizationRewardAccount).toBe(3n); - expect(totalEncryptedAmount).toBe(0n); - expect(bakingRewardAccount).toBe(2n); - expect(totalAmount).toBe(188279875066742n); - expect(gasAccount).toBe(3n); - expect(protocolVersion).toBe(4n); - expect(totalStakedCapital).toBe(15002000000000n); - expect(foundationTransactionRewards).toBe(0n); - expect(nextPaydayTime).toEqual(new Date('2022-03-07T07:15:01.5Z')); - expect(nextPaydayMintRate).toBe(1.088e-5); -}); - -test('reward status is undefined at an unknown block', async () => { - const blockHash = - '7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749'; - const rs = await client.getRewardStatus(blockHash); - return expect(rs).toBeUndefined(); -}); - -test('baker list can be accessed at given block', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - const bl = await client.getBakerList(blockHash); - - expect(bl).toEqual([0n, 1n, 2n, 3n, 4n]); -}); - -test('baker list is undefined at an unknown block', async () => { - const blockHash = - '7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749'; - const bl = await client.getBakerList(blockHash); - return expect(bl).toBeUndefined(); -}); - -test('pool status can be accessed at given block for L-pool', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - - const ps = await client.getPoolStatus(blockHash); - - if (!ps) { - throw new Error('Test could not retrieve reward status of block.'); - } - - expect(ps.poolType).toBe(PoolStatusType.LPool); - - const { - commissionRates, - delegatedCapital, - currentPaydayDelegatedCapital, - currentPaydayTransactionFeesEarned, - } = ps; - - expect(commissionRates.bakingCommission).toBe(0.1); - expect(commissionRates.transactionCommission).toBe(0.1); - expect(commissionRates.finalizationCommission).toBe(1); - expect(delegatedCapital.toString()).toBe('1000000000'); - expect(currentPaydayDelegatedCapital.toString()).toBe('0'); - expect(currentPaydayTransactionFeesEarned.toString()).toBe('0'); -}); - -test('pool status can be accessed at given block for specific baker', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - const bid: BakerId = 1n; - - const ps = await client.getPoolStatus(blockHash, bid); - - if (!ps) { - throw new Error('Test could not retrieve reward status of block.'); - } - - const { - poolType, - delegatedCapital, - bakerId, - poolInfo: { - openStatus, - metadataUrl, - commissionRates: { - finalizationCommission, - transactionCommission, - bakingCommission, - }, - }, - bakerAddress, - bakerEquityCapital, - delegatedCapitalCap, - bakerStakePendingChange, - currentPaydayStatus, - } = ps; - - console.log(ps); - console.log(ps.currentPaydayStatus); - - expect(poolType).toBe(PoolStatusType.BakerPool); - expect(bakerId).toBe(1n); - expect(bakerAddress).toBe( - '39BjG2g6JaTUVSEizZQu6DrsPjwNMKW2ftqToBvTVTMna7pNud' - ); - expect(delegatedCapital).toBe(0n); - expect(openStatus).toBe(OpenStatusText.OpenForAll); - expect(metadataUrl).toBe(''); - expect(finalizationCommission).toBe(1); - expect(transactionCommission).toBe(0.05); - expect(bakingCommission).toBe(0.05); - expect(bakerEquityCapital).toBe(3000000000000n); - expect(delegatedCapitalCap).toBe(1000666666666n); - expect(bakerStakePendingChange.pendingChangeType).toBe( - BakerPoolPendingChangeType.NoChange - ); - expect(currentPaydayStatus?.bakerEquityCapital).toBe(3000000000000n); - expect(currentPaydayStatus?.blocksBaked).toBe(25n); - expect(currentPaydayStatus?.finalizationLive).toBe(true); - expect(currentPaydayStatus?.transactionFeesEarned).toBe(0n); - expect(currentPaydayStatus?.effectiveStake).toBe(3000000000000n); - expect(currentPaydayStatus?.lotteryPower).toBe(0.2); - expect(currentPaydayStatus?.delegatedCapital).toBe(0n); -}); - -test('pool status is undefined at an unknown block', async () => { - const blockHash = - '7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749'; - const ps = await client.getPoolStatus(blockHash); - return expect(ps).toBeUndefined(); -}); - -test('account info with new baker info can be accessed', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - const address = new AccountAddress( - '2zmRFpd7g12oBAZHSDqnbJ3Eg5HGr2sE9aFCL6mD3pyUSsiDSJ' - ); - - const ai = await client.getAccountInfo(address, blockHash); - - if (!ai) { - throw new Error('Expected account info to be accessible.'); - } - - if (!isBakerAccountV1(ai)) { - throw new Error( - 'Test assumes the account is a baker on delegation protocol version' - ); - } - - const { - accountBaker: { - bakerId, - stakedAmount, - bakerPoolInfo: { - metadataUrl, - openStatus, - commissionRates: { - bakingCommission, - transactionCommission, - finalizationCommission, - }, - }, - restakeEarnings, - pendingChange, - }, - } = ai; - - expect(bakerId).toBe(0n); - expect(stakedAmount).toBe(3000000000000n); - expect(restakeEarnings).toBe(false); - expect(pendingChange).toBeUndefined(); - expect(metadataUrl).toBe(''); - expect(openStatus).toBe(OpenStatusText.OpenForAll); - expect(bakingCommission).toBe(0.05); - expect(transactionCommission).toBe(0.05); - expect(finalizationCommission).toBe(1); -}); - -test('account info with delegation to specific baker can be accessed', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - const address = new AccountAddress( - '3UztagvMc6dMeRgW51tG8SR18BdHXaGyWBdx8nAz3caKTrWnuo' - ); - - const ai = await client.getAccountInfo(address, blockHash); - - if (!ai) { - throw new Error('Expected account info to be accessible.'); - } - - if (!isDelegatorAccount(ai)) { - throw new Error('Test assumes the account is a delegator'); - } - - const { - accountDelegation: { - restakeEarnings, - stakedAmount, - pendingChange, - delegationTarget, - }, - } = ai; - - expect(stakedAmount).toBe(1000000000n); - expect(restakeEarnings).toBe(true); - expect(pendingChange).toBeUndefined(); - expect(delegationTarget.delegateType).toBe(DelegationTargetType.Baker); - expect((delegationTarget as DelegationTargetBaker).bakerId).toBe(0n); -}); - -test('account info with delegation to L-pool can be accessed', async () => { - const blockHash = - '1e69dbed0234f0e8cf7965191bae42cd49415646984346e01716c8f8577ab6e0'; - const address = new AccountAddress( - '4Y7qexYDywtB8K5NySAqZDUqg8FBNwDdu616NdvVqUfVQ1ULSq' - ); - - const ai = await client.getAccountInfo(address, blockHash); - - if (!ai) { - throw new Error('Expected account info to be accessible.'); - } - - if (!isDelegatorAccount(ai)) { - throw new Error('Test assumes the account is a delegator'); - } - - const { - accountDelegation: { - restakeEarnings, - stakedAmount, - pendingChange, - delegationTarget, - }, - } = ai; - - expect(stakedAmount).toBe(1000000000n); - expect(restakeEarnings).toBe(true); - expect(pendingChange).toBeUndefined(); - expect(delegationTarget.delegateType).toBe(DelegationTargetType.LPool); -}); diff --git a/test/testHelpers.ts b/test/testHelpers.ts index 2d673b91d..8c3df0970 100644 --- a/test/testHelpers.ts +++ b/test/testHelpers.ts @@ -9,11 +9,11 @@ import { MobileWalletExport } from '../src/wallet/types'; * Creates a client to communicate with a local concordium-node * used for automatic tests. */ -export function getNodeClient(address = '127.0.0.1'): ConcordiumNodeClient { +export function getNodeClient(): ConcordiumNodeClient { const metadata = new Metadata(); metadata.add('authentication', 'rpcadmin'); return new ConcordiumNodeClient( - address, + '127.0.0.1', 10000, credentials.createInsecure(), metadata, diff --git a/test/util.test.ts b/test/util.test.ts deleted file mode 100644 index 94998daad..000000000 --- a/test/util.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { intListToStringList } from '../src/util'; - -test('Correctly converts stringified list of numbers to stringified list of corresponding strings', () => { - // List of ints - let numbers = '[1, 22, 332]'; - let strings = intListToStringList(numbers); - - expect(strings).toEqual('["1", "22", "332"]'); - - // Empty list - numbers = '[]'; - strings = intListToStringList(numbers); - - expect(strings).toEqual('[]'); - - // Single int list - numbers = '[1]'; - strings = intListToStringList(numbers); - - expect(strings).toEqual('["1"]'); - - // negative int list - numbers = '[-1, 21, -32]'; - strings = intListToStringList(numbers); - - expect(strings).toEqual('["-1", "21", "-32"]'); -});