eco-solver 0.0.1-security → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of eco-solver might be problematic. Click here for more details.
- package/.eslintignore +8 -0
- package/.eslintrc.js +24 -0
- package/.github/workflows/ci.yaml +38 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/Dockerfile +11 -0
- package/LICENSE +21 -0
- package/README.md +29 -5
- package/config/default.ts +135 -0
- package/config/development.ts +95 -0
- package/config/preproduction.ts +17 -0
- package/config/production.ts +17 -0
- package/config/staging.ts +17 -0
- package/config/test.ts +7 -0
- package/index.js +43 -0
- package/jest.config.ts +14 -0
- package/nest-cli.json +8 -0
- package/package.json +115 -6
- package/src/api/api.module.ts +27 -0
- package/src/api/balance.controller.ts +41 -0
- package/src/api/quote.controller.ts +54 -0
- package/src/api/tests/balance.controller.spec.ts +113 -0
- package/src/api/tests/quote.controller.spec.ts +83 -0
- package/src/app.module.ts +74 -0
- package/src/balance/balance.module.ts +14 -0
- package/src/balance/balance.service.ts +230 -0
- package/src/balance/balance.ws.service.ts +104 -0
- package/src/balance/types.ts +16 -0
- package/src/bullmq/bullmq.helper.ts +41 -0
- package/src/bullmq/processors/eth-ws.processor.ts +47 -0
- package/src/bullmq/processors/inbox.processor.ts +55 -0
- package/src/bullmq/processors/interval.processor.ts +54 -0
- package/src/bullmq/processors/processor.module.ts +14 -0
- package/src/bullmq/processors/signer.processor.ts +41 -0
- package/src/bullmq/processors/solve-intent.processor.ts +73 -0
- package/src/bullmq/processors/tests/solve-intent.processor.spec.ts +3 -0
- package/src/bullmq/utils/queue.ts +22 -0
- package/src/chain-monitor/chain-monitor.module.ts +12 -0
- package/src/chain-monitor/chain-sync.service.ts +134 -0
- package/src/chain-monitor/tests/chain-sync.service.spec.ts +190 -0
- package/src/commander/.eslintrc.js +6 -0
- package/src/commander/balance/balance-command.module.ts +12 -0
- package/src/commander/balance/balance.command.ts +73 -0
- package/src/commander/command-main.ts +15 -0
- package/src/commander/commander-app.module.ts +31 -0
- package/src/commander/eco-config.command.ts +20 -0
- package/src/commander/safe/safe-command.module.ts +11 -0
- package/src/commander/safe/safe.command.ts +70 -0
- package/src/commander/transfer/client.command.ts +24 -0
- package/src/commander/transfer/transfer-command.module.ts +26 -0
- package/src/commander/transfer/transfer.command.ts +138 -0
- package/src/commander/utils.ts +8 -0
- package/src/common/chains/definitions/arbitrum.ts +12 -0
- package/src/common/chains/definitions/base.ts +21 -0
- package/src/common/chains/definitions/eco.ts +54 -0
- package/src/common/chains/definitions/ethereum.ts +22 -0
- package/src/common/chains/definitions/helix.ts +53 -0
- package/src/common/chains/definitions/mantle.ts +12 -0
- package/src/common/chains/definitions/optimism.ts +22 -0
- package/src/common/chains/definitions/polygon.ts +12 -0
- package/src/common/chains/supported.ts +26 -0
- package/src/common/chains/transport.ts +19 -0
- package/src/common/errors/eco-error.ts +155 -0
- package/src/common/events/constants.ts +3 -0
- package/src/common/events/viem.ts +22 -0
- package/src/common/logging/eco-log-message.ts +74 -0
- package/src/common/redis/constants.ts +55 -0
- package/src/common/redis/redis-connection-utils.ts +106 -0
- package/src/common/routes/constants.ts +3 -0
- package/src/common/utils/objects.ts +34 -0
- package/src/common/utils/strings.ts +49 -0
- package/src/common/utils/tests/objects.spec.ts +23 -0
- package/src/common/utils/tests/strings.spec.ts +22 -0
- package/src/common/viem/contracts.ts +25 -0
- package/src/common/viem/tests/utils.spec.ts +115 -0
- package/src/common/viem/utils.ts +78 -0
- package/src/contracts/ERC20.contract.ts +389 -0
- package/src/contracts/EntryPoint.V6.contract.ts +1309 -0
- package/src/contracts/KernelAccount.abi.ts +87 -0
- package/src/contracts/OwnableExecutor.abi.ts +128 -0
- package/src/contracts/SimpleAccount.contract.ts +524 -0
- package/src/contracts/inbox.ts +8 -0
- package/src/contracts/index.ts +9 -0
- package/src/contracts/intent-source.ts +55 -0
- package/src/contracts/interfaces/index.ts +1 -0
- package/src/contracts/interfaces/prover.interface.ts +22 -0
- package/src/contracts/prover.ts +9 -0
- package/src/contracts/tests/erc20.contract.spec.ts +59 -0
- package/src/contracts/utils.ts +31 -0
- package/src/decoder/decoder.interface.ts +3 -0
- package/src/decoder/tests/utils.spec.ts +36 -0
- package/src/decoder/utils.ts +24 -0
- package/src/decorators/cacheable.decorator.ts +48 -0
- package/src/eco-configs/aws-config.service.ts +75 -0
- package/src/eco-configs/eco-config.module.ts +44 -0
- package/src/eco-configs/eco-config.service.ts +220 -0
- package/src/eco-configs/eco-config.types.ts +278 -0
- package/src/eco-configs/interfaces/config-source.interface.ts +3 -0
- package/src/eco-configs/tests/aws-config.service.spec.ts +52 -0
- package/src/eco-configs/tests/eco-config.service.spec.ts +137 -0
- package/src/eco-configs/tests/utils.spec.ts +84 -0
- package/src/eco-configs/utils.ts +49 -0
- package/src/fee/fee.module.ts +10 -0
- package/src/fee/fee.service.ts +467 -0
- package/src/fee/tests/fee.service.spec.ts +909 -0
- package/src/fee/tests/utils.spec.ts +49 -0
- package/src/fee/types.ts +44 -0
- package/src/fee/utils.ts +23 -0
- package/src/flags/flags.module.ts +10 -0
- package/src/flags/flags.service.ts +112 -0
- package/src/flags/tests/flags.service.spec.ts +68 -0
- package/src/flags/utils.ts +22 -0
- package/src/health/constants.ts +1 -0
- package/src/health/health.controller.ts +23 -0
- package/src/health/health.module.ts +25 -0
- package/src/health/health.service.ts +40 -0
- package/src/health/indicators/balance.indicator.ts +196 -0
- package/src/health/indicators/eco-redis.indicator.ts +23 -0
- package/src/health/indicators/git-commit.indicator.ts +67 -0
- package/src/health/indicators/mongodb.indicator.ts +11 -0
- package/src/health/indicators/permission.indicator.ts +64 -0
- package/src/intent/create-intent.service.ts +129 -0
- package/src/intent/feasable-intent.service.ts +80 -0
- package/src/intent/fulfill-intent.service.ts +318 -0
- package/src/intent/intent.controller.ts +199 -0
- package/src/intent/intent.module.ts +49 -0
- package/src/intent/schemas/intent-call-data.schema.ts +16 -0
- package/src/intent/schemas/intent-data.schema.ts +114 -0
- package/src/intent/schemas/intent-source.schema.ts +33 -0
- package/src/intent/schemas/intent-token-amount.schema.ts +14 -0
- package/src/intent/schemas/reward-data.schema.ts +48 -0
- package/src/intent/schemas/route-data.schema.ts +52 -0
- package/src/intent/schemas/watch-event.schema.ts +32 -0
- package/src/intent/tests/create-intent.service.spec.ts +215 -0
- package/src/intent/tests/feasable-intent.service.spec.ts +155 -0
- package/src/intent/tests/fulfill-intent.service.spec.ts +564 -0
- package/src/intent/tests/utils-intent.service.spec.ts +308 -0
- package/src/intent/tests/utils.spec.ts +62 -0
- package/src/intent/tests/validate-intent.service.spec.ts +297 -0
- package/src/intent/tests/validation.service.spec.ts +337 -0
- package/src/intent/utils-intent.service.ts +168 -0
- package/src/intent/utils.ts +37 -0
- package/src/intent/validate-intent.service.ts +176 -0
- package/src/intent/validation.sevice.ts +223 -0
- package/src/interceptors/big-int.interceptor.ts +30 -0
- package/src/intervals/interval.module.ts +18 -0
- package/src/intervals/retry-infeasable-intents.service.ts +89 -0
- package/src/intervals/tests/retry-infeasable-intents.service.spec.ts +167 -0
- package/src/kms/errors.ts +0 -0
- package/src/kms/kms.module.ts +12 -0
- package/src/kms/kms.service.ts +65 -0
- package/src/kms/tests/kms.service.spec.ts +60 -0
- package/src/liquidity-manager/jobs/check-balances-cron.job.ts +229 -0
- package/src/liquidity-manager/jobs/liquidity-manager.job.ts +52 -0
- package/src/liquidity-manager/jobs/rebalance.job.ts +61 -0
- package/src/liquidity-manager/liquidity-manager.module.ts +29 -0
- package/src/liquidity-manager/processors/base.processor.ts +117 -0
- package/src/liquidity-manager/processors/eco-protocol-intents.processor.ts +34 -0
- package/src/liquidity-manager/processors/grouped-jobs.processor.ts +103 -0
- package/src/liquidity-manager/queues/liquidity-manager.queue.ts +48 -0
- package/src/liquidity-manager/schemas/rebalance-token.schema.ts +32 -0
- package/src/liquidity-manager/schemas/rebalance.schema.ts +32 -0
- package/src/liquidity-manager/services/liquidity-manager.service.ts +188 -0
- package/src/liquidity-manager/services/liquidity-provider.service.ts +25 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.spec.ts +125 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.ts +117 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/utils/get-transaction-hashes.ts +16 -0
- package/src/liquidity-manager/tests/liquidity-manager.service.spec.ts +142 -0
- package/src/liquidity-manager/types/token-state.enum.ts +5 -0
- package/src/liquidity-manager/types/types.d.ts +52 -0
- package/src/liquidity-manager/utils/address.ts +5 -0
- package/src/liquidity-manager/utils/math.ts +9 -0
- package/src/liquidity-manager/utils/serialize.spec.ts +24 -0
- package/src/liquidity-manager/utils/serialize.ts +47 -0
- package/src/liquidity-manager/utils/token.ts +91 -0
- package/src/main.ts +63 -0
- package/src/nest-redlock/nest-redlock.config.ts +14 -0
- package/src/nest-redlock/nest-redlock.interface.ts +5 -0
- package/src/nest-redlock/nest-redlock.module.ts +64 -0
- package/src/nest-redlock/nest-redlock.service.ts +59 -0
- package/src/prover/proof.service.ts +184 -0
- package/src/prover/prover.module.ts +10 -0
- package/src/prover/tests/proof.service.spec.ts +154 -0
- package/src/quote/dto/quote.intent.data.dto.ts +35 -0
- package/src/quote/dto/quote.reward.data.dto.ts +67 -0
- package/src/quote/dto/quote.route.data.dto.ts +71 -0
- package/src/quote/dto/types.ts +18 -0
- package/src/quote/errors.ts +215 -0
- package/src/quote/quote.module.ts +17 -0
- package/src/quote/quote.service.ts +299 -0
- package/src/quote/schemas/quote-call.schema.ts +16 -0
- package/src/quote/schemas/quote-intent.schema.ts +27 -0
- package/src/quote/schemas/quote-reward.schema.ts +24 -0
- package/src/quote/schemas/quote-route.schema.ts +30 -0
- package/src/quote/schemas/quote-token.schema.ts +14 -0
- package/src/quote/tests/quote.service.spec.ts +444 -0
- package/src/sign/atomic-signer.service.ts +24 -0
- package/src/sign/atomic.nonce.service.ts +114 -0
- package/src/sign/kms-account/kmsToAccount.ts +73 -0
- package/src/sign/kms-account/signKms.ts +30 -0
- package/src/sign/kms-account/signKmsTransaction.ts +37 -0
- package/src/sign/kms-account/signKmsTypedData.ts +21 -0
- package/src/sign/nonce.service.ts +89 -0
- package/src/sign/schemas/nonce.schema.ts +36 -0
- package/src/sign/sign.controller.ts +52 -0
- package/src/sign/sign.helper.ts +23 -0
- package/src/sign/sign.module.ts +27 -0
- package/src/sign/signer-kms.service.ts +27 -0
- package/src/sign/signer.service.ts +26 -0
- package/src/solver/filters/tests/valid-smart-wallet.service.spec.ts +87 -0
- package/src/solver/filters/valid-smart-wallet.service.ts +58 -0
- package/src/solver/solver.module.ts +10 -0
- package/src/transaction/multichain-public-client.service.ts +15 -0
- package/src/transaction/smart-wallets/kernel/actions/encodeData.kernel.ts +57 -0
- package/src/transaction/smart-wallets/kernel/create-kernel-client-v2.account.ts +183 -0
- package/src/transaction/smart-wallets/kernel/create.kernel.account.ts +270 -0
- package/src/transaction/smart-wallets/kernel/index.ts +2 -0
- package/src/transaction/smart-wallets/kernel/kernel-account-client-v2.service.ts +90 -0
- package/src/transaction/smart-wallets/kernel/kernel-account-client.service.ts +107 -0
- package/src/transaction/smart-wallets/kernel/kernel-account.client.ts +105 -0
- package/src/transaction/smart-wallets/kernel/kernel-account.config.ts +34 -0
- package/src/transaction/smart-wallets/simple-account/create.simple.account.ts +19 -0
- package/src/transaction/smart-wallets/simple-account/index.ts +2 -0
- package/src/transaction/smart-wallets/simple-account/simple-account-client.service.ts +42 -0
- package/src/transaction/smart-wallets/simple-account/simple-account.client.ts +83 -0
- package/src/transaction/smart-wallets/simple-account/simple-account.config.ts +5 -0
- package/src/transaction/smart-wallets/smart-wallet.types.ts +38 -0
- package/src/transaction/smart-wallets/utils.ts +14 -0
- package/src/transaction/transaction.module.ts +25 -0
- package/src/transaction/viem_multichain_client.service.ts +100 -0
- package/src/transforms/viem-address.decorator.ts +14 -0
- package/src/utils/bigint.ts +44 -0
- package/src/utils/types.ts +18 -0
- package/src/watch/intent/tests/watch-create-intent.service.spec.ts +257 -0
- package/src/watch/intent/tests/watch-fulfillment.service.spec.ts +141 -0
- package/src/watch/intent/watch-create-intent.service.ts +106 -0
- package/src/watch/intent/watch-event.service.ts +133 -0
- package/src/watch/intent/watch-fulfillment.service.ts +115 -0
- package/src/watch/watch.module.ts +13 -0
- package/test/app.e2e-spec.ts +21 -0
- package/test/jest-e2e.json +9 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +29 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
import { QUEUES } from '@/common/redis/constants'
|
2
|
+
import { EcoConfigService } from '@/eco-configs/eco-config.service'
|
3
|
+
import { WatchCreateIntentService } from '@/watch/intent/watch-create-intent.service'
|
4
|
+
import { createMock, DeepMocked } from '@golevelup/ts-jest'
|
5
|
+
import { BullModule, getQueueToken } from '@nestjs/bullmq'
|
6
|
+
import { Test, TestingModule } from '@nestjs/testing'
|
7
|
+
import { Job, Queue } from 'bullmq'
|
8
|
+
import { EcoError } from '@/common/errors/eco-error'
|
9
|
+
import { MultichainPublicClientService } from '@/transaction/multichain-public-client.service'
|
10
|
+
import { serialize } from '@/liquidity-manager/utils/serialize'
|
11
|
+
import { IntentCreatedLog } from '@/contracts'
|
12
|
+
|
13
|
+
describe('WatchIntentService', () => {
|
14
|
+
let watchIntentService: WatchCreateIntentService
|
15
|
+
let publicClientService: DeepMocked<MultichainPublicClientService>
|
16
|
+
let ecoConfigService: DeepMocked<EcoConfigService>
|
17
|
+
let queue: DeepMocked<Queue>
|
18
|
+
const mockLogDebug = jest.fn()
|
19
|
+
const mockLogLog = jest.fn()
|
20
|
+
const mockLogError = jest.fn()
|
21
|
+
|
22
|
+
const sources = [
|
23
|
+
{ chainID: 1n, sourceAddress: '0x1234', provers: ['0x88'], network: 'testnet1' },
|
24
|
+
{ chainID: 2n, sourceAddress: '0x5678', provers: ['0x88', '0x99'], network: 'testnet2' },
|
25
|
+
] as any
|
26
|
+
const supportedChains = sources.map((s) => BigInt(s.chainID))
|
27
|
+
|
28
|
+
beforeEach(async () => {
|
29
|
+
const chainMod: TestingModule = await Test.createTestingModule({
|
30
|
+
providers: [
|
31
|
+
WatchCreateIntentService,
|
32
|
+
{
|
33
|
+
provide: MultichainPublicClientService,
|
34
|
+
useValue: createMock<MultichainPublicClientService>(),
|
35
|
+
},
|
36
|
+
{ provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
|
37
|
+
],
|
38
|
+
imports: [
|
39
|
+
BullModule.registerQueue({
|
40
|
+
name: QUEUES.SOURCE_INTENT.queue,
|
41
|
+
}),
|
42
|
+
],
|
43
|
+
})
|
44
|
+
.overrideProvider(getQueueToken(QUEUES.SOURCE_INTENT.queue))
|
45
|
+
.useValue(createMock<Queue>())
|
46
|
+
.compile()
|
47
|
+
|
48
|
+
watchIntentService = chainMod.get(WatchCreateIntentService)
|
49
|
+
publicClientService = chainMod.get(MultichainPublicClientService)
|
50
|
+
ecoConfigService = chainMod.get(EcoConfigService)
|
51
|
+
queue = chainMod.get(getQueueToken(QUEUES.SOURCE_INTENT.queue))
|
52
|
+
|
53
|
+
watchIntentService['logger'].debug = mockLogDebug
|
54
|
+
watchIntentService['logger'].log = mockLogLog
|
55
|
+
watchIntentService['logger'].error = mockLogError
|
56
|
+
})
|
57
|
+
|
58
|
+
afterEach(async () => {
|
59
|
+
// restore the spy created with spyOn
|
60
|
+
jest.restoreAllMocks()
|
61
|
+
mockLogDebug.mockClear()
|
62
|
+
mockLogLog.mockClear()
|
63
|
+
})
|
64
|
+
|
65
|
+
describe('on lifecycle', () => {
|
66
|
+
describe('on startup', () => {
|
67
|
+
it('should subscribe to nothing if no source intents', async () => {
|
68
|
+
const mock = jest.spyOn(watchIntentService, 'subscribe')
|
69
|
+
await watchIntentService.onApplicationBootstrap()
|
70
|
+
expect(mock).toHaveBeenCalledTimes(1)
|
71
|
+
})
|
72
|
+
|
73
|
+
it('should subscribe to all source intents', async () => {
|
74
|
+
const mockWatch = jest.fn()
|
75
|
+
publicClientService.getClient.mockResolvedValue({
|
76
|
+
watchContractEvent: mockWatch,
|
77
|
+
} as any)
|
78
|
+
ecoConfigService.getIntentSources.mockReturnValue(sources)
|
79
|
+
ecoConfigService.getSolvers.mockReturnValue(sources)
|
80
|
+
await watchIntentService.onApplicationBootstrap()
|
81
|
+
expect(mockWatch).toHaveBeenCalledTimes(2)
|
82
|
+
|
83
|
+
for (const [index, s] of sources.entries()) {
|
84
|
+
const { address, eventName, args } = mockWatch.mock.calls[index][0]
|
85
|
+
const partial = { address, eventName, args }
|
86
|
+
expect(partial).toEqual({
|
87
|
+
address: s.sourceAddress,
|
88
|
+
eventName: 'IntentCreated',
|
89
|
+
args: { prover: s.provers },
|
90
|
+
})
|
91
|
+
}
|
92
|
+
})
|
93
|
+
})
|
94
|
+
|
95
|
+
describe('on destroy', () => {
|
96
|
+
it('should unsubscribe to nothing if no source intents', async () => {
|
97
|
+
const mock = jest.spyOn(watchIntentService, 'unsubscribe')
|
98
|
+
await watchIntentService.onModuleDestroy()
|
99
|
+
expect(mock).toHaveBeenCalledTimes(1)
|
100
|
+
})
|
101
|
+
|
102
|
+
it('should unsubscribe to all source intents', async () => {
|
103
|
+
const mockUnwatch = jest.fn()
|
104
|
+
publicClientService.getClient.mockResolvedValue({
|
105
|
+
watchContractEvent: () => mockUnwatch,
|
106
|
+
} as any)
|
107
|
+
ecoConfigService.getIntentSources.mockReturnValue(sources)
|
108
|
+
ecoConfigService.getSolvers.mockReturnValue(sources)
|
109
|
+
await watchIntentService.onApplicationBootstrap()
|
110
|
+
await watchIntentService.onModuleDestroy()
|
111
|
+
expect(mockUnwatch).toHaveBeenCalledTimes(2)
|
112
|
+
})
|
113
|
+
})
|
114
|
+
})
|
115
|
+
|
116
|
+
describe('on intent', () => {
|
117
|
+
const s = sources[0]
|
118
|
+
const log: any = { logIndex: 2, args: { hash: '0x1' } as Partial<IntentCreatedLog['args']> }
|
119
|
+
let mockQueueAdd: jest.SpyInstance<Promise<Job<any, any, string>>>
|
120
|
+
|
121
|
+
beforeEach(async () => {
|
122
|
+
mockQueueAdd = jest.spyOn(queue, 'add')
|
123
|
+
await watchIntentService.addJob(s)([log])
|
124
|
+
expect(mockLogDebug).toHaveBeenCalledTimes(1)
|
125
|
+
})
|
126
|
+
it('should convert all bigints to strings', async () => {
|
127
|
+
expect(mockLogDebug.mock.calls[0][0].createIntent).toEqual(
|
128
|
+
expect.objectContaining(serialize(log)),
|
129
|
+
)
|
130
|
+
})
|
131
|
+
|
132
|
+
it('should should attach source chainID and network', async () => {
|
133
|
+
expect(mockLogDebug.mock.calls[0][0].createIntent).toEqual(
|
134
|
+
expect.objectContaining(
|
135
|
+
serialize({
|
136
|
+
sourceChainID: s.chainID,
|
137
|
+
sourceNetwork: s.network,
|
138
|
+
}),
|
139
|
+
),
|
140
|
+
)
|
141
|
+
})
|
142
|
+
|
143
|
+
it('should should enque a job for every intent', async () => {
|
144
|
+
expect(mockQueueAdd).toHaveBeenCalledTimes(1)
|
145
|
+
expect(mockQueueAdd).toHaveBeenCalledWith(
|
146
|
+
QUEUES.SOURCE_INTENT.jobs.create_intent,
|
147
|
+
expect.any(Object),
|
148
|
+
{ jobId: 'watch-create-intent-0x1-2' },
|
149
|
+
)
|
150
|
+
})
|
151
|
+
})
|
152
|
+
|
153
|
+
describe('on unsubscribe', () => {
|
154
|
+
let mockUnwatch1: jest.Mock = jest.fn()
|
155
|
+
let mockUnwatch2: jest.Mock = jest.fn()
|
156
|
+
beforeEach(async () => {
|
157
|
+
mockUnwatch1 = jest.fn()
|
158
|
+
mockUnwatch2 = jest.fn()
|
159
|
+
watchIntentService['unwatch'] = {
|
160
|
+
1: mockUnwatch1,
|
161
|
+
2: mockUnwatch2,
|
162
|
+
}
|
163
|
+
})
|
164
|
+
|
165
|
+
afterEach(async () => {
|
166
|
+
jest.clearAllMocks()
|
167
|
+
})
|
168
|
+
|
169
|
+
it('should unsubscribe to every unwatch and catch any throws', async () => {
|
170
|
+
const e = new Error('test')
|
171
|
+
mockUnwatch1.mockImplementation(() => {
|
172
|
+
throw e
|
173
|
+
})
|
174
|
+
await watchIntentService.unsubscribe()
|
175
|
+
expect(mockUnwatch1).toHaveBeenCalledTimes(1)
|
176
|
+
expect(mockUnwatch2).toHaveBeenCalledTimes(1)
|
177
|
+
expect(mockLogError).toHaveBeenCalledTimes(1)
|
178
|
+
expect(mockLogError).toHaveBeenCalledWith({
|
179
|
+
msg: 'watch-event: unsubscribe',
|
180
|
+
error: EcoError.WatchEventUnsubscribeError.toString(),
|
181
|
+
errorPassed: e,
|
182
|
+
})
|
183
|
+
})
|
184
|
+
|
185
|
+
it('should unsubscribe to every unwatch', async () => {
|
186
|
+
await watchIntentService.unsubscribe()
|
187
|
+
expect(mockUnwatch1).toHaveBeenCalledTimes(1)
|
188
|
+
expect(mockUnwatch2).toHaveBeenCalledTimes(1)
|
189
|
+
expect(mockLogError).toHaveBeenCalledTimes(0)
|
190
|
+
expect(mockLogDebug).toHaveBeenCalledTimes(1)
|
191
|
+
expect(mockLogDebug).toHaveBeenCalledWith({
|
192
|
+
msg: 'watch-event: unsubscribe',
|
193
|
+
})
|
194
|
+
})
|
195
|
+
})
|
196
|
+
|
197
|
+
describe('on unsubscribeFrom', () => {
|
198
|
+
let mockUnwatch1: jest.Mock = jest.fn()
|
199
|
+
const chainID = 1
|
200
|
+
beforeEach(async () => {
|
201
|
+
mockUnwatch1 = jest.fn()
|
202
|
+
watchIntentService['unwatch'] = {
|
203
|
+
[chainID]: mockUnwatch1,
|
204
|
+
}
|
205
|
+
})
|
206
|
+
|
207
|
+
afterEach(async () => {
|
208
|
+
jest.clearAllMocks()
|
209
|
+
})
|
210
|
+
|
211
|
+
describe('on unwatch exists', () => {
|
212
|
+
it('should unsubscribe to unwatch', async () => {
|
213
|
+
await watchIntentService.unsubscribeFrom(chainID)
|
214
|
+
expect(mockUnwatch1).toHaveBeenCalledTimes(1)
|
215
|
+
expect(mockLogDebug).toHaveBeenCalledTimes(1)
|
216
|
+
expect(mockLogError).toHaveBeenCalledTimes(0)
|
217
|
+
expect(mockLogDebug).toHaveBeenCalledWith({
|
218
|
+
msg: 'watch-event: unsubscribeFrom',
|
219
|
+
chainID,
|
220
|
+
})
|
221
|
+
})
|
222
|
+
|
223
|
+
it('should unsubscribe to unwatch and catch throw', async () => {
|
224
|
+
const e = new Error('test')
|
225
|
+
mockUnwatch1.mockImplementation(() => {
|
226
|
+
throw e
|
227
|
+
})
|
228
|
+
await watchIntentService.unsubscribeFrom(chainID)
|
229
|
+
expect(mockUnwatch1).toHaveBeenCalledTimes(1)
|
230
|
+
expect(mockLogError).toHaveBeenCalledTimes(1)
|
231
|
+
expect(mockLogError).toHaveBeenCalledWith({
|
232
|
+
msg: 'watch-event: unsubscribeFrom',
|
233
|
+
error: EcoError.WatchEventUnsubscribeFromError(chainID).toString(),
|
234
|
+
errorPassed: e,
|
235
|
+
chainID,
|
236
|
+
})
|
237
|
+
})
|
238
|
+
})
|
239
|
+
|
240
|
+
describe('on unwatch doesnt exist', () => {
|
241
|
+
beforeEach(async () => {
|
242
|
+
watchIntentService['unwatch'] = {}
|
243
|
+
})
|
244
|
+
|
245
|
+
it('should log error', async () => {
|
246
|
+
await watchIntentService.unsubscribeFrom(chainID)
|
247
|
+
expect(mockUnwatch1).toHaveBeenCalledTimes(0)
|
248
|
+
expect(mockLogError).toHaveBeenCalledTimes(1)
|
249
|
+
expect(mockLogError).toHaveBeenCalledWith({
|
250
|
+
msg: 'watch event: unsubscribeFrom',
|
251
|
+
error: EcoError.WatchEventNoUnsubscribeError(chainID).toString(),
|
252
|
+
chainID,
|
253
|
+
})
|
254
|
+
})
|
255
|
+
})
|
256
|
+
})
|
257
|
+
})
|
@@ -0,0 +1,141 @@
|
|
1
|
+
import { QUEUES } from '@/common/redis/constants'
|
2
|
+
import { EcoConfigService } from '@/eco-configs/eco-config.service'
|
3
|
+
import { MultichainPublicClientService } from '@/transaction/multichain-public-client.service'
|
4
|
+
import { WatchFulfillmentService } from '@/watch/intent/watch-fulfillment.service'
|
5
|
+
import { createMock, DeepMocked } from '@golevelup/ts-jest'
|
6
|
+
import { BullModule, getQueueToken } from '@nestjs/bullmq'
|
7
|
+
import { Test, TestingModule } from '@nestjs/testing'
|
8
|
+
import { Job, Queue } from 'bullmq'
|
9
|
+
|
10
|
+
describe('WatchFulfillmentService', () => {
|
11
|
+
let watchFulfillmentService: WatchFulfillmentService
|
12
|
+
let publicClientService: DeepMocked<MultichainPublicClientService>
|
13
|
+
let ecoConfigService: DeepMocked<EcoConfigService>
|
14
|
+
let queue: DeepMocked<Queue>
|
15
|
+
const mockLogDebug = jest.fn()
|
16
|
+
const mockLogLog = jest.fn()
|
17
|
+
|
18
|
+
const inboxes = [
|
19
|
+
{ chainID: 1, inboxAddress: '0x1234' },
|
20
|
+
{ chainID: 2, inboxAddress: '0x5678' },
|
21
|
+
] as any
|
22
|
+
const inboxRecord = inboxes.reduce((acc, solver) => {
|
23
|
+
acc[solver.chainID] = solver
|
24
|
+
return acc
|
25
|
+
}, {})
|
26
|
+
const supportedChains = inboxes.map((s) => BigInt(s.chainID))
|
27
|
+
|
28
|
+
beforeEach(async () => {
|
29
|
+
const chainMod: TestingModule = await Test.createTestingModule({
|
30
|
+
providers: [
|
31
|
+
WatchFulfillmentService,
|
32
|
+
{
|
33
|
+
provide: MultichainPublicClientService,
|
34
|
+
useValue: createMock<MultichainPublicClientService>(),
|
35
|
+
},
|
36
|
+
{ provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
|
37
|
+
],
|
38
|
+
imports: [
|
39
|
+
BullModule.registerQueue({
|
40
|
+
name: QUEUES.INBOX.queue,
|
41
|
+
}),
|
42
|
+
],
|
43
|
+
})
|
44
|
+
.overrideProvider(getQueueToken(QUEUES.INBOX.queue))
|
45
|
+
.useValue(createMock<Queue>())
|
46
|
+
.compile()
|
47
|
+
|
48
|
+
watchFulfillmentService = chainMod.get(WatchFulfillmentService)
|
49
|
+
publicClientService = chainMod.get(MultichainPublicClientService)
|
50
|
+
ecoConfigService = chainMod.get(EcoConfigService)
|
51
|
+
queue = chainMod.get(getQueueToken(QUEUES.INBOX.queue))
|
52
|
+
|
53
|
+
watchFulfillmentService['logger'].debug = mockLogDebug
|
54
|
+
watchFulfillmentService['logger'].log = mockLogLog
|
55
|
+
})
|
56
|
+
|
57
|
+
afterEach(async () => {
|
58
|
+
// restore the spy created with spyOn
|
59
|
+
jest.restoreAllMocks()
|
60
|
+
mockLogDebug.mockClear()
|
61
|
+
mockLogLog.mockClear()
|
62
|
+
})
|
63
|
+
|
64
|
+
describe('on lifecycle', () => {
|
65
|
+
describe('on startup', () => {
|
66
|
+
it('should subscribe to nothing if no solvers', async () => {
|
67
|
+
const mock = jest.spyOn(watchFulfillmentService, 'subscribe')
|
68
|
+
await watchFulfillmentService.onApplicationBootstrap()
|
69
|
+
expect(mock).toHaveBeenCalledTimes(1)
|
70
|
+
})
|
71
|
+
|
72
|
+
it('should subscribe to all solvers', async () => {
|
73
|
+
const mockWatch = jest.fn()
|
74
|
+
publicClientService.getClient.mockResolvedValue({
|
75
|
+
watchContractEvent: mockWatch,
|
76
|
+
} as any)
|
77
|
+
ecoConfigService.getSolvers.mockReturnValue(inboxRecord)
|
78
|
+
watchFulfillmentService.getSupportedChains = jest.fn().mockReturnValue(supportedChains)
|
79
|
+
await watchFulfillmentService.onApplicationBootstrap()
|
80
|
+
expect(mockWatch).toHaveBeenCalledTimes(2)
|
81
|
+
|
82
|
+
for (const [index, s] of inboxes.entries()) {
|
83
|
+
const { address, eventName, args } = mockWatch.mock.calls[index][0]
|
84
|
+
const partial = { address, eventName, args }
|
85
|
+
expect(partial).toEqual({
|
86
|
+
address: s.inboxAddress,
|
87
|
+
eventName: 'Fulfillment',
|
88
|
+
args: { _sourceChainID: supportedChains },
|
89
|
+
})
|
90
|
+
}
|
91
|
+
})
|
92
|
+
})
|
93
|
+
|
94
|
+
describe('on destroy', () => {
|
95
|
+
it('should unsubscribe to nothing if no solvers', async () => {
|
96
|
+
const mock = jest.spyOn(watchFulfillmentService, 'unsubscribe')
|
97
|
+
await watchFulfillmentService.onModuleDestroy()
|
98
|
+
expect(mock).toHaveBeenCalledTimes(1)
|
99
|
+
})
|
100
|
+
|
101
|
+
it('should unsubscribe to all solvers', async () => {
|
102
|
+
const mockUnwatch = jest.fn()
|
103
|
+
publicClientService.getClient.mockResolvedValue({
|
104
|
+
watchContractEvent: () => mockUnwatch,
|
105
|
+
} as any)
|
106
|
+
ecoConfigService.getSolvers.mockReturnValue(inboxRecord)
|
107
|
+
await watchFulfillmentService.onApplicationBootstrap()
|
108
|
+
await watchFulfillmentService.onModuleDestroy()
|
109
|
+
expect(mockUnwatch).toHaveBeenCalledTimes(2)
|
110
|
+
})
|
111
|
+
})
|
112
|
+
|
113
|
+
describe('on fulfillment', () => {
|
114
|
+
const log = { args: { _hash: BigInt(1), logIndex: BigInt(2) } } as any
|
115
|
+
let mockQueueAdd: jest.SpyInstance<Promise<Job<any, any, string>>>
|
116
|
+
|
117
|
+
beforeEach(async () => {
|
118
|
+
mockQueueAdd = jest.spyOn(queue, 'add')
|
119
|
+
await watchFulfillmentService.addJob()([log])
|
120
|
+
expect(mockLogDebug).toHaveBeenCalledTimes(1)
|
121
|
+
})
|
122
|
+
|
123
|
+
it('should convert all bigints to strings', async () => {
|
124
|
+
expect(mockLogDebug.mock.calls[0][0].fulfillment).toEqual(
|
125
|
+
expect.objectContaining({
|
126
|
+
args: { _hash: log.args._hash.toString(), logIndex: log.args.logIndex.toString() },
|
127
|
+
}),
|
128
|
+
)
|
129
|
+
})
|
130
|
+
|
131
|
+
it('should should enque a job for every intent', async () => {
|
132
|
+
expect(mockQueueAdd).toHaveBeenCalledTimes(1)
|
133
|
+
expect(mockQueueAdd).toHaveBeenCalledWith(
|
134
|
+
QUEUES.INBOX.jobs.fulfillment,
|
135
|
+
expect.any(Object),
|
136
|
+
{ jobId: 'watch-fulfillement-1-0' },
|
137
|
+
)
|
138
|
+
})
|
139
|
+
})
|
140
|
+
})
|
141
|
+
})
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common'
|
2
|
+
import { EcoConfigService } from '@/eco-configs/eco-config.service'
|
3
|
+
import { Queue } from 'bullmq'
|
4
|
+
import { QUEUES } from '@/common/redis/constants'
|
5
|
+
import { InjectQueue } from '@nestjs/bullmq'
|
6
|
+
import { getIntentJobId } from '@/common/utils/strings'
|
7
|
+
import { IntentSource } from '@/eco-configs/eco-config.types'
|
8
|
+
import { EcoLogMessage } from '@/common/logging/eco-log-message'
|
9
|
+
import { MultichainPublicClientService } from '@/transaction/multichain-public-client.service'
|
10
|
+
import { IntentCreatedLog } from '@/contracts'
|
11
|
+
import { PublicClient } from 'viem'
|
12
|
+
import { IntentSourceAbi } from '@eco-foundation/routes-ts'
|
13
|
+
import { WatchEventService } from '@/watch/intent/watch-event.service'
|
14
|
+
import * as BigIntSerializer from '@/liquidity-manager/utils/serialize'
|
15
|
+
|
16
|
+
/**
|
17
|
+
* This service subscribes to IntentSource contracts for IntentCreated events. It subscribes on all
|
18
|
+
* supported chains and prover addresses. When an event is emitted, it mutates the event log, and then
|
19
|
+
* adds it intent queue for processing.
|
20
|
+
*/
|
21
|
+
@Injectable()
|
22
|
+
export class WatchCreateIntentService extends WatchEventService<IntentSource> {
|
23
|
+
protected logger = new Logger(WatchCreateIntentService.name)
|
24
|
+
|
25
|
+
constructor(
|
26
|
+
@InjectQueue(QUEUES.SOURCE_INTENT.queue) protected readonly intentQueue: Queue,
|
27
|
+
protected readonly publicClientService: MultichainPublicClientService,
|
28
|
+
protected readonly ecoConfigService: EcoConfigService,
|
29
|
+
) {
|
30
|
+
super(intentQueue, publicClientService, ecoConfigService)
|
31
|
+
}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Subscribes to all IntentSource contracts for IntentCreated events. It subscribes on all supported chains
|
35
|
+
* filtering on the prover addresses and destination chain ids. It loads a mapping of the unsubscribe events to
|
36
|
+
* call {@link onModuleDestroy} to close the clients.
|
37
|
+
*/
|
38
|
+
async subscribe(): Promise<void> {
|
39
|
+
const subscribeTasks = this.ecoConfigService.getIntentSources().map(async (source) => {
|
40
|
+
const client = await this.publicClientService.getClient(source.chainID)
|
41
|
+
await this.subscribeTo(client, source)
|
42
|
+
})
|
43
|
+
|
44
|
+
await Promise.all(subscribeTasks)
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Unsubscribes from all IntentSource contracts. It closes all clients in {@link onModuleDestroy}
|
49
|
+
*/
|
50
|
+
async unsubscribe() {
|
51
|
+
super.unsubscribe()
|
52
|
+
}
|
53
|
+
|
54
|
+
async subscribeTo(client: PublicClient, source: IntentSource) {
|
55
|
+
this.logger.debug(
|
56
|
+
EcoLogMessage.fromDefault({
|
57
|
+
message: `watch create intent: subscribeToSource`,
|
58
|
+
properties: {
|
59
|
+
source,
|
60
|
+
},
|
61
|
+
}),
|
62
|
+
)
|
63
|
+
this.unwatch[source.chainID] = client.watchContractEvent({
|
64
|
+
onError: async (error) => {
|
65
|
+
await this.onError(error, client, source)
|
66
|
+
},
|
67
|
+
address: source.sourceAddress,
|
68
|
+
abi: IntentSourceAbi,
|
69
|
+
eventName: 'IntentCreated',
|
70
|
+
args: {
|
71
|
+
// // restrict by acceptable chains, chain ids must be bigints
|
72
|
+
// _destinationChain: solverSupportedChains,
|
73
|
+
prover: source.provers,
|
74
|
+
},
|
75
|
+
onLogs: this.addJob(source),
|
76
|
+
})
|
77
|
+
}
|
78
|
+
|
79
|
+
addJob(source: IntentSource) {
|
80
|
+
return async (logs: IntentCreatedLog[]) => {
|
81
|
+
for (const log of logs) {
|
82
|
+
log.sourceChainID = BigInt(source.chainID)
|
83
|
+
log.sourceNetwork = source.network
|
84
|
+
|
85
|
+
// bigint as it can't serialize to JSON
|
86
|
+
const createIntent = BigIntSerializer.serialize(log)
|
87
|
+
const jobId = getIntentJobId(
|
88
|
+
'watch-create-intent',
|
89
|
+
createIntent.args.hash,
|
90
|
+
createIntent.logIndex,
|
91
|
+
)
|
92
|
+
this.logger.debug(
|
93
|
+
EcoLogMessage.fromDefault({
|
94
|
+
message: `watch intent`,
|
95
|
+
properties: { createIntent, jobId },
|
96
|
+
}),
|
97
|
+
)
|
98
|
+
// add to processing queue
|
99
|
+
await this.intentQueue.add(QUEUES.SOURCE_INTENT.jobs.create_intent, createIntent, {
|
100
|
+
jobId,
|
101
|
+
...this.intentJobConfig,
|
102
|
+
})
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
@@ -0,0 +1,133 @@
|
|
1
|
+
import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'
|
2
|
+
import { EcoConfigService } from '../../eco-configs/eco-config.service'
|
3
|
+
import { JobsOptions, Queue } from 'bullmq'
|
4
|
+
import { MultichainPublicClientService } from '../../transaction/multichain-public-client.service'
|
5
|
+
import { PublicClient, WatchContractEventReturnType } from 'viem'
|
6
|
+
import { EcoLogMessage } from '@/common/logging/eco-log-message'
|
7
|
+
import { EcoError } from '@/common/errors/eco-error'
|
8
|
+
|
9
|
+
/**
|
10
|
+
* This service subscribes has hooks for subscribing and unsubscribing to a contract event.
|
11
|
+
*/
|
12
|
+
@Injectable()
|
13
|
+
export abstract class WatchEventService<T extends { chainID: number }>
|
14
|
+
implements OnApplicationBootstrap, OnModuleDestroy
|
15
|
+
{
|
16
|
+
protected logger: Logger
|
17
|
+
protected intentJobConfig: JobsOptions
|
18
|
+
protected unwatch: Record<string, WatchContractEventReturnType> = {}
|
19
|
+
|
20
|
+
constructor(
|
21
|
+
protected readonly queue: Queue,
|
22
|
+
protected readonly publicClientService: MultichainPublicClientService,
|
23
|
+
protected readonly ecoConfigService: EcoConfigService,
|
24
|
+
) {}
|
25
|
+
|
26
|
+
async onModuleInit() {
|
27
|
+
this.intentJobConfig = this.ecoConfigService.getRedis().jobs.intentJobConfig
|
28
|
+
}
|
29
|
+
|
30
|
+
async onApplicationBootstrap() {
|
31
|
+
await this.subscribe()
|
32
|
+
}
|
33
|
+
|
34
|
+
async onModuleDestroy() {
|
35
|
+
// close all clients
|
36
|
+
this.unsubscribe()
|
37
|
+
}
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Subscribes to the events. It loads a mapping of the unsubscribe events to
|
41
|
+
* call {@link onModuleDestroy} to close the clients.
|
42
|
+
*/
|
43
|
+
abstract subscribe(): Promise<void>
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Subscribes to a contract on a specific chain
|
47
|
+
* @param client the client to subscribe to
|
48
|
+
* @param contract the contract to subscribe to
|
49
|
+
*/
|
50
|
+
abstract subscribeTo(client: PublicClient, contract: T): Promise<void>
|
51
|
+
|
52
|
+
/**
|
53
|
+
* Unsubscribes from all events. It closes all clients in {@link onModuleDestroy}
|
54
|
+
*/
|
55
|
+
async unsubscribe(): Promise<void> {
|
56
|
+
this.logger.debug(
|
57
|
+
EcoLogMessage.fromDefault({
|
58
|
+
message: `watch-event: unsubscribe`,
|
59
|
+
}),
|
60
|
+
)
|
61
|
+
Object.values(this.unwatch).forEach((unwatch) => {
|
62
|
+
try {
|
63
|
+
unwatch()
|
64
|
+
} catch (e) {
|
65
|
+
this.logger.error(
|
66
|
+
EcoLogMessage.withError({
|
67
|
+
message: `watch-event: unsubscribe`,
|
68
|
+
error: EcoError.WatchEventUnsubscribeError,
|
69
|
+
properties: {
|
70
|
+
errorPassed: e,
|
71
|
+
},
|
72
|
+
}),
|
73
|
+
)
|
74
|
+
}
|
75
|
+
})
|
76
|
+
}
|
77
|
+
|
78
|
+
async onError(error: any, client: PublicClient, contract: T) {
|
79
|
+
this.logger.error(
|
80
|
+
EcoLogMessage.fromDefault({
|
81
|
+
message: `rpc client error`,
|
82
|
+
properties: {
|
83
|
+
error,
|
84
|
+
},
|
85
|
+
}),
|
86
|
+
)
|
87
|
+
//reset the filters as they might have expired or we might have been moved to a new node
|
88
|
+
//https://support.quicknode.com/hc/en-us/articles/10838914856977-Error-code-32000-message-filter-not-found
|
89
|
+
await this.unsubscribeFrom(contract.chainID)
|
90
|
+
await this.subscribeTo(client, contract)
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Unsubscribes from a specific chain
|
95
|
+
* @param chainID the chain id to unsubscribe from
|
96
|
+
*/
|
97
|
+
async unsubscribeFrom(chainID: number) {
|
98
|
+
if (this.unwatch[chainID]) {
|
99
|
+
this.logger.debug(
|
100
|
+
EcoLogMessage.fromDefault({
|
101
|
+
message: `watch-event: unsubscribeFrom`,
|
102
|
+
properties: {
|
103
|
+
chainID,
|
104
|
+
},
|
105
|
+
}),
|
106
|
+
)
|
107
|
+
try {
|
108
|
+
this.unwatch[chainID]()
|
109
|
+
} catch (e) {
|
110
|
+
this.logger.error(
|
111
|
+
EcoLogMessage.withError({
|
112
|
+
message: `watch-event: unsubscribeFrom`,
|
113
|
+
error: EcoError.WatchEventUnsubscribeFromError(chainID),
|
114
|
+
properties: {
|
115
|
+
chainID,
|
116
|
+
errorPassed: e,
|
117
|
+
},
|
118
|
+
}),
|
119
|
+
)
|
120
|
+
}
|
121
|
+
} else {
|
122
|
+
this.logger.error(
|
123
|
+
EcoLogMessage.withError({
|
124
|
+
message: `watch event: unsubscribeFrom`,
|
125
|
+
error: EcoError.WatchEventNoUnsubscribeError(chainID),
|
126
|
+
properties: {
|
127
|
+
chainID,
|
128
|
+
},
|
129
|
+
}),
|
130
|
+
)
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|