From 3a5931702ab6aeb5a62b18d2834125ce6fbfc594 Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Wed, 13 Dec 2023 19:08:51 +0100 Subject: [PATCH] feat!: use new conway certs in stake and delegation scenarios BREAKING CHANGE: typo stakeKeyCertficates renamed to stakeKeyCertificates --- .../core/src/Cardano/types/Certificate.ts | 49 +++ packages/core/src/util/txInspector.ts | 2 +- .../test/Cardano/types/Certificates.test.ts | 20 +- .../certificates/withStakeKeyRegistrations.ts | 9 +- .../Mappers/certificates/withStakeKeys.ts | 39 ++- .../withStakeKeyRegistrations.test.ts | 6 +- .../certificates/withStakeKeys.test.ts | 65 ++-- .../DelegationTracker/DelegationTracker.ts | 32 +- .../DelegationTracker/RewardAccounts.ts | 39 ++- .../DelegationTracker/RewardsHistory.ts | 2 +- .../transactionCertificates.ts | 19 +- .../DelegationTracker.test.ts | 59 +++- .../DelegationTracker/RewardAccounts.test.ts | 284 +++++++++++++----- .../DelegationTracker/RewardsHistory.test.ts | 181 ++++++----- .../transactionCertificates.test.ts | 21 +- 15 files changed, 531 insertions(+), 296 deletions(-) diff --git a/packages/core/src/Cardano/types/Certificate.ts b/packages/core/src/Cardano/types/Certificate.ts index 9d966f73794..3929b724a56 100644 --- a/packages/core/src/Cardano/types/Certificate.ts +++ b/packages/core/src/Cardano/types/Certificate.ts @@ -175,6 +175,46 @@ export type Certificate = | UnRegisterDelegateRepresentativeCertificate | UpdateDelegateRepresentativeCertificate; +export const StakeRegistrationCertificateTypes = [ + CertificateType.StakeRegistration, + CertificateType.Registration, + CertificateType.VoteRegistrationDelegation, + CertificateType.StakeRegistrationDelegation, + CertificateType.StakeVoteRegistrationDelegation +] as const; + +export type StakeRegistrationCertificateTypes = typeof StakeRegistrationCertificateTypes[number]; + +export type StakeDelegationCertificateUnion = + | StakeDelegationCertificate + | StakeVoteDelegationCertificate + | StakeRegistrationDelegationCertificate + | StakeVoteRegistrationDelegationCertificate; + +export const StakeDelegationCertificateTypes = [ + CertificateType.StakeDelegation, + CertificateType.StakeVoteDelegation, + CertificateType.StakeRegistrationDelegation, + CertificateType.StakeVoteRegistrationDelegation +] as const; + +export type StakeDelegationCertificateTypes = typeof StakeDelegationCertificateTypes[number]; + +export type RegAndDeregCertificateUnion = + | StakeAddressCertificate + | NewStakeAddressCertificate + | VoteRegistrationDelegationCertificate + | StakeRegistrationDelegationCertificate + | StakeVoteRegistrationDelegationCertificate; + +export const RegAndDeregCertificateTypes = [ + ...StakeRegistrationCertificateTypes, + CertificateType.Unregistration, + CertificateType.StakeDeregistration +] as const; + +export type RegAndDeregCertificateTypes = typeof RegAndDeregCertificateTypes[number]; + /** * Creates a stake key registration certificate from a given reward account. * @@ -215,3 +255,12 @@ export const createDelegationCert = (rewardAccount: RewardAccount, poolId: PoolI type: CredentialType.KeyHash } }); + +/** Filters certificates, returning only stake key register/deregister certificates */ +export const stakeKeyCertificates = (certificates?: Certificate[]) => + certificates?.filter((certificate): certificate is RegAndDeregCertificateUnion => + RegAndDeregCertificateTypes.includes(certificate.__typename as RegAndDeregCertificateTypes) + ) || []; + +export const includesAnyCertificate = (haystack: Certificate[], needle: readonly CertificateType[]) => + haystack.some(({ __typename }) => needle.includes(__typename)) || false; diff --git a/packages/core/src/util/txInspector.ts b/packages/core/src/util/txInspector.ts index 731aad92dca..e158a42a8fa 100644 --- a/packages/core/src/util/txInspector.ts +++ b/packages/core/src/util/txInspector.ts @@ -163,7 +163,7 @@ export const totalAddressOutputsValueInspector: SendReceiveValueInspector = (own export const getCertificatesByType = ( tx: Tx, rewardAccounts: RewardAccount[], - certificateTypes?: CertificateType[] + certificateTypes?: readonly CertificateType[] ) => { if (!tx.body.certificates || tx.body.certificates.length === 0) return []; const certificates = certificateTypes diff --git a/packages/core/test/Cardano/types/Certificates.test.ts b/packages/core/test/Cardano/types/Certificates.test.ts index c89577b41e2..87dec32dfcf 100644 --- a/packages/core/test/Cardano/types/Certificates.test.ts +++ b/packages/core/test/Cardano/types/Certificates.test.ts @@ -6,7 +6,8 @@ import { RewardAccount, createDelegationCert, createStakeDeregistrationCert, - createStakeRegistrationCert + createStakeRegistrationCert, + stakeKeyCertificates } from '../../../src/Cardano'; const rewardAccount = RewardAccount('stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr'); @@ -47,4 +48,21 @@ describe('Certificate', () => { }); }); }); + + it('can identify stake key certificates', () => { + const certificates = stakeKeyCertificates([ + { __typename: Cardano.CertificateType.StakeDelegation } as Cardano.Certificate, // does not register stake key + { __typename: Cardano.CertificateType.StakeRegistration } as Cardano.Certificate, + { __typename: Cardano.CertificateType.StakeDeregistration } as Cardano.Certificate, + { __typename: Cardano.CertificateType.StakeVoteDelegation } as Cardano.Certificate, // does not register stake key + { __typename: Cardano.CertificateType.StakeRegistrationDelegation } as Cardano.Certificate, + { __typename: Cardano.CertificateType.StakeVoteDelegation } as Cardano.Certificate, // does not register stake key + { __typename: Cardano.CertificateType.StakeVoteRegistrationDelegation } as Cardano.Certificate, + { __typename: Cardano.CertificateType.VoteRegistrationDelegation } as Cardano.Certificate, + { __typename: Cardano.CertificateType.Registration } as Cardano.Certificate + ]); + expect(certificates).toHaveLength(6); + expect(certificates[0].__typename).toBe(Cardano.CertificateType.StakeRegistration); + expect(certificates[1].__typename).toBe(Cardano.CertificateType.StakeDeregistration); + }); }); diff --git a/packages/projection/src/operators/Mappers/certificates/withStakeKeyRegistrations.ts b/packages/projection/src/operators/Mappers/certificates/withStakeKeyRegistrations.ts index b99960b88f4..0de7a8bf36a 100644 --- a/packages/projection/src/operators/Mappers/certificates/withStakeKeyRegistrations.ts +++ b/packages/projection/src/operators/Mappers/certificates/withStakeKeyRegistrations.ts @@ -19,10 +19,15 @@ export const withStakeKeyRegistrations = unifiedProjectorOperator { - if (certificate.__typename === Cardano.CertificateType.StakeRegistration) { + if ( + Cardano.StakeRegistrationCertificateTypes.includes( + certificate.__typename as Cardano.StakeRegistrationCertificateTypes + ) + ) { return { pointer, - stakeKeyHash: certificate.stakeCredential.hash as unknown as Ed25519KeyHashHex + stakeKeyHash: (certificate as Cardano.RegAndDeregCertificateUnion).stakeCredential + .hash as unknown as Ed25519KeyHashHex }; } return null; diff --git a/packages/projection/src/operators/Mappers/certificates/withStakeKeys.ts b/packages/projection/src/operators/Mappers/certificates/withStakeKeys.ts index 2cf2494acd3..e28170a809d 100644 --- a/packages/projection/src/operators/Mappers/certificates/withStakeKeys.ts +++ b/packages/projection/src/operators/Mappers/certificates/withStakeKeys.ts @@ -20,25 +20,34 @@ export interface WithStakeKeys { * The intended use case of this operator is to keep track of the current set of active stake keys, * ignoring **when** they were registered or unregistered. */ +// eslint-disable-next-line sonarjs/cognitive-complexity export const withStakeKeys = unifiedProjectorOperator((evt) => { const register = new Set(); const deregister = new Set(); for (const { certificate } of evt.certificates) { - switch (certificate.__typename) { - case Cardano.CertificateType.StakeRegistration: - if (deregister.has(certificate.stakeCredential.hash)) { - deregister.delete(certificate.stakeCredential.hash); - } else { - register.add(certificate.stakeCredential.hash); - } - break; - case Cardano.CertificateType.StakeDeregistration: - if (register.has(certificate.stakeCredential.hash)) { - register.delete(certificate.stakeCredential.hash); - } else { - deregister.add(certificate.stakeCredential.hash); - } - break; + if (Cardano.RegAndDeregCertificateTypes.includes(certificate.__typename as Cardano.RegAndDeregCertificateTypes)) { + const { + stakeCredential: { hash: stakeCredentialHash } + } = certificate as Cardano.RegAndDeregCertificateUnion; + + switch (certificate.__typename) { + case Cardano.CertificateType.StakeDeregistration: + case Cardano.CertificateType.Unregistration: + if (register.has(certificate.stakeCredential.hash)) { + register.delete(certificate.stakeCredential.hash); + } else { + deregister.add(certificate.stakeCredential.hash); + } + break; + default: + // Stake registration + if (deregister.has(stakeCredentialHash)) { + deregister.delete(stakeCredentialHash); + } else { + register.add(stakeCredentialHash); + } + break; + } } } const [insert, del] = diff --git a/packages/projection/test/operators/Mappers/certificates/withStakeKeyRegistrations.test.ts b/packages/projection/test/operators/Mappers/certificates/withStakeKeyRegistrations.test.ts index 01d0b6e39ea..2cf372c5080 100644 --- a/packages/projection/test/operators/Mappers/certificates/withStakeKeyRegistrations.test.ts +++ b/packages/projection/test/operators/Mappers/certificates/withStakeKeyRegistrations.test.ts @@ -6,7 +6,7 @@ import { firstValueFrom, of } from 'rxjs'; type EventData = Mappers.WithCertificates & { eventType: ChainSyncEventType }; describe('withStakeKeyRegistrations', () => { - it('collects all key registration certificates', async () => { + it.each(Cardano.StakeRegistrationCertificateTypes)('collects %s registration certificates', async (regCertType) => { const pointer: Cardano.Pointer = { certIndex: Cardano.CertIndex(1), slot: Cardano.Slot(123), @@ -16,12 +16,12 @@ describe('withStakeKeyRegistrations', () => { certificates: [ { certificate: { - __typename: Cardano.CertificateType.StakeRegistration, + __typename: regCertType, stakeCredential: { hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b'), type: Cardano.CredentialType.KeyHash } - }, + } as Cardano.Certificate, pointer }, { diff --git a/packages/projection/test/operators/Mappers/certificates/withStakeKeys.test.ts b/packages/projection/test/operators/Mappers/certificates/withStakeKeys.test.ts index 1f9ea7779fb..8e745bff866 100644 --- a/packages/projection/test/operators/Mappers/certificates/withStakeKeys.test.ts +++ b/packages/projection/test/operators/Mappers/certificates/withStakeKeys.test.ts @@ -7,39 +7,42 @@ type EventData = Mappers.WithCertificates & { eventType: ChainSyncEventType }; describe('withStakeKeys', () => { describe('1 certificate per stake key', () => { - it('collects all key registration and deregistration certificates', async () => { - const data: EventData = { - certificates: [ - { - certificate: { - __typename: Cardano.CertificateType.StakeRegistration, - stakeCredential: { - hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b'), - type: Cardano.CredentialType.KeyHash - } - }, - pointer: {} as Cardano.Pointer - }, - { - certificate: { - __typename: Cardano.CertificateType.StakeDeregistration, - stakeCredential: { - hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c'), - type: Cardano.CredentialType.KeyHash - } + it.each(Cardano.StakeRegistrationCertificateTypes)( + 'collects all key registration [%s] and deregistration certificates', + async (regCertType) => { + const data: EventData = { + certificates: [ + { + certificate: { + __typename: regCertType, + stakeCredential: { + hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b'), + type: Cardano.CredentialType.KeyHash + } + } as Cardano.Certificate, + pointer: {} as Cardano.Pointer }, - pointer: {} as Cardano.Pointer - } - ], - eventType: ChainSyncEventType.RollForward - }; + { + certificate: { + __typename: Cardano.CertificateType.StakeDeregistration, + stakeCredential: { + hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c'), + type: Cardano.CredentialType.KeyHash + } + }, + pointer: {} as Cardano.Pointer + } + ], + eventType: ChainSyncEventType.RollForward + }; - const result = await firstValueFrom( - Mappers.withStakeKeys()(of(data as UnifiedExtChainSyncEvent)) - ); - expect(result.stakeKeys.insert).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b']); - expect(result.stakeKeys.del).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c']); - }); + const result = await firstValueFrom( + Mappers.withStakeKeys()(of(data as UnifiedExtChainSyncEvent)) + ); + expect(result.stakeKeys.insert).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b']); + expect(result.stakeKeys.del).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c']); + } + ); it('reverses the logic on RollBackward', async () => { const data: EventData = { diff --git a/packages/wallet/src/services/DelegationTracker/DelegationTracker.ts b/packages/wallet/src/services/DelegationTracker/DelegationTracker.ts index 134d93adb35..ba206ce4e51 100644 --- a/packages/wallet/src/services/DelegationTracker/DelegationTracker.ts +++ b/packages/wallet/src/services/DelegationTracker/DelegationTracker.ts @@ -71,25 +71,13 @@ export const certificateTransactionsWithEpochs = ( ) ); -const hasDelegationCert = (certificates: Array | undefined): boolean => { - if (!certificates || certificates.length === 0) return false; - - return certificates.some((cert) => { - let hasCert = false; - - switch (cert.__typename) { - case Cardano.CertificateType.StakeDelegation: - case Cardano.CertificateType.StakeRegistration: - case Cardano.CertificateType.StakeDeregistration: - hasCert = true; - break; - default: - hasCert = false; - } - - return hasCert; - }); -}; +const hasDelegationCert = (certificates: Array | undefined): boolean => + !!certificates && + certificates.some((cert) => + [...Cardano.RegAndDeregCertificateTypes, ...Cardano.StakeDelegationCertificateTypes].includes( + cert.__typename as Cardano.RegAndDeregCertificateTypes | Cardano.StakeDelegationCertificateTypes + ) + ); export const createDelegationPortfolioTracker = (transactions: Observable) => transactions.pipe( @@ -153,11 +141,7 @@ export const createDelegationTracker = ({ transactionsTracker, rewardAccountAddresses$, slotEpochCalc$, - [ - Cardano.CertificateType.StakeDelegation, - Cardano.CertificateType.StakeRegistration, - Cardano.CertificateType.StakeDeregistration - ] + [...Cardano.RegAndDeregCertificateTypes, ...Cardano.StakeDelegationCertificateTypes] ).pipe(tap((transactionsWithEpochs) => logger.debug(`Found ${transactionsWithEpochs.length} staking transactions`))); const rewardsHistory$ = new TrackerSubject( diff --git a/packages/wallet/src/services/DelegationTracker/RewardAccounts.ts b/packages/wallet/src/services/DelegationTracker/RewardAccounts.ts index 007340479b0..a3b28685620 100644 --- a/packages/wallet/src/services/DelegationTracker/RewardAccounts.ts +++ b/packages/wallet/src/services/DelegationTracker/RewardAccounts.ts @@ -21,15 +21,11 @@ import { import { KeyValueStore } from '../../persistence'; import { OutgoingOnChainTx, TxInFlight } from '../types'; import { PAGE_SIZE } from '../TransactionsTracker'; -import { - RegAndDeregCertificateTypes, - includesAnyCertificate, - isLastStakeKeyCertOfType -} from './transactionCertificates'; import { RetryBackoffConfig } from 'backoff-rxjs'; import { TrackedStakePoolProvider } from '../ProviderTracker'; import { TxWithEpoch } from './types'; import { coldObservableProvider } from '@cardano-sdk/util-rxjs'; +import { isLastStakeKeyCertOfType } from './transactionCertificates'; import findLast from 'lodash/findLast'; import isEqual from 'lodash/isEqual'; import uniq from 'lodash/uniq'; @@ -128,8 +124,8 @@ export const createRewardsProvider = ); export type ObservableRewardsProvider = ReturnType; -const isDelegationCertificate = (cert: Cardano.Certificate): cert is Cardano.StakeDelegationCertificate => - cert.__typename === Cardano.CertificateType.StakeDelegation; +const isDelegationCertificate = (cert: Cardano.Certificate): cert is Cardano.StakeDelegationCertificateUnion => + Cardano.StakeDelegationCertificateTypes.includes(cert.__typename as Cardano.StakeDelegationCertificateTypes); const getAccountsKeyStatus = (addresses: Cardano.RewardAccount[]) => @@ -144,17 +140,17 @@ const getAccountsKeyStatus = } }) => certificates || [] ), - Cardano.CertificateType.StakeRegistration, + Cardano.StakeRegistrationCertificateTypes, address ); const isRegistering = isLastStakeKeyCertOfType( certificatesInFlight, - Cardano.CertificateType.StakeRegistration, + Cardano.StakeRegistrationCertificateTypes, address ); const isUnregistering = isLastStakeKeyCertOfType( certificatesInFlight, - Cardano.CertificateType.StakeDeregistration, + [Cardano.CertificateType.StakeDeregistration, Cardano.CertificateType.Unregistration], address ); return isRegistering @@ -177,8 +173,10 @@ const accountCertificateTransactions = ( transactions .map(({ tx, epoch }) => ({ certificates: (tx.body.certificates || []) - .filter((cert): cert is Cardano.StakeDelegationCertificate | Cardano.StakeAddressCertificate => - [...RegAndDeregCertificateTypes, Cardano.CertificateType.StakeDelegation].includes(cert.__typename) + .filter((cert): cert is Cardano.RegAndDeregCertificateUnion | Cardano.StakeDelegationCertificateUnion => + [...Cardano.RegAndDeregCertificateTypes, ...Cardano.StakeDelegationCertificateTypes].includes( + cert.__typename as Cardano.RegAndDeregCertificateTypes | Cardano.StakeDelegationCertificateTypes + ) ) .filter((cert) => (cert.stakeCredential.hash as unknown as Crypto.Ed25519KeyHashHex) === stakeKeyHash), epoch @@ -192,13 +190,26 @@ const accountCertificateTransactions = ( type ObservableType = O extends Observable ? T : unknown; type TransactionsCertificates = ObservableType>; +/** + * Check if the stake key was registered and is delegated, and return the pool ID. + * A stake key is considered delegated 3 epochs after the certificate was sent. + * + * @returns + * - the stake pool ID that is delegated to at the given epoch. + * - undefined if the stake key was not registered. + * Returns the stake pool ID that is delegated to at the given epoch. + * If the stake key was not registered, it returns undefined. + */ export const getStakePoolIdAtEpoch = (transactions: TransactionsCertificates) => (atEpoch: Cardano.EpochNo) => { const certificatesUpToEpoch = transactions .filter(({ epoch }) => epoch < atEpoch - 2) .map(({ certificates }) => certificates); - if (!isLastStakeKeyCertOfType(certificatesUpToEpoch, Cardano.CertificateType.StakeRegistration)) return; + if (!isLastStakeKeyCertOfType(certificatesUpToEpoch, Cardano.StakeRegistrationCertificateTypes)) { + return; + } + const delegationTxCertificates = findLast(certificatesUpToEpoch, (certs) => - includesAnyCertificate(certs, [Cardano.CertificateType.StakeDelegation]) + Cardano.includesAnyCertificate(certs, Cardano.StakeDelegationCertificateTypes) ); if (!delegationTxCertificates) return; return findLast(delegationTxCertificates.filter(isDelegationCertificate))?.poolId; diff --git a/packages/wallet/src/services/DelegationTracker/RewardsHistory.ts b/packages/wallet/src/services/DelegationTracker/RewardsHistory.ts index 2616c230ef0..320769c9eab 100644 --- a/packages/wallet/src/services/DelegationTracker/RewardsHistory.ts +++ b/packages/wallet/src/services/DelegationTracker/RewardsHistory.ts @@ -48,7 +48,7 @@ const firstDelegationEpoch$ = (transactions$: Observable, rewardA map((transactions) => first( transactions.filter( - ({ tx }) => getCertificatesByType(tx, rewardAccounts, [Cardano.CertificateType.StakeDelegation]).length > 0 + ({ tx }) => getCertificatesByType(tx, rewardAccounts, Cardano.StakeDelegationCertificateTypes).length > 0 ) ) ), diff --git a/packages/wallet/src/services/DelegationTracker/transactionCertificates.ts b/packages/wallet/src/services/DelegationTracker/transactionCertificates.ts index c6a9e040d62..f9e8c3ef58e 100644 --- a/packages/wallet/src/services/DelegationTracker/transactionCertificates.ts +++ b/packages/wallet/src/services/DelegationTracker/transactionCertificates.ts @@ -5,22 +5,9 @@ import { isNotNil } from '@cardano-sdk/util'; import { transactionsEquals } from '../util/equals'; import last from 'lodash/last'; -export const RegAndDeregCertificateTypes = [ - Cardano.CertificateType.StakeRegistration, - Cardano.CertificateType.StakeDeregistration -]; - -export const stakeKeyCertficates = (certificates?: Cardano.Certificate[]) => - certificates?.filter((certificate): certificate is Cardano.StakeAddressCertificate => - RegAndDeregCertificateTypes.includes(certificate.__typename) - ) || []; - -export const includesAnyCertificate = (haystack: Cardano.Certificate[], needle: Cardano.CertificateType[]) => - haystack.some(({ __typename }) => needle.includes(__typename)) || false; - export const isLastStakeKeyCertOfType = ( transactionsCertificates: Cardano.Certificate[][], - certType: Cardano.CertificateType.StakeRegistration | Cardano.CertificateType.StakeDeregistration, + certTypes: readonly Cardano.RegAndDeregCertificateTypes[], rewardAccount?: Cardano.RewardAccount ) => { const stakeKeyHash = rewardAccount @@ -29,7 +16,7 @@ export const isLastStakeKeyCertOfType = ( const lastRegOrDereg = last( transactionsCertificates .map((certificates) => { - const allStakeKeyCertificates = stakeKeyCertficates(certificates); + const allStakeKeyCertificates = Cardano.stakeKeyCertificates(certificates); const addressStakeKeyCertificates = stakeKeyHash ? allStakeKeyCertificates.filter(({ stakeCredential: certStakeCred }) => stakeKeyHash === certStakeCred.hash) : allStakeKeyCertificates; @@ -37,7 +24,7 @@ export const isLastStakeKeyCertOfType = ( }) .filter(isNotNil) ); - return lastRegOrDereg?.__typename === certType; + return certTypes.includes(lastRegOrDereg?.__typename as Cardano.RegAndDeregCertificateTypes); }; export const transactionsWithCertificates = ( diff --git a/packages/wallet/test/services/DelegationTracker/DelegationTracker.test.ts b/packages/wallet/test/services/DelegationTracker/DelegationTracker.test.ts index e7809262059..2c69b2ab604 100644 --- a/packages/wallet/test/services/DelegationTracker/DelegationTracker.test.ts +++ b/packages/wallet/test/services/DelegationTracker/DelegationTracker.test.ts @@ -211,7 +211,8 @@ describe('DelegationTracker', () => { 285, [ { - __typename: Cardano.CertificateType.StakeRegistration, + __typename: Cardano.CertificateType.Registration, + deposit: 2_000_000n, stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash @@ -237,7 +238,8 @@ describe('DelegationTracker', () => { 285, [ { - __typename: Cardano.CertificateType.StakeRegistration, + __typename: Cardano.CertificateType.Registration, + deposit: 2_000_000n, stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash @@ -272,7 +274,8 @@ describe('DelegationTracker', () => { 285, [ { - __typename: Cardano.CertificateType.StakeRegistration, + __typename: Cardano.CertificateType.Registration, + deposit: 2_000_000n, stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash @@ -296,7 +299,11 @@ describe('DelegationTracker', () => { 287, [ { - __typename: Cardano.CertificateType.StakeRegistration, + __typename: Cardano.CertificateType.VoteRegistrationDelegation, + dRep: { + __typename: 'AlwaysAbstain' + }, + deposit: 2_000_000n, stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash @@ -344,9 +351,29 @@ describe('DelegationTracker', () => { ) ], b: [ + createStubTxWithSlot( + 284, + [ + { + __typename: Cardano.CertificateType.StakeRegistration, + stakeCredential: { + hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), + type: Cardano.CredentialType.KeyHash + } + } + ], + { + blob: new Map([[Cardano.DelegationMetadataLabel, metadatum.jsonToMetadatum(cip17DelegationPortfolio)]]) + } + ), createStubTxWithSlot(286, [ { - __typename: Cardano.CertificateType.StakeRegistration, + __typename: Cardano.CertificateType.StakeVoteRegistrationDelegation, + dRep: { + __typename: 'AlwaysAbstain' + }, + deposit: 2_000_000n, + poolId: 'abc' as Cardano.PoolId, stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash @@ -375,7 +402,12 @@ describe('DelegationTracker', () => { 284, [ { - __typename: Cardano.CertificateType.StakeRegistration, + __typename: Cardano.CertificateType.StakeVoteDelegation, + dRep: { + __typename: 'AlwaysAbstain' + }, + poolId: 'abc' as Cardano.PoolId, + stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash @@ -388,6 +420,21 @@ describe('DelegationTracker', () => { ) ], b: [ + createStubTxWithSlot( + 284, + [ + { + __typename: Cardano.CertificateType.StakeRegistration, + stakeCredential: { + hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), + type: Cardano.CredentialType.KeyHash + } + } + ], + { + blob: new Map([[Cardano.DelegationMetadataLabel, metadatum.jsonToMetadatum(cip17DelegationPortfolio)]]) + } + ), createStubTxWithSlot(289, undefined, { blob: new Map([ [Cardano.DelegationMetadataLabel, metadatum.jsonToMetadatum(cip17DelegationPortfolioChangeWeights)] diff --git a/packages/wallet/test/services/DelegationTracker/RewardAccounts.test.ts b/packages/wallet/test/services/DelegationTracker/RewardAccounts.test.ts index c88407c631c..4f78ead9da5 100644 --- a/packages/wallet/test/services/DelegationTracker/RewardAccounts.test.ts +++ b/packages/wallet/test/services/DelegationTracker/RewardAccounts.test.ts @@ -35,7 +35,6 @@ jest.mock('@cardano-sdk/util-rxjs', () => { }; }); - describe('RewardAccounts', () => { const coldObservableProviderMock = coldObservableProvider as jest.MockedFunction; const txId1 = Cardano.TransactionId('0000000000000000000000000000000000000000000000000000000000000000'); @@ -89,22 +88,23 @@ describe('RewardAccounts', () => { expect(store.getValues).toHaveBeenCalledWith([poolId1, poolId2]); }); - test('getStakePoolIdAtEpoch ', () => { + test('getStakePoolIdAtEpoch', () => { const transactions = [ { certificates: [{ __typename: Cardano.CertificateType.StakeRegistration } as Cardano.StakeAddressCertificate], epoch: Cardano.EpochNo(100) }, { - certificates: [{ - __typename: Cardano.CertificateType.StakeDelegation, poolId: poolId1 - } as Cardano.StakeDelegationCertificate], + certificates: [ + { + __typename: Cardano.CertificateType.StakeDelegation, + poolId: poolId1 + } as Cardano.StakeDelegationCertificate + ], epoch: Cardano.EpochNo(101) }, { - certificates: [ - { __typename: Cardano.CertificateType.StakeDeregistration } as Cardano.StakeAddressCertificate - ], + certificates: [{ __typename: Cardano.CertificateType.StakeDeregistration } as Cardano.StakeAddressCertificate], epoch: Cardano.EpochNo(102) }, { @@ -121,7 +121,107 @@ describe('RewardAccounts', () => { expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(106))).toBeUndefined(); }); - test('addressKeyStatuses ', () => { + test('getStakePoolIdAtEpoch Conway era', () => { + const transactions = [ + { + certificates: [{ __typename: Cardano.CertificateType.Registration } as Cardano.NewStakeAddressCertificate], + epoch: Cardano.EpochNo(100) + }, + { + certificates: [ + { + __typename: Cardano.CertificateType.StakeDelegation, + poolId: poolId1 + } as Cardano.StakeDelegationCertificate + ], + epoch: Cardano.EpochNo(101) + }, + // Unregister stake key + // Register stake key with vote_reg_deleg_cert + // Delegate to pool with stake_vote_deleg_cert + { + certificates: [{ __typename: Cardano.CertificateType.Unregistration } as Cardano.NewStakeAddressCertificate], + epoch: Cardano.EpochNo(102) + }, + { + certificates: [ + { + __typename: Cardano.CertificateType.VoteRegistrationDelegation + } as Cardano.VoteRegistrationDelegationCertificate], + epoch: Cardano.EpochNo(103) + }, + { + certificates: [ + { + __typename: Cardano.CertificateType.StakeVoteDelegation, + poolId: poolId2 + } as Cardano.StakeVoteDelegationCertificate + ], + epoch: Cardano.EpochNo(104) + }, + // Unregister stake key + // Register stake key and delegate with stake_reg_deleg_cert + { + certificates: [{ __typename: Cardano.CertificateType.Unregistration } as Cardano.NewStakeAddressCertificate], + epoch: Cardano.EpochNo(105) + }, + { + certificates: [ + { + __typename: Cardano.CertificateType.StakeRegistrationDelegation, + poolId: poolId1 + } as Cardano.StakeRegistrationDelegationCertificate], + epoch: Cardano.EpochNo(106) + }, + // Unregister stake key + // Register stake key and delegate with stake_vote_reg_deleg_cert + { + certificates: [{ __typename: Cardano.CertificateType.Unregistration } as Cardano.NewStakeAddressCertificate], + epoch: Cardano.EpochNo(107) + }, + { + certificates: [ + { + __typename: Cardano.CertificateType.StakeVoteRegistrationDelegation, + poolId: poolId2 + } as Cardano.StakeVoteRegistrationDelegationCertificate], + epoch: Cardano.EpochNo(108) + }, + // Delegation ignored after stake key is unregistered + { + certificates: [{ __typename: Cardano.CertificateType.Unregistration } as Cardano.NewStakeAddressCertificate], + epoch: Cardano.EpochNo(109) + }, + { + certificates: [ + { __typename: Cardano.CertificateType.StakeDelegation, poolId: poolId1 } as Cardano.StakeDelegationCertificate + ], + epoch: Cardano.EpochNo(110) + } + ]; + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(102))).toBeUndefined(); + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(103))).toBeUndefined(); + // PoolId is available 3 epochs after delegation + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(104))).toBe(poolId1); + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(105))).toBeUndefined(); + // Stake key is registered and delegated using VoteRegistrationDelegationCertificate and StakeVoteDelegation + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(107))).toBe(poolId2); + // Stake key is registered and delegated using StakeRegistrationDelegationCertificate + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(109))).toBe(poolId1); + // Stake key is registered and delegated using StakeVoteRegistrationDelegationCertificate + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(111))).toBe(poolId2); + // New delegation has no effect due to stake key being unregistered + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(112))).toBeUndefined(); + expect(getStakePoolIdAtEpoch(transactions)(Cardano.EpochNo(113))).toBeUndefined(); + }); + + test.each([ + Cardano.CertificateType.Registration, + Cardano.CertificateType.StakeRegistration, + Cardano.CertificateType.StakeRegistrationDelegation, + Cardano.CertificateType.StakeVoteRegistrationDelegation, + Cardano.CertificateType.VoteRegistrationDelegation + ])('addressKeyStatuses %p', (registrationCertType) => { createTestScheduler().run(({ cold, expectObservable }) => { const rewardAccount = Cardano.RewardAccount('stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27'); const stakeKeyHash = Cardano.RewardAccount.toHash(rewardAccount); @@ -131,13 +231,16 @@ describe('RewardAccounts', () => { { tx: { body: { - certificates: [{ - __typename: Cardano.CertificateType.StakeRegistration, - stakeCredential: { - hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), - type: Cardano.CredentialType.KeyHash + certificates: [ + { + __typename: registrationCertType, + deposit: 0n, + stakeCredential: { + hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), + type: Cardano.CredentialType.KeyHash + } } - }] + ] } } } as TxWithEpoch @@ -146,26 +249,32 @@ describe('RewardAccounts', () => { { tx: { body: { - certificates: [{ - __typename: Cardano.CertificateType.StakeRegistration, - stakeCredential: { - hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), - type: Cardano.CredentialType.KeyHash + certificates: [ + { + __typename: registrationCertType, + deposit: 0n, + stakeCredential: { + hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), + type: Cardano.CredentialType.KeyHash + } } - }] + ] } } } as TxWithEpoch, { tx: { body: { - certificates: [{ - __typename: Cardano.CertificateType.StakeDeregistration, - stakeCredential: { - hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), - type: Cardano.CredentialType.KeyHash + certificates: [ + { + __typename: Cardano.CertificateType.Unregistration, + deposit: 0n, + stakeCredential: { + hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), + type: Cardano.CredentialType.KeyHash + } } - }] + ] } } } as TxWithEpoch @@ -176,11 +285,16 @@ describe('RewardAccounts', () => { b: [ { body: { - certificates: [{ __typename: Cardano.CertificateType.StakeRegistration, - stakeCredential: { - hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), - type: Cardano.CredentialType.KeyHash - } }] + certificates: [ + { + __typename: registrationCertType, + deposit: 0n, + stakeCredential: { + hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), + type: Cardano.CredentialType.KeyHash + } + } + ] } as Cardano.TxBody, cbor: dummyCbor, id: txId1 @@ -189,11 +303,16 @@ describe('RewardAccounts', () => { c: [ { body: { - certificates: [{ __typename: Cardano.CertificateType.StakeDeregistration, - stakeCredential: { - hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), - type: Cardano.CredentialType.KeyHash - } }] + certificates: [ + { + __typename: Cardano.CertificateType.Unregistration, + deposit: 0n, + stakeCredential: { + hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(stakeKeyHash), + type: Cardano.CredentialType.KeyHash + } + } + ] } as Cardano.TxBody, cbor: dummyCbor, id: txId2 @@ -224,20 +343,26 @@ describe('RewardAccounts', () => { // even if more (unrelated) transactions get discovered on-chain const transactionsInFlight$ = hot('a-b--a--b-aaa', { a: [], - b: [{ - body: { - withdrawals: [{ - quantity: acc1PendingWithdrawalQty, stakeAddress: twoRewardAccounts[0] - } as Cardano.Withdrawal] - } as Cardano.TxBody, - cbor: dummyCbor, - id: txId1 - }] - }); - const rewardsProvider = () => hot('-a--b-a--b---a', { - a: [acc1Balance1, acc2Balance], - b: [acc1Balance2, acc2Balance] + b: [ + { + body: { + withdrawals: [ + { + quantity: acc1PendingWithdrawalQty, + stakeAddress: twoRewardAccounts[0] + } as Cardano.Withdrawal + ] + } as Cardano.TxBody, + cbor: dummyCbor, + id: txId1 + } + ] }); + const rewardsProvider = () => + hot('-a--b-a--b---a', { + a: [acc1Balance1, acc2Balance], + b: [acc1Balance2, acc2Balance] + }); const balancesStore = { getValues(_: Cardano.RewardAccount[]) { return cold('(a|)', { a: storedBalances }) as Observable; @@ -247,7 +372,10 @@ describe('RewardAccounts', () => { } } as KeyValueStore; const addressRewards$ = addressRewards( - twoRewardAccounts, transactionsInFlight$, rewardsProvider, balancesStore + twoRewardAccounts, + transactionsInFlight$, + rewardsProvider, + balancesStore ); expectObservable(addressRewards$).toBe('abc-d-b-cd---b', { a: storedBalances, @@ -266,15 +394,20 @@ describe('RewardAccounts', () => { const acc1PendingWithdrawalQty = 1_000_000n; const transactionsInFlightEmits: Record = { x: [], - y: [{ - body: { - withdrawals: [{ - quantity: acc1PendingWithdrawalQty, stakeAddress: twoRewardAccounts[0] - } as Cardano.Withdrawal] - } as Cardano.TxBody, - cbor: dummyCbor, - id: txId1 - }] + y: [ + { + body: { + withdrawals: [ + { + quantity: acc1PendingWithdrawalQty, + stakeAddress: twoRewardAccounts[0] + } as Cardano.Withdrawal + ] + } as Cardano.TxBody, + cbor: dummyCbor, + id: txId1 + } + ] }; const rewardsProviderEmits = { a: [accBalance1], @@ -294,7 +427,10 @@ describe('RewardAccounts', () => { } } as KeyValueStore; const addressRewards$ = addressRewards( - twoRewardAccounts, transactionsInFlight$, rewardsProvider, balancesStore + twoRewardAccounts, + transactionsInFlight$, + rewardsProvider, + balancesStore ); expectObservable(addressRewards$).toBe(expectedFrames, { m: [accBalance1 - acc1PendingWithdrawalQty], @@ -305,17 +441,18 @@ describe('RewardAccounts', () => { }); }); - describe('fetchRewardsTrigger$', () => { it('emits every epoch and after making a transaction with withdrawals', () => { const rewardAccount = Cardano.RewardAccount('stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27'); createTestScheduler().run(({ cold, expectObservable }) => { const onChainTx1: OutgoingOnChainTx = { body: { - withdrawals: [{ - quantity: 3n, - stakeAddress: Cardano.RewardAccount('stake_test1up7pvfq8zn4quy45r2g572290p9vf99mr9tn7r9xrgy2l2qdsf58d') - }] + withdrawals: [ + { + quantity: 3n, + stakeAddress: Cardano.RewardAccount('stake_test1up7pvfq8zn4quy45r2g572290p9vf99mr9tn7r9xrgy2l2qdsf58d') + } + ] } as Cardano.TxBody, cbor: dummyCbor, id: txId1, @@ -327,14 +464,16 @@ describe('RewardAccounts', () => { id: txId2, slot: Cardano.Slot(2) }; - const epoch$ = cold( 'a-b--', { a: Cardano.EpochNo(100), b: Cardano.EpochNo(101) }); + const epoch$ = cold('a-b--', { a: Cardano.EpochNo(100), b: Cardano.EpochNo(101) }); const txConfirmed$ = cold('-a--b', { a: onChainTx1, b: onChainTx2 }); const target$ = fetchRewardsTrigger$(epoch$, txConfirmed$, rewardAccount); expectObservable(target$).toBe('a-b-c', { - a: 100, b: 101, c: 5n + a: 100, + b: 101, + c: 5n }); }); }); @@ -359,12 +498,7 @@ describe('RewardAccounts', () => { a: 3n }) ); - const target$ = createRewardsProvider( - epoch$, - onChainTx$, - rewardsProvider, - config - )(twoRewardAccounts); + const target$ = createRewardsProvider(epoch$, onChainTx$, rewardsProvider, config)(twoRewardAccounts); expectObservable(target$).toBe('-ab-c', { a: [0n, 3n], b: [5n, 3n], @@ -378,7 +512,7 @@ describe('RewardAccounts', () => { describe('createDelegateeTracker', () => { it('queries and maps stake pools for epoch, epoch+1 and epoch+2', () => { createTestScheduler().run(({ cold, expectObservable, flush }) => { - const epoch = Cardano.EpochNo(currentEpoch.number); + const epoch = Cardano.EpochNo(currentEpoch.number); const epoch$ = cold('-a', { a: epoch }); const stakePoolQueryResult = [{ id: poolId1 }, { id: poolId2 }]; const stakePoolProvider = jest.fn().mockReturnValue(cold('-a', { a: stakePoolQueryResult })); diff --git a/packages/wallet/test/services/DelegationTracker/RewardsHistory.test.ts b/packages/wallet/test/services/DelegationTracker/RewardsHistory.test.ts index 67592f7f06a..5fd57e82de4 100644 --- a/packages/wallet/test/services/DelegationTracker/RewardsHistory.test.ts +++ b/packages/wallet/test/services/DelegationTracker/RewardsHistory.test.ts @@ -43,113 +43,110 @@ describe('RewardsHistory', () => { }); describe('createRewardsHistoryTracker', () => { - it('queries and maps reward history starting from first delegation epoch+2', () => { - createTestScheduler().run(({ cold, expectObservable, flush }) => { - const accountRewardsHistory = rewardsHistory.get(rewardAccount)!; - const epoch = accountRewardsHistory[0].epoch; - const getRewardsHistory = jest.fn().mockReturnValue(cold('-a', { a: rewardsHistory })); - const target$ = createRewardsHistoryTracker( - cold('aa', { - a: [ - { - epoch: Cardano.EpochNo(0), - tx: createStubTxWithCertificates([ - { __typename: Cardano.CertificateType.StakeDeregistration } as Cardano.Certificate - ]) - }, - { - epoch, - tx: createStubTxWithCertificates( - [{ __typename: Cardano.CertificateType.StakeDelegation } as Cardano.Certificate], - { + it.each(Cardano.StakeDelegationCertificateTypes)( + 'queries and maps reward history starting from first delegation epoch+2 with %s', + (delegationCertificateType) => { + createTestScheduler().run(({ cold, expectObservable, flush }) => { + const accountRewardsHistory = rewardsHistory.get(rewardAccount)!; + const epoch = accountRewardsHistory[0].epoch; + const getRewardsHistory = jest.fn().mockReturnValue(cold('-a', { a: rewardsHistory })); + const target$ = createRewardsHistoryTracker( + cold('aa', { + a: [ + { + epoch: Cardano.EpochNo(0), + tx: createStubTxWithCertificates([ + { __typename: Cardano.CertificateType.StakeDeregistration } as Cardano.Certificate + ]) + }, + { + epoch, + tx: createStubTxWithCertificates([{ __typename: delegationCertificateType } as Cardano.Certificate], { stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash } - } - ) - } - ] - }), - of(rewardAccounts), - getRewardsHistory, - new InMemoryRewardsHistoryStore(), - logger - ); - expectObservable(target$).toBe('-a', { - a: { - all: accountRewardsHistory, - avgReward: 10_500n, - lastReward: accountRewardsHistory[1], - lifetimeRewards: 21_000n - } as RewardsHistory + }) + } + ] + }), + of(rewardAccounts), + getRewardsHistory, + new InMemoryRewardsHistoryStore(), + logger + ); + expectObservable(target$).toBe('-a', { + a: { + all: accountRewardsHistory, + avgReward: 10_500n, + lastReward: accountRewardsHistory[1], + lifetimeRewards: 21_000n + } as RewardsHistory + }); + flush(); + expect(getRewardsHistory).toBeCalledTimes(1); + expect(getRewardsHistory).toBeCalledWith( + rewardAccounts, + Cardano.EpochNo(calcFirstDelegationEpoch(epoch)), + undefined + ); }); - flush(); - expect(getRewardsHistory).toBeCalledTimes(1); - expect(getRewardsHistory).toBeCalledWith( - rewardAccounts, - Cardano.EpochNo(calcFirstDelegationEpoch(epoch)), - undefined - ); - }); - }); + } + ); - it('considers only first delegation signed by the reward account', () => { - createTestScheduler().run(({ cold, expectObservable, flush }) => { - const accountRewardsHistory = rewardsHistory.get(rewardAccount)!; - const epoch = accountRewardsHistory[0].epoch; - const getRewardsHistory = jest.fn().mockReturnValue(cold('-a', { a: rewardsHistory })); - const target$ = createRewardsHistoryTracker( - cold('aa', { - a: [ - { - epoch: Cardano.EpochNo(0), - tx: createStubTxWithCertificates( - [{ __typename: Cardano.CertificateType.StakeDelegation } as Cardano.Certificate], - { + it.each(Cardano.StakeDelegationCertificateTypes)( + 'considers only first delegation signed by the reward account with %s', + (delegationCertificateType) => { + createTestScheduler().run(({ cold, expectObservable, flush }) => { + const accountRewardsHistory = rewardsHistory.get(rewardAccount)!; + const epoch = accountRewardsHistory[0].epoch; + const getRewardsHistory = jest.fn().mockReturnValue(cold('-a', { a: rewardsHistory })); + const target$ = createRewardsHistoryTracker( + cold('aa', { + a: [ + { + epoch: Cardano.EpochNo(0), + tx: createStubTxWithCertificates([{ __typename: delegationCertificateType } as Cardano.Certificate], { stakeCredential: { hash: Crypto.Hash28ByteBase16('00000000000000000000000000000000000000000000000000000000'), type: Cardano.CredentialType.KeyHash } - } - ) - }, - { - epoch, - tx: createStubTxWithCertificates( - [{ __typename: Cardano.CertificateType.StakeDelegation } as Cardano.Certificate], - { + }) + }, + { + epoch, + tx: createStubTxWithCertificates([{ __typename: delegationCertificateType } as Cardano.Certificate], { stakeCredential: { hash: Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(Cardano.RewardAccount.toHash(rewardAccount)), type: Cardano.CredentialType.KeyHash } - } - ) - } - ] - }), - of(rewardAccounts), - getRewardsHistory, - new InMemoryRewardsHistoryStore(), - logger - ); - expectObservable(target$).toBe('-a', { - a: { - all: accountRewardsHistory, - avgReward: 10_500n, - lastReward: accountRewardsHistory[1], - lifetimeRewards: 21_000n - } as RewardsHistory + }) + } + ] + }), + of(rewardAccounts), + getRewardsHistory, + new InMemoryRewardsHistoryStore(), + logger + ); + expectObservable(target$).toBe('-a', { + a: { + all: accountRewardsHistory, + avgReward: 10_500n, + lastReward: accountRewardsHistory[1], + lifetimeRewards: 21_000n + } as RewardsHistory + }); + flush(); + expect(getRewardsHistory).toBeCalledTimes(1); + expect(getRewardsHistory).toBeCalledWith( + rewardAccounts, + Cardano.EpochNo(calcFirstDelegationEpoch(epoch)), + undefined + ); }); - flush(); - expect(getRewardsHistory).toBeCalledTimes(1); - expect(getRewardsHistory).toBeCalledWith( - rewardAccounts, - Cardano.EpochNo(calcFirstDelegationEpoch(epoch)), - undefined - ); - }); - }); + } + ); it.todo('emits value from store if it exists and updates store after provider response'); }); diff --git a/packages/wallet/test/services/DelegationTracker/transactionCertificates.test.ts b/packages/wallet/test/services/DelegationTracker/transactionCertificates.test.ts index 30009ab84cb..ab340e3e34f 100644 --- a/packages/wallet/test/services/DelegationTracker/transactionCertificates.test.ts +++ b/packages/wallet/test/services/DelegationTracker/transactionCertificates.test.ts @@ -1,20 +1,9 @@ import * as Crypto from '@cardano-sdk/crypto'; import { Cardano } from '@cardano-sdk/core'; import { createTestScheduler } from '@cardano-sdk/util-dev'; -import { isLastStakeKeyCertOfType, stakeKeyCertficates, transactionsWithCertificates } from '../../../src'; +import { isLastStakeKeyCertOfType, transactionsWithCertificates } from '../../../src'; describe('transactionCertificates', () => { - test('transactionStakeKeyCertficates', () => { - const certificates = stakeKeyCertficates([ - { __typename: Cardano.CertificateType.StakeDelegation } as Cardano.Certificate, - { __typename: Cardano.CertificateType.StakeRegistration } as Cardano.Certificate, - { __typename: Cardano.CertificateType.StakeDeregistration } as Cardano.Certificate - ]); - expect(certificates).toHaveLength(2); - expect(certificates[0].__typename).toBe(Cardano.CertificateType.StakeRegistration); - expect(certificates[1].__typename).toBe(Cardano.CertificateType.StakeDeregistration); - }); - test('isLastStakeKeyCertOfType', () => { const rewardAccount = Cardano.RewardAccount('stake_test1up7pvfq8zn4quy45r2g572290p9vf99mr9tn7r9xrgy2l2qdsf58d'); const stakeKeyHash = Cardano.RewardAccount.toHash(rewardAccount); @@ -45,9 +34,11 @@ describe('transactionCertificates', () => { } as Cardano.Certificate) ] ]; - expect(isLastStakeKeyCertOfType(certificates, Cardano.CertificateType.StakeRegistration)).toBe(false); - expect(isLastStakeKeyCertOfType(certificates, Cardano.CertificateType.StakeRegistration, rewardAccount)).toBe(true); - expect(isLastStakeKeyCertOfType(certificates, Cardano.CertificateType.StakeDeregistration)).toBe(true); + expect(isLastStakeKeyCertOfType(certificates, [Cardano.CertificateType.StakeRegistration])).toBe(false); + expect(isLastStakeKeyCertOfType(certificates, [Cardano.CertificateType.StakeRegistration], rewardAccount)).toBe( + true + ); + expect(isLastStakeKeyCertOfType(certificates, [Cardano.CertificateType.StakeDeregistration])).toBe(true); }); test('outgoingTransactionsWithCertificates', () => {