Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle *_DELEGATE Transaction Service events #2106

Merged
merged 6 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/datasources/cache/cache.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ export class CacheRouter {
return `${args.chainId}_${CacheRouter.SAFE_COLLECTIBLES_KEY}_${args.safeAddress}`;
}

static getDelegatesCacheKey(args: {
chainId: string;
safeAddress?: `0x${string}`;
}): string {
return `${args.chainId}_${CacheRouter.DELEGATES_KEY}_${args.safeAddress}`;
}

static getDelegatesCacheDir(args: {
chainId: string;
safeAddress?: `0x${string}`;
Expand All @@ -209,7 +216,7 @@ export class CacheRouter {
offset?: number;
}): CacheDir {
return new CacheDir(
`${args.chainId}_${CacheRouter.DELEGATES_KEY}_${args.safeAddress}`,
CacheRouter.getDelegatesCacheKey(args),
`${args.delegate}_${args.delegator}_${args.label}_${args.limit}_${args.offset}`,
);
}
Expand Down
8 changes: 8 additions & 0 deletions src/datasources/transaction-api/transaction-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,14 @@ export class TransactionApi implements ITransactionApi {
}
}

async clearDelegates(safeAddress?: `0x${string}`): Promise<void> {
const cacheKey = CacheRouter.getDelegatesCacheKey({
chainId: this.chainId,
safeAddress,
});
await this.cacheService.deleteByKey(cacheKey);
}

async postDelegate(args: {
safeAddress: `0x${string}` | null;
delegate: `0x${string}`;
Expand Down
5 changes: 5 additions & 0 deletions src/domain/delegate/v2/delegates.v2.repository.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface IDelegatesV2Repository {
offset?: number;
}): Promise<Page<Delegate>>;

clearDelegates(args: {
chainId: string;
safeAddress?: string;
}): Promise<void>;

postDelegate(args: {
chainId: string;
safeAddress: `0x${string}` | null;
Expand Down
10 changes: 10 additions & 0 deletions src/domain/delegate/v2/delegates.v2.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ export class DelegatesV2Repository implements IDelegatesV2Repository {
return DelegatePageSchema.parse(page);
}

async clearDelegates(args: {
chainId: string;
safeAddress?: `0x${string}`;
}): Promise<void> {
const transactionService = await this.transactionApiManager.getApi(
args.chainId,
);
await transactionService.clearDelegates(args.safeAddress);
}

async postDelegate(args: {
chainId: string;
safeAddress: `0x${string}` | null;
Expand Down
2 changes: 2 additions & 0 deletions src/domain/hooks/helpers/event-cache.helper.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import { SafeAppsRepositoryModule } from '@/domain/safe-apps/safe-apps.repositor
import { SafeRepositoryModule } from '@/domain/safe/safe.repository.interface';
import { StakingRepositoryModule } from '@/domain/staking/staking.repository.module';
import { TransactionsRepositoryModule } from '@/domain/transactions/transactions.repository.interface';
import { DelegatesV2RepositoryModule } from '@/domain/delegate/v2/delegates.v2.repository.interface';

@Module({
imports: [
BalancesRepositoryModule,
BlockchainRepositoryModule,
ChainsRepositoryModule,
CollectiblesRepositoryModule,
DelegatesV2RepositoryModule,
MessagesRepositoryModule,
SafeAppsRepositoryModule,
SafeRepositoryModule,
Expand Down
65 changes: 62 additions & 3 deletions src/domain/hooks/helpers/event-cache.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IBalancesRepository } from '@/domain/balances/balances.repository.inter
import { IBlockchainRepository } from '@/domain/blockchain/blockchain.repository.interface';
import { IChainsRepository } from '@/domain/chains/chains.repository.interface';
import { ICollectiblesRepository } from '@/domain/collectibles/collectibles.repository.interface';
import { IDelegatesV2Repository } from '@/domain/delegate/v2/delegates.v2.repository.interface';
import { IMessagesRepository } from '@/domain/messages/messages.repository.interface';
import { ISafeAppsRepository } from '@/domain/safe-apps/safe-apps.repository.interface';
import { ISafeRepository } from '@/domain/safe/safe.repository.interface';
Expand Down Expand Up @@ -40,6 +41,8 @@ export class EventCacheHelper {
private readonly chainsRepository: IChainsRepository,
@Inject(ICollectiblesRepository)
private readonly collectiblesRepository: ICollectiblesRepository,
@Inject(IDelegatesV2Repository)
private readonly delegatesRepository: IDelegatesV2Repository,
@Inject(IMessagesRepository)
private readonly messagesRepository: IMessagesRepository,
@Inject(ISafeAppsRepository)
Expand Down Expand Up @@ -91,6 +94,12 @@ export class EventCacheHelper {
[TransactionEventType.REORG_DETECTED]: () => [],
[TransactionEventType.SAFE_CREATED]:
this.onTransactionEventSafeCreated.bind(this),
[TransactionEventType.NEW_DELEGATE]:
this.onTransactionEventDelegate.bind(this),
[TransactionEventType.DELETED_DELEGATE]:
this.onTransactionEventDelegate.bind(this),
[TransactionEventType.UPDATED_DELEGATE]:
this.onTransactionEventDelegate.bind(this),
[ConfigEventType.CHAIN_UPDATE]: this.onConfigEventChainUpdate.bind(this),
[ConfigEventType.SAFE_APPS_UPDATE]:
this.onConfigEventSafeAppsUpdate.bind(this),
Expand Down Expand Up @@ -124,6 +133,9 @@ export class EventCacheHelper {
case TransactionEventType.MESSAGE_CONFIRMATION:
this._logMessageEvent(event);
break;
case TransactionEventType.NEW_DELEGATE:
case TransactionEventType.UPDATED_DELEGATE:
case TransactionEventType.DELETED_DELEGATE:
case ConfigEventType.CHAIN_UPDATE:
case ConfigEventType.SAFE_APPS_UPDATE:
this._logEvent(event);
Expand Down Expand Up @@ -508,8 +520,38 @@ export class EventCacheHelper {
return [this.safeRepository.clearIsSafe(event)];
}

private onTransactionEventDelegate(
event: Extract<
Event,
{
type:
| TransactionEventType.NEW_DELEGATE
| TransactionEventType.UPDATED_DELEGATE
| TransactionEventType.DELETED_DELEGATE;
}
>,
): Array<Promise<void>> {
// A delegate change affects:
// - the delegates associated to the Safe
return [
this.delegatesRepository.clearDelegates({
chainId: event.chainId,
safeAddress: event.address ?? undefined,
}),
];
}

private _logSafeTxEvent(
event: Event & { address: string; safeTxHash: string },
event: Extract<
Event,
{
type:
| TransactionEventType.PENDING_MULTISIG_TRANSACTION
| TransactionEventType.DELETED_MULTISIG_TRANSACTION
| TransactionEventType.EXECUTED_MULTISIG_TRANSACTION
| TransactionEventType.NEW_CONFIRMATION;
}
>,
): void {
this.loggingService.info({
type: EventCacheHelper.HOOK_TYPE,
Expand All @@ -521,7 +563,17 @@ export class EventCacheHelper {
}

private _logTxEvent(
event: Event & { address: string; txHash: string },
event: Extract<
Event,
{
type:
| TransactionEventType.MODULE_TRANSACTION
| TransactionEventType.INCOMING_ETHER
| TransactionEventType.OUTGOING_ETHER
| TransactionEventType.INCOMING_TOKEN
| TransactionEventType.OUTGOING_TOKEN;
}
>,
): void {
this.loggingService.info({
type: EventCacheHelper.HOOK_TYPE,
Expand All @@ -533,7 +585,14 @@ export class EventCacheHelper {
}

private _logMessageEvent(
event: Event & { address: string; messageHash: string },
event: Extract<
Event,
{
type:
| TransactionEventType.MESSAGE_CREATED
| TransactionEventType.MESSAGE_CONFIRMATION;
}
>,
): void {
this.loggingService.info({
type: EventCacheHelper.HOOK_TYPE,
Expand Down
9 changes: 8 additions & 1 deletion src/domain/hooks/helpers/event-notifications.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export class EventNotificationsHelper {
* @param event - {@link Event} to check
*/
private isEventToNotify(event: Event): event is EventToNotify {
// TODO: Simplify this by inverting the logic and/or refactor mapEventNotification to explicitly handle types
return (
// Don't notify about Config events
event.type !== ConfigEventType.CHAIN_UPDATE &&
Expand All @@ -124,7 +125,13 @@ export class EventNotificationsHelper {
// We only notify required confirmations on required - see MESSAGE_CREATED
event.type !== TransactionEventType.MESSAGE_CONFIRMATION &&
// You cannot subscribe to Safes-to-be-created
event.type !== TransactionEventType.SAFE_CREATED
event.type !== TransactionEventType.SAFE_CREATED &&
// We don't notify about reorgs
event.type !== TransactionEventType.REORG_DETECTED &&
// We don't notify about delegate events
event.type !== TransactionEventType.NEW_DELEGATE &&
event.type !== TransactionEventType.UPDATED_DELEGATE &&
event.type !== TransactionEventType.DELETED_DELEGATE
);
}

Expand Down
7 changes: 7 additions & 0 deletions src/domain/hooks/hooks.repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { BlockchainRepository } from '@/domain/blockchain/blockchain.reposi
import type { ChainsRepository } from '@/domain/chains/chains.repository';
import { chainBuilder } from '@/domain/chains/entities/__tests__/chain.builder';
import type { CollectiblesRepository } from '@/domain/collectibles/collectibles.repository';
import type { DelegatesV2Repository } from '@/domain/delegate/v2/delegates.v2.repository';
import { pageBuilder } from '@/domain/entities/__tests__/page.builder';
import { EventCacheHelper } from '@/domain/hooks/helpers/event-cache.helper';
import type { EventNotificationsHelper } from '@/domain/hooks/helpers/event-notifications.helper';
Expand Down Expand Up @@ -42,6 +43,10 @@ const mockCollectiblesRepository = jest.mocked({
clearCollectibles: jest.fn(),
} as jest.MockedObjectDeep<CollectiblesRepository>);

const mockDelegatesRepository = jest.mocked({
clearDelegates: jest.fn(),
} as jest.MockedObjectDeep<DelegatesV2Repository>);

const mockMessagesRepository = jest.mocked({
clearMessages: jest.fn(),
} as unknown as jest.MockedObjectDeep<MessagesRepository>);
Expand Down Expand Up @@ -100,6 +105,7 @@ describe('HooksRepository (Unit)', () => {
mockBlockchainRepository,
mockChainsRepository,
mockCollectiblesRepository,
mockDelegatesRepository,
mockMessagesRepository,
mockSafeAppsRepository,
mockSafeRepository,
Expand Down Expand Up @@ -278,6 +284,7 @@ describe('HooksRepositoryWithNotifications (Unit)', () => {
mockBlockchainRepository,
mockChainsRepository,
mockCollectiblesRepository,
mockDelegatesRepository,
mockMessagesRepository,
mockSafeAppsRepository,
mockSafeRepository,
Expand Down
2 changes: 2 additions & 0 deletions src/domain/interfaces/transaction-api.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export interface ITransactionApi {
offset?: number;
}): Promise<Raw<Page<Delegate>>>;

clearDelegates(safeAddress?: `0x${string}`): Promise<void>;

postDelegate(args: {
safeAddress: `0x${string}` | null;
delegate: `0x${string}`;
Expand Down
33 changes: 33 additions & 0 deletions src/routes/hooks/__tests__/event-hooks-queue.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,4 +540,37 @@ describe('Events queue processing e2e tests', () => {
expect(cacheContent).toBeNull();
});
});

it.each(['NEW_DELEGATE', 'UPDATED_DELEGATE', 'DELETED_DELEGATE'])(
'%s clears delegates',
async (type) => {
const cacheDir = new CacheDir(
`${TEST_SAFE.chainId}_delegates_${TEST_SAFE.address}`,
'',
);
await redisClient.hSet(
`${cacheKeyPrefix}-${cacheDir.key}`,
cacheDir.field,
faker.string.alpha(),
);
const data = {
type,
chainId: TEST_SAFE.chainId,
address: TEST_SAFE.address,
delegate: faker.finance.ethereumAddress(),
delegator: faker.finance.ethereumAddress(),
label: faker.lorem.word(),
};

await channel.sendToQueue(queueName, data);

await retry(async () => {
const cacheContent = await redisClient.hGet(
`${cacheKeyPrefix}-${cacheDir.key}`,
cacheDir.field,
);
expect(cacheContent).toBeNull();
});
},
);
});
45 changes: 45 additions & 0 deletions src/routes/hooks/entities/__tests__/delegate-events.builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { faker } from '@faker-js/faker';
import { getAddress } from 'viem';
import type { z } from 'zod';
import { TransactionEventType } from '@/routes/hooks/entities/event-type.entity';
import { Builder } from '@/__tests__/builder';
import type { IBuilder } from '@/__tests__/builder';
import type {
DelegateEventPayloadSchema,
DeletedDelegateEvent,
NewDelegateEvent,
UpdatedDelegateEvent,
} from '@/routes/hooks/entities/schemas/delegate-events.schema';

type DelegateEventPayload = z.infer<typeof DelegateEventPayloadSchema>;

function delegateEventBuilder(): IBuilder<DelegateEventPayload> {
return new Builder<DelegateEventPayload>()
.with('chainId', faker.string.numeric())
.with('address', getAddress(faker.finance.ethereumAddress()))
.with('delegate', getAddress(faker.finance.ethereumAddress()))
.with('delegator', getAddress(faker.finance.ethereumAddress()))
.with('label', faker.lorem.word())
.with('expiryDateSeconds', faker.number.int());
}

export function newDelegateEventBuilder(): IBuilder<NewDelegateEvent> {
return (delegateEventBuilder() as IBuilder<NewDelegateEvent>).with(
'type',
TransactionEventType.NEW_DELEGATE,
);
}

export function updatedDelegateEventBuilder(): IBuilder<UpdatedDelegateEvent> {
return (delegateEventBuilder() as IBuilder<UpdatedDelegateEvent>).with(
'type',
TransactionEventType.UPDATED_DELEGATE,
);
}

export function deletedDelegateEventBuilder(): IBuilder<DeletedDelegateEvent> {
return (delegateEventBuilder() as IBuilder<DeletedDelegateEvent>).with(
'type',
TransactionEventType.DELETED_DELEGATE,
);
}
3 changes: 3 additions & 0 deletions src/routes/hooks/entities/event-type.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum TransactionEventType {
DELETED_DELEGATE = 'DELETED_DELEGATE',
DELETED_MULTISIG_TRANSACTION = 'DELETED_MULTISIG_TRANSACTION',
EXECUTED_MULTISIG_TRANSACTION = 'EXECUTED_MULTISIG_TRANSACTION',
INCOMING_ETHER = 'INCOMING_ETHER',
Expand All @@ -7,11 +8,13 @@ export enum TransactionEventType {
MESSAGE_CREATED = 'MESSAGE_CREATED',
MODULE_TRANSACTION = 'MODULE_TRANSACTION',
NEW_CONFIRMATION = 'NEW_CONFIRMATION',
NEW_DELEGATE = 'NEW_DELEGATE',
OUTGOING_ETHER = 'OUTGOING_ETHER',
OUTGOING_TOKEN = 'OUTGOING_TOKEN',
PENDING_MULTISIG_TRANSACTION = 'PENDING_MULTISIG_TRANSACTION',
REORG_DETECTED = 'REORG_DETECTED',
SAFE_CREATED = 'SAFE_CREATED',
UPDATED_DELEGATE = 'UPDATED_DELEGATE',
}

export enum ConfigEventType {
Expand Down
Loading