eco-solver 0.0.1-security → 1.5.0

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.

Files changed (244) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.js +24 -0
  3. package/.github/workflows/ci.yaml +38 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc +8 -0
  7. package/Dockerfile +11 -0
  8. package/LICENSE +21 -0
  9. package/README.md +29 -5
  10. package/config/default.ts +135 -0
  11. package/config/development.ts +95 -0
  12. package/config/preproduction.ts +17 -0
  13. package/config/production.ts +17 -0
  14. package/config/staging.ts +17 -0
  15. package/config/test.ts +7 -0
  16. package/index.js +66 -0
  17. package/jest.config.ts +14 -0
  18. package/nest-cli.json +8 -0
  19. package/package.json +115 -6
  20. package/src/api/api.module.ts +27 -0
  21. package/src/api/balance.controller.ts +41 -0
  22. package/src/api/quote.controller.ts +54 -0
  23. package/src/api/tests/balance.controller.spec.ts +113 -0
  24. package/src/api/tests/quote.controller.spec.ts +83 -0
  25. package/src/app.module.ts +74 -0
  26. package/src/balance/balance.module.ts +14 -0
  27. package/src/balance/balance.service.ts +230 -0
  28. package/src/balance/balance.ws.service.ts +104 -0
  29. package/src/balance/types.ts +16 -0
  30. package/src/bullmq/bullmq.helper.ts +41 -0
  31. package/src/bullmq/processors/eth-ws.processor.ts +47 -0
  32. package/src/bullmq/processors/inbox.processor.ts +55 -0
  33. package/src/bullmq/processors/interval.processor.ts +54 -0
  34. package/src/bullmq/processors/processor.module.ts +14 -0
  35. package/src/bullmq/processors/signer.processor.ts +41 -0
  36. package/src/bullmq/processors/solve-intent.processor.ts +73 -0
  37. package/src/bullmq/processors/tests/solve-intent.processor.spec.ts +3 -0
  38. package/src/bullmq/utils/queue.ts +22 -0
  39. package/src/chain-monitor/chain-monitor.module.ts +12 -0
  40. package/src/chain-monitor/chain-sync.service.ts +134 -0
  41. package/src/chain-monitor/tests/chain-sync.service.spec.ts +190 -0
  42. package/src/commander/.eslintrc.js +6 -0
  43. package/src/commander/balance/balance-command.module.ts +12 -0
  44. package/src/commander/balance/balance.command.ts +73 -0
  45. package/src/commander/command-main.ts +15 -0
  46. package/src/commander/commander-app.module.ts +31 -0
  47. package/src/commander/eco-config.command.ts +20 -0
  48. package/src/commander/safe/safe-command.module.ts +11 -0
  49. package/src/commander/safe/safe.command.ts +70 -0
  50. package/src/commander/transfer/client.command.ts +24 -0
  51. package/src/commander/transfer/transfer-command.module.ts +26 -0
  52. package/src/commander/transfer/transfer.command.ts +138 -0
  53. package/src/commander/utils.ts +8 -0
  54. package/src/common/chains/definitions/arbitrum.ts +12 -0
  55. package/src/common/chains/definitions/base.ts +21 -0
  56. package/src/common/chains/definitions/eco.ts +54 -0
  57. package/src/common/chains/definitions/ethereum.ts +22 -0
  58. package/src/common/chains/definitions/helix.ts +53 -0
  59. package/src/common/chains/definitions/mantle.ts +12 -0
  60. package/src/common/chains/definitions/optimism.ts +22 -0
  61. package/src/common/chains/definitions/polygon.ts +12 -0
  62. package/src/common/chains/supported.ts +26 -0
  63. package/src/common/chains/transport.ts +19 -0
  64. package/src/common/errors/eco-error.ts +155 -0
  65. package/src/common/events/constants.ts +3 -0
  66. package/src/common/events/viem.ts +22 -0
  67. package/src/common/logging/eco-log-message.ts +74 -0
  68. package/src/common/redis/constants.ts +55 -0
  69. package/src/common/redis/redis-connection-utils.ts +106 -0
  70. package/src/common/routes/constants.ts +3 -0
  71. package/src/common/utils/objects.ts +34 -0
  72. package/src/common/utils/strings.ts +49 -0
  73. package/src/common/utils/tests/objects.spec.ts +23 -0
  74. package/src/common/utils/tests/strings.spec.ts +22 -0
  75. package/src/common/viem/contracts.ts +25 -0
  76. package/src/common/viem/tests/utils.spec.ts +115 -0
  77. package/src/common/viem/utils.ts +78 -0
  78. package/src/contracts/ERC20.contract.ts +389 -0
  79. package/src/contracts/EntryPoint.V6.contract.ts +1309 -0
  80. package/src/contracts/KernelAccount.abi.ts +87 -0
  81. package/src/contracts/OwnableExecutor.abi.ts +128 -0
  82. package/src/contracts/SimpleAccount.contract.ts +524 -0
  83. package/src/contracts/inbox.ts +8 -0
  84. package/src/contracts/index.ts +9 -0
  85. package/src/contracts/intent-source.ts +55 -0
  86. package/src/contracts/interfaces/index.ts +1 -0
  87. package/src/contracts/interfaces/prover.interface.ts +22 -0
  88. package/src/contracts/prover.ts +9 -0
  89. package/src/contracts/tests/erc20.contract.spec.ts +59 -0
  90. package/src/contracts/utils.ts +31 -0
  91. package/src/decoder/decoder.interface.ts +3 -0
  92. package/src/decoder/tests/utils.spec.ts +36 -0
  93. package/src/decoder/utils.ts +24 -0
  94. package/src/decorators/cacheable.decorator.ts +48 -0
  95. package/src/eco-configs/aws-config.service.ts +75 -0
  96. package/src/eco-configs/eco-config.module.ts +44 -0
  97. package/src/eco-configs/eco-config.service.ts +220 -0
  98. package/src/eco-configs/eco-config.types.ts +278 -0
  99. package/src/eco-configs/interfaces/config-source.interface.ts +3 -0
  100. package/src/eco-configs/tests/aws-config.service.spec.ts +52 -0
  101. package/src/eco-configs/tests/eco-config.service.spec.ts +137 -0
  102. package/src/eco-configs/tests/utils.spec.ts +84 -0
  103. package/src/eco-configs/utils.ts +49 -0
  104. package/src/fee/fee.module.ts +10 -0
  105. package/src/fee/fee.service.ts +467 -0
  106. package/src/fee/tests/fee.service.spec.ts +909 -0
  107. package/src/fee/tests/utils.spec.ts +49 -0
  108. package/src/fee/types.ts +44 -0
  109. package/src/fee/utils.ts +23 -0
  110. package/src/flags/flags.module.ts +10 -0
  111. package/src/flags/flags.service.ts +112 -0
  112. package/src/flags/tests/flags.service.spec.ts +68 -0
  113. package/src/flags/utils.ts +22 -0
  114. package/src/health/constants.ts +1 -0
  115. package/src/health/health.controller.ts +23 -0
  116. package/src/health/health.module.ts +25 -0
  117. package/src/health/health.service.ts +40 -0
  118. package/src/health/indicators/balance.indicator.ts +196 -0
  119. package/src/health/indicators/eco-redis.indicator.ts +23 -0
  120. package/src/health/indicators/git-commit.indicator.ts +67 -0
  121. package/src/health/indicators/mongodb.indicator.ts +11 -0
  122. package/src/health/indicators/permission.indicator.ts +64 -0
  123. package/src/intent/create-intent.service.ts +129 -0
  124. package/src/intent/feasable-intent.service.ts +80 -0
  125. package/src/intent/fulfill-intent.service.ts +318 -0
  126. package/src/intent/intent.controller.ts +199 -0
  127. package/src/intent/intent.module.ts +49 -0
  128. package/src/intent/schemas/intent-call-data.schema.ts +16 -0
  129. package/src/intent/schemas/intent-data.schema.ts +114 -0
  130. package/src/intent/schemas/intent-source.schema.ts +33 -0
  131. package/src/intent/schemas/intent-token-amount.schema.ts +14 -0
  132. package/src/intent/schemas/reward-data.schema.ts +48 -0
  133. package/src/intent/schemas/route-data.schema.ts +52 -0
  134. package/src/intent/schemas/watch-event.schema.ts +32 -0
  135. package/src/intent/tests/create-intent.service.spec.ts +215 -0
  136. package/src/intent/tests/feasable-intent.service.spec.ts +155 -0
  137. package/src/intent/tests/fulfill-intent.service.spec.ts +564 -0
  138. package/src/intent/tests/utils-intent.service.spec.ts +308 -0
  139. package/src/intent/tests/utils.spec.ts +62 -0
  140. package/src/intent/tests/validate-intent.service.spec.ts +297 -0
  141. package/src/intent/tests/validation.service.spec.ts +337 -0
  142. package/src/intent/utils-intent.service.ts +168 -0
  143. package/src/intent/utils.ts +37 -0
  144. package/src/intent/validate-intent.service.ts +176 -0
  145. package/src/intent/validation.sevice.ts +223 -0
  146. package/src/interceptors/big-int.interceptor.ts +30 -0
  147. package/src/intervals/interval.module.ts +18 -0
  148. package/src/intervals/retry-infeasable-intents.service.ts +89 -0
  149. package/src/intervals/tests/retry-infeasable-intents.service.spec.ts +167 -0
  150. package/src/kms/errors.ts +0 -0
  151. package/src/kms/kms.module.ts +12 -0
  152. package/src/kms/kms.service.ts +65 -0
  153. package/src/kms/tests/kms.service.spec.ts +60 -0
  154. package/src/liquidity-manager/jobs/check-balances-cron.job.ts +229 -0
  155. package/src/liquidity-manager/jobs/liquidity-manager.job.ts +52 -0
  156. package/src/liquidity-manager/jobs/rebalance.job.ts +61 -0
  157. package/src/liquidity-manager/liquidity-manager.module.ts +29 -0
  158. package/src/liquidity-manager/processors/base.processor.ts +117 -0
  159. package/src/liquidity-manager/processors/eco-protocol-intents.processor.ts +34 -0
  160. package/src/liquidity-manager/processors/grouped-jobs.processor.ts +103 -0
  161. package/src/liquidity-manager/queues/liquidity-manager.queue.ts +48 -0
  162. package/src/liquidity-manager/schemas/rebalance-token.schema.ts +32 -0
  163. package/src/liquidity-manager/schemas/rebalance.schema.ts +32 -0
  164. package/src/liquidity-manager/services/liquidity-manager.service.ts +188 -0
  165. package/src/liquidity-manager/services/liquidity-provider.service.ts +25 -0
  166. package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.spec.ts +125 -0
  167. package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.ts +117 -0
  168. package/src/liquidity-manager/services/liquidity-providers/LiFi/utils/get-transaction-hashes.ts +16 -0
  169. package/src/liquidity-manager/tests/liquidity-manager.service.spec.ts +142 -0
  170. package/src/liquidity-manager/types/token-state.enum.ts +5 -0
  171. package/src/liquidity-manager/types/types.d.ts +52 -0
  172. package/src/liquidity-manager/utils/address.ts +5 -0
  173. package/src/liquidity-manager/utils/math.ts +9 -0
  174. package/src/liquidity-manager/utils/serialize.spec.ts +24 -0
  175. package/src/liquidity-manager/utils/serialize.ts +47 -0
  176. package/src/liquidity-manager/utils/token.ts +91 -0
  177. package/src/main.ts +63 -0
  178. package/src/nest-redlock/nest-redlock.config.ts +14 -0
  179. package/src/nest-redlock/nest-redlock.interface.ts +5 -0
  180. package/src/nest-redlock/nest-redlock.module.ts +64 -0
  181. package/src/nest-redlock/nest-redlock.service.ts +59 -0
  182. package/src/prover/proof.service.ts +184 -0
  183. package/src/prover/prover.module.ts +10 -0
  184. package/src/prover/tests/proof.service.spec.ts +154 -0
  185. package/src/quote/dto/quote.intent.data.dto.ts +35 -0
  186. package/src/quote/dto/quote.reward.data.dto.ts +67 -0
  187. package/src/quote/dto/quote.route.data.dto.ts +71 -0
  188. package/src/quote/dto/types.ts +18 -0
  189. package/src/quote/errors.ts +215 -0
  190. package/src/quote/quote.module.ts +17 -0
  191. package/src/quote/quote.service.ts +299 -0
  192. package/src/quote/schemas/quote-call.schema.ts +16 -0
  193. package/src/quote/schemas/quote-intent.schema.ts +27 -0
  194. package/src/quote/schemas/quote-reward.schema.ts +24 -0
  195. package/src/quote/schemas/quote-route.schema.ts +30 -0
  196. package/src/quote/schemas/quote-token.schema.ts +14 -0
  197. package/src/quote/tests/quote.service.spec.ts +444 -0
  198. package/src/sign/atomic-signer.service.ts +24 -0
  199. package/src/sign/atomic.nonce.service.ts +114 -0
  200. package/src/sign/kms-account/kmsToAccount.ts +73 -0
  201. package/src/sign/kms-account/signKms.ts +30 -0
  202. package/src/sign/kms-account/signKmsTransaction.ts +37 -0
  203. package/src/sign/kms-account/signKmsTypedData.ts +21 -0
  204. package/src/sign/nonce.service.ts +89 -0
  205. package/src/sign/schemas/nonce.schema.ts +36 -0
  206. package/src/sign/sign.controller.ts +52 -0
  207. package/src/sign/sign.helper.ts +23 -0
  208. package/src/sign/sign.module.ts +27 -0
  209. package/src/sign/signer-kms.service.ts +27 -0
  210. package/src/sign/signer.service.ts +26 -0
  211. package/src/solver/filters/tests/valid-smart-wallet.service.spec.ts +87 -0
  212. package/src/solver/filters/valid-smart-wallet.service.ts +58 -0
  213. package/src/solver/solver.module.ts +10 -0
  214. package/src/transaction/multichain-public-client.service.ts +15 -0
  215. package/src/transaction/smart-wallets/kernel/actions/encodeData.kernel.ts +57 -0
  216. package/src/transaction/smart-wallets/kernel/create-kernel-client-v2.account.ts +183 -0
  217. package/src/transaction/smart-wallets/kernel/create.kernel.account.ts +270 -0
  218. package/src/transaction/smart-wallets/kernel/index.ts +2 -0
  219. package/src/transaction/smart-wallets/kernel/kernel-account-client-v2.service.ts +90 -0
  220. package/src/transaction/smart-wallets/kernel/kernel-account-client.service.ts +107 -0
  221. package/src/transaction/smart-wallets/kernel/kernel-account.client.ts +105 -0
  222. package/src/transaction/smart-wallets/kernel/kernel-account.config.ts +34 -0
  223. package/src/transaction/smart-wallets/simple-account/create.simple.account.ts +19 -0
  224. package/src/transaction/smart-wallets/simple-account/index.ts +2 -0
  225. package/src/transaction/smart-wallets/simple-account/simple-account-client.service.ts +42 -0
  226. package/src/transaction/smart-wallets/simple-account/simple-account.client.ts +83 -0
  227. package/src/transaction/smart-wallets/simple-account/simple-account.config.ts +5 -0
  228. package/src/transaction/smart-wallets/smart-wallet.types.ts +38 -0
  229. package/src/transaction/smart-wallets/utils.ts +14 -0
  230. package/src/transaction/transaction.module.ts +25 -0
  231. package/src/transaction/viem_multichain_client.service.ts +100 -0
  232. package/src/transforms/viem-address.decorator.ts +14 -0
  233. package/src/utils/bigint.ts +44 -0
  234. package/src/utils/types.ts +18 -0
  235. package/src/watch/intent/tests/watch-create-intent.service.spec.ts +257 -0
  236. package/src/watch/intent/tests/watch-fulfillment.service.spec.ts +141 -0
  237. package/src/watch/intent/watch-create-intent.service.ts +106 -0
  238. package/src/watch/intent/watch-event.service.ts +133 -0
  239. package/src/watch/intent/watch-fulfillment.service.ts +115 -0
  240. package/src/watch/watch.module.ts +13 -0
  241. package/test/app.e2e-spec.ts +21 -0
  242. package/test/jest-e2e.json +9 -0
  243. package/tsconfig.build.json +4 -0
  244. package/tsconfig.json +29 -0
@@ -0,0 +1,308 @@
1
+ const mockDecodeFunctionData = jest.fn()
2
+ import { createMock, DeepMocked } from '@golevelup/ts-jest'
3
+ import { EcoConfigService } from '../../eco-configs/eco-config.service'
4
+ import { Test, TestingModule } from '@nestjs/testing'
5
+ import { getModelToken } from '@nestjs/mongoose'
6
+ import { IntentSourceModel } from '../schemas/intent-source.schema'
7
+ import { Model } from 'mongoose'
8
+ import { UtilsIntentService } from '../utils-intent.service'
9
+ import { getQueueToken } from '@nestjs/bullmq'
10
+ import { QUEUES } from '../../common/redis/constants'
11
+ import { Queue } from 'bullmq'
12
+ import { EcoError } from '../../common/errors/eco-error'
13
+ import { getFunctionBytes } from '../../common/viem/contracts'
14
+ import { FulfillmentLog } from '@/contracts/inbox'
15
+ import { CallDataInterface } from '@/contracts'
16
+ import { ValidationChecks } from '@/intent/validation.sevice'
17
+ import { QuoteError } from '@/quote/errors'
18
+
19
+ jest.mock('viem', () => {
20
+ return {
21
+ ...jest.requireActual('viem'),
22
+ decodeFunctionData: mockDecodeFunctionData,
23
+ }
24
+ })
25
+
26
+ describe('UtilsIntentService', () => {
27
+ let utilsIntentService: UtilsIntentService
28
+ let ecoConfigService: DeepMocked<EcoConfigService>
29
+ let intentModel: DeepMocked<Model<IntentSourceModel>>
30
+ const mockLogDebug = jest.fn()
31
+ const mockLogLog = jest.fn()
32
+ const mockLogWarn = jest.fn()
33
+ const address1 = '0x1111111111111111111111111111111111111111'
34
+ const address2 = '0x2222222222222222222222222222222222222222'
35
+ beforeEach(async () => {
36
+ const chainMod: TestingModule = await Test.createTestingModule({
37
+ providers: [
38
+ UtilsIntentService,
39
+ { provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
40
+ {
41
+ provide: getModelToken(IntentSourceModel.name),
42
+ useValue: createMock<Model<IntentSourceModel>>(),
43
+ },
44
+ ],
45
+ })
46
+ .overrideProvider(getQueueToken(QUEUES.SOURCE_INTENT.queue))
47
+ .useValue(createMock<Queue>())
48
+ .compile()
49
+
50
+ utilsIntentService = chainMod.get(UtilsIntentService)
51
+ ecoConfigService = chainMod.get(EcoConfigService)
52
+ intentModel = chainMod.get(getModelToken(IntentSourceModel.name))
53
+
54
+ utilsIntentService['logger'].debug = mockLogDebug
55
+ utilsIntentService['logger'].log = mockLogLog
56
+ utilsIntentService['logger'].warn = mockLogWarn
57
+ })
58
+
59
+ afterEach(async () => {
60
+ // restore the spy created with spyOn
61
+ jest.restoreAllMocks()
62
+ mockLogDebug.mockClear()
63
+ mockLogLog.mockClear()
64
+ mockLogWarn.mockClear()
65
+ mockDecodeFunctionData.mockClear()
66
+ })
67
+
68
+ describe('on update models', () => {
69
+ const mockUpdateOne = jest.fn()
70
+ const model = { intent: { hash: '0x123' } } as any
71
+ beforeEach(() => {
72
+ intentModel.updateOne = mockUpdateOne
73
+ })
74
+
75
+ afterEach(() => {
76
+ mockUpdateOne.mockClear()
77
+ })
78
+
79
+ describe('on updateIntentModel', () => {
80
+ it('should updateOne model off the intent hash', async () => {
81
+ await utilsIntentService.updateIntentModel(model)
82
+ expect(mockUpdateOne).toHaveBeenCalledTimes(1)
83
+ expect(mockUpdateOne).toHaveBeenCalledWith({ 'intent.hash': model.intent.hash }, model)
84
+ })
85
+ })
86
+
87
+ describe('on updateInvalidIntentModel', () => {
88
+ it('should updateOne the model as invalid', async () => {
89
+ const invalidCause = {
90
+ supportedProver: false,
91
+ supportedTargets: true,
92
+ supportedSelectors: true,
93
+ validExpirationTime: true,
94
+ validDestination: true,
95
+ fulfillOnDifferentChain: true,
96
+ } as ValidationChecks
97
+ await utilsIntentService.updateInvalidIntentModel(model, invalidCause)
98
+ expect(mockUpdateOne).toHaveBeenCalledTimes(1)
99
+ expect(mockUpdateOne).toHaveBeenCalledWith(
100
+ { 'intent.hash': model.intent.hash },
101
+ { ...model, status: 'INVALID', receipt: invalidCause },
102
+ )
103
+ })
104
+ })
105
+
106
+ describe('on updateInfeasableIntentModel', () => {
107
+ it('should updateOne the model as infeasable', async () => {
108
+ const error = QuoteError.RouteIsInfeasable(10n, 9n)
109
+ await utilsIntentService.updateInfeasableIntentModel(model, error)
110
+ expect(mockUpdateOne).toHaveBeenCalledTimes(1)
111
+ expect(mockUpdateOne).toHaveBeenCalledWith(
112
+ { 'intent.hash': model.intent.hash },
113
+ { ...model, status: 'INFEASABLE', receipt: error },
114
+ )
115
+ })
116
+ })
117
+ })
118
+
119
+ // describe('on selectorsSupported', () => {
120
+ // it('should return false when target length is 0', async () => {
121
+ // const model = { intent: { route: { calls: [] } } } as any
122
+ // expect(utilsIntentService.selectorsSupported(model, {} as any)).toBe(false)
123
+ // expect(mockLogLog).toHaveBeenCalledTimes(1)
124
+ // expect(mockLogLog).toHaveBeenCalledWith({
125
+ // msg: 'validateIntent: Target/data invalid',
126
+ // intent: model.intent,
127
+ // })
128
+ // })
129
+
130
+ // it('should return false some target transactions fail to decode', async () => {
131
+ // const model = {
132
+ // intent: {
133
+ // route: {
134
+ // calls: [
135
+ // { target: address1, data: '0x11' },
136
+ // { target: address2, data: '0x22' },
137
+ // ],
138
+ // },
139
+ // },
140
+ // } as any
141
+ // utilsIntentService.getTransactionTargetData = jest
142
+ // .fn()
143
+ // .mockImplementation((model, solver, call) => {
144
+ // if (call.target === address2) return null
145
+ // return { decoded: true }
146
+ // })
147
+ // expect(utilsIntentService.selectorsSupported(model, {} as any)).toBe(false)
148
+ // })
149
+
150
+ // it('should return true when all target transactions decode', async () => {
151
+ // const model = {
152
+ // intent: {
153
+ // route: {
154
+ // calls: [
155
+ // { target: address1, data: '0x11' },
156
+ // { target: address2, data: '0x22' },
157
+ // ],
158
+ // },
159
+ // },
160
+ // } as any
161
+ // utilsIntentService.getTransactionTargetData = jest.fn().mockReturnValue({ decoded: true })
162
+ // expect(utilsIntentService.selectorsSupported(model, {} as any)).toBe(true)
163
+ // })
164
+ // })
165
+ // describe('on targetsSupported', () => {
166
+ // const target = address1
167
+ // const target1 = address2
168
+ // const targetConfig = { contractType: 'erc20', selectors: [] }
169
+
170
+ // it('should return false if model targets are empty', async () => {
171
+ // const model = {
172
+ // intent: { route: { calls: [] }, hash: '0x9' },
173
+ // event: { sourceNetwork: 'opt-sepolia' },
174
+ // } as any
175
+ // const solver = { targets: { address1: { contractType: 'erc20', selectors: [] } } }
176
+ // expect(utilsIntentService.supportedTargets(model, solver as any)).toBe(false)
177
+ // expect(mockLogDebug).toHaveBeenCalledTimes(1)
178
+ // expect(mockLogDebug).toHaveBeenCalledWith({
179
+ // msg: `Targets not supported for intent ${model.intent.hash}`,
180
+ // intentHash: model.intent.hash,
181
+ // sourceNetwork: model.event.sourceNetwork,
182
+ // })
183
+ // })
184
+
185
+ // it('should return false if solver targets are empty', async () => {
186
+ // const model = {
187
+ // intent: { route: { calls: [{ target, data: '0x' }] }, hash: '0x9' },
188
+ // event: { sourceNetwork: 'opt-sepolia' },
189
+ // } as any
190
+ // const solver = { targets: {} }
191
+ // expect(utilsIntentService.supportedTargets(model, solver as any)).toBe(false)
192
+ // expect(mockLogDebug).toHaveBeenCalledTimes(1)
193
+ // expect(mockLogDebug).toHaveBeenCalledWith({
194
+ // msg: `Targets not supported for intent ${model.intent.hash}`,
195
+ // intentHash: model.intent.hash,
196
+ // sourceNetwork: model.event.sourceNetwork,
197
+ // })
198
+ // })
199
+
200
+ // it('should return false if solver doesn`t support the targets of the model', async () => {
201
+ // const model = {
202
+ // intent: { route: { calls: [{ target, data: '0x' }] }, hash: '0x9' },
203
+ // event: { sourceNetwork: 'opt-sepolia' },
204
+ // } as any
205
+ // const solver = { targets: { [target1]: targetConfig } }
206
+ // expect(utilsIntentService.supportedTargets(model, solver as any)).toBe(false)
207
+ // expect(mockLogDebug).toHaveBeenCalledTimes(1)
208
+ // expect(mockLogDebug).toHaveBeenCalledWith({
209
+ // msg: `Targets not supported for intent ${model.intent.hash}`,
210
+ // intentHash: model.intent.hash,
211
+ // sourceNetwork: model.event.sourceNetwork,
212
+ // })
213
+ // })
214
+
215
+ // it('should return true if model targets are a subset of solver targets', async () => {
216
+ // const model = {
217
+ // intent: { route: { calls: [{ target, data: '0x' }] }, hash: '0x9' },
218
+ // event: { sourceNetwork: 'opt-sepolia' },
219
+ // } as any
220
+ // const solver = { targets: { [target]: targetConfig, [target1]: targetConfig } }
221
+ // expect(utilsIntentService.supportedTargets(model, solver as any)).toBe(true)
222
+ // })
223
+ // })
224
+
225
+ describe('on getIntentProcessData', () => {
226
+ const intentHash = address1
227
+ const model = {
228
+ intent: { route: { hash: intentHash, destination: '85432' } },
229
+ event: { sourceNetwork: 'opt-sepolia' },
230
+ } as any
231
+ it('should return undefined if it could not find the model in the db', async () => {
232
+ intentModel.findOne = jest.fn().mockReturnValue(null)
233
+ expect(await utilsIntentService.getIntentProcessData(intentHash)).toStrictEqual({
234
+ err: EcoError.IntentSourceDataNotFound(intentHash),
235
+ model: null,
236
+ solver: null,
237
+ })
238
+ })
239
+
240
+ it('should return undefined if solver could for destination chain could not be found', async () => {
241
+ intentModel.findOne = jest.fn().mockReturnValue(model)
242
+ ecoConfigService.getSolver = jest.fn().mockReturnValue(undefined)
243
+ expect(await utilsIntentService.getIntentProcessData(intentHash)).toBe(undefined)
244
+ expect(mockLogLog).toHaveBeenCalledTimes(1)
245
+ expect(mockLogLog).toHaveBeenCalledWith({
246
+ msg: `No solver found for chain ${model.intent.route.destination}`,
247
+ intentHash: intentHash,
248
+ sourceNetwork: model.event.sourceNetwork,
249
+ })
250
+ })
251
+
252
+ it('should throw an error if model db throws (permissions issue usually)', async () => {
253
+ const mockLogError = jest.fn()
254
+ utilsIntentService['logger'].error = mockLogError
255
+ const err = new Error('DB error')
256
+ intentModel.findOne = jest.fn().mockRejectedValue(err)
257
+ expect(await utilsIntentService.getIntentProcessData(intentHash)).toBe(undefined)
258
+ expect(mockLogError).toHaveBeenCalledTimes(1)
259
+ expect(mockLogError).toHaveBeenCalledWith({
260
+ msg: `Error in getIntentProcessData ${intentHash}`,
261
+ intentHash: intentHash,
262
+ error: err,
263
+ })
264
+ })
265
+
266
+ it('should return the model and solver when successful', async () => {
267
+ intentModel.findOne = jest.fn().mockReturnValue(model)
268
+ const solver = { chainID: '85432' }
269
+ ecoConfigService.getSolver = jest.fn().mockReturnValue(solver)
270
+ expect(await utilsIntentService.getIntentProcessData(intentHash)).toEqual({ model, solver })
271
+ })
272
+ })
273
+
274
+ describe('on updateOnFulfillment', () => {
275
+ const fulfillment = {
276
+ args: {
277
+ _hash: '0x123',
278
+ _solver: '0x456',
279
+ _intent: '0x789',
280
+ _receipt: '0xabc',
281
+ _result: '0xdef',
282
+ },
283
+ } as any as FulfillmentLog
284
+ it('should log a warning if no intent exists in the db for the fulfillment hash', async () => {
285
+ intentModel.findOne = jest.fn().mockReturnValue(undefined)
286
+ await utilsIntentService.updateOnFulfillment(fulfillment)
287
+ expect(mockLogWarn).toHaveBeenCalledTimes(1)
288
+ expect(mockLogWarn).toHaveBeenCalledWith({
289
+ msg: `Intent not found for fulfillment ${fulfillment.args._hash}`,
290
+ fulfillment,
291
+ })
292
+ })
293
+
294
+ it('should update the intent as solved if it exists', async () => {
295
+ const model = {
296
+ face: 1,
297
+ status: 'PENDING',
298
+ }
299
+ intentModel.findOne = jest.fn().mockReturnValue(model)
300
+ await utilsIntentService.updateOnFulfillment(fulfillment)
301
+ expect(intentModel.updateOne).toHaveBeenCalledTimes(1)
302
+ expect(intentModel.updateOne).toHaveBeenCalledWith(
303
+ { 'intent.hash': fulfillment.args._hash },
304
+ { ...model, status: 'SOLVED' },
305
+ )
306
+ })
307
+ })
308
+ })
@@ -0,0 +1,62 @@
1
+ const mockDecodeFunctionData = jest.fn()
2
+ import { EcoError } from '@/common/errors/eco-error'
3
+ import { getFunctionBytes } from '@/common/viem/contracts'
4
+ import { CallDataInterface } from '@/contracts'
5
+ import { getTransactionTargetData } from '@/intent/utils'
6
+
7
+ jest.mock('viem', () => {
8
+ return {
9
+ ...jest.requireActual('viem'),
10
+ decodeFunctionData: mockDecodeFunctionData,
11
+ }
12
+ })
13
+
14
+ const address1 = '0x1111111111111111111111111111111111111111'
15
+ describe('utils tests', () => {
16
+ describe('on getTransactionTargetData', () => {
17
+ const callData: CallDataInterface = { target: address1, data: '0xa9059cbb3333333', value: 0n } //transfer selector plus data fake
18
+ const selectors = ['transfer(address,uint256)']
19
+ const targetConfig = { contractType: 'erc20', selectors }
20
+ const decodedData = { stuff: true }
21
+
22
+ it('should throw when no target config exists on solver', async () => {
23
+ const solver = { targets: {} }
24
+ expect(() => getTransactionTargetData(solver as any, callData)).toThrow(
25
+ EcoError.IntentSourceTargetConfigNotFound(callData.target as string),
26
+ )
27
+ })
28
+
29
+ it('should return null when tx is not decoded ', async () => {
30
+ mockDecodeFunctionData.mockReturnValue(null)
31
+ expect(
32
+ getTransactionTargetData(
33
+ { targets: { [address1]: { contractType: 'erc20', selectors } } } as any,
34
+ callData,
35
+ ),
36
+ ).toBe(null)
37
+ })
38
+
39
+ it('should return null when target selector is not supported by the solver', async () => {
40
+ const fakeData = '0xaaaaaaaa11112333'
41
+ const call: CallDataInterface = { target: callData.target, data: fakeData, value: 0n }
42
+ mockDecodeFunctionData.mockReturnValue(decodedData)
43
+ expect(
44
+ getTransactionTargetData(
45
+ { targets: { [address1]: { contractType: 'erc20', selectors } } } as any,
46
+ call,
47
+ ),
48
+ ).toBe(null)
49
+ })
50
+
51
+ it('should return the decoded function data, selctor and target config when successful', async () => {
52
+ mockDecodeFunctionData.mockReturnValue(decodedData)
53
+ expect(
54
+ getTransactionTargetData({ targets: { [address1]: targetConfig } } as any, callData),
55
+ ).toEqual({
56
+ decodedFunctionData: decodedData,
57
+ selector: getFunctionBytes(callData.data),
58
+ targetConfig,
59
+ })
60
+ })
61
+ })
62
+ })
@@ -0,0 +1,297 @@
1
+ const mockGetIntentJobId = jest.fn()
2
+ import { createMock, DeepMocked } from '@golevelup/ts-jest'
3
+ import { EcoConfigService } from '../../eco-configs/eco-config.service'
4
+ import { Test, TestingModule } from '@nestjs/testing'
5
+ import { getModelToken } from '@nestjs/mongoose'
6
+ import { IntentSourceModel } from '../schemas/intent-source.schema'
7
+ import { Model } from 'mongoose'
8
+ import { ValidateIntentService } from '../validate-intent.service'
9
+ import { UtilsIntentService } from '../utils-intent.service'
10
+ import { BullModule, getQueueToken } from '@nestjs/bullmq'
11
+ import { QUEUES } from '../../common/redis/constants'
12
+ import { Queue } from 'bullmq'
13
+ import { ValidationService } from '@/intent/validation.sevice'
14
+ import { zeroHash } from 'viem'
15
+ import { MultichainPublicClientService } from '@/transaction/multichain-public-client.service'
16
+ import { EcoError } from '@/common/errors/eco-error'
17
+
18
+ jest.mock('../../common/utils/strings', () => {
19
+ return {
20
+ ...jest.requireActual('../../common/utils/strings'),
21
+ getIntentJobId: mockGetIntentJobId,
22
+ }
23
+ })
24
+
25
+ describe('ValidateIntentService', () => {
26
+ let validateIntentService: ValidateIntentService
27
+ let validationService: DeepMocked<ValidationService>
28
+ let multichainPublicClientService: DeepMocked<MultichainPublicClientService>
29
+ let utilsIntentService: DeepMocked<UtilsIntentService>
30
+ let ecoConfigService: DeepMocked<EcoConfigService>
31
+ let queue: DeepMocked<Queue>
32
+ const mockLogDebug = jest.fn()
33
+ const mockLog = jest.fn()
34
+ const mockLogError = jest.fn()
35
+
36
+ beforeEach(async () => {
37
+ const chainMod: TestingModule = await Test.createTestingModule({
38
+ providers: [
39
+ ValidateIntentService,
40
+ {
41
+ provide: UtilsIntentService,
42
+ useValue: createMock<UtilsIntentService>(),
43
+ },
44
+ { provide: ValidationService, useValue: createMock<ValidationService>() },
45
+ {
46
+ provide: MultichainPublicClientService,
47
+ useValue: createMock<MultichainPublicClientService>(),
48
+ },
49
+ { provide: UtilsIntentService, useValue: createMock<UtilsIntentService>() },
50
+ { provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
51
+ {
52
+ provide: getModelToken(IntentSourceModel.name),
53
+ useValue: createMock<Model<IntentSourceModel>>(),
54
+ },
55
+ ],
56
+ imports: [
57
+ BullModule.registerQueue({
58
+ name: QUEUES.SOURCE_INTENT.queue,
59
+ }),
60
+ ],
61
+ })
62
+ .overrideProvider(getQueueToken(QUEUES.SOURCE_INTENT.queue))
63
+ .useValue(createMock<Queue>())
64
+ .compile()
65
+
66
+ validateIntentService = chainMod.get(ValidateIntentService)
67
+ validationService = chainMod.get(ValidationService)
68
+ multichainPublicClientService = chainMod.get(MultichainPublicClientService)
69
+ utilsIntentService = chainMod.get(UtilsIntentService)
70
+ ecoConfigService = chainMod.get(EcoConfigService)
71
+ queue = chainMod.get(getQueueToken(QUEUES.SOURCE_INTENT.queue))
72
+
73
+ validateIntentService['logger'].debug = mockLogDebug
74
+ validateIntentService['logger'].log = mockLog
75
+ validateIntentService['logger'].error = mockLogError
76
+ })
77
+
78
+ afterEach(async () => {
79
+ // restore the spy created with spyOn
80
+ jest.resetAllMocks()
81
+ })
82
+
83
+ describe('on module init', () => {
84
+ it('should set the intentJobConfig', () => {
85
+ const config = { a: 1 } as any
86
+ ecoConfigService.getRedis = jest
87
+ .fn()
88
+ .mockReturnValueOnce({ jobs: { intentJobConfig: config } })
89
+ validateIntentService.onModuleInit()
90
+ expect(validateIntentService['intentJobConfig']).toEqual(config)
91
+ })
92
+ })
93
+
94
+ describe('on destructureIntent', () => {
95
+ it('should throw if get intent returns no data', async () => {
96
+ utilsIntentService.getIntentProcessData.mockResolvedValueOnce(undefined)
97
+ await expect(validateIntentService['destructureIntent'](zeroHash)).rejects.toThrow(
98
+ 'Desctructuring the intent from the intent hash failed',
99
+ )
100
+ })
101
+
102
+ it('should throw if solver is undefined', async () => {
103
+ utilsIntentService.getIntentProcessData.mockResolvedValueOnce({ model: {} } as any)
104
+ await expect(validateIntentService['destructureIntent'](zeroHash)).rejects.toThrow(
105
+ 'Desctructuring the intent from the intent hash failed',
106
+ )
107
+ })
108
+
109
+ it('should throw if model is undefined', async () => {
110
+ utilsIntentService.getIntentProcessData.mockResolvedValueOnce({ solver: {} } as any)
111
+ await expect(validateIntentService['destructureIntent'](zeroHash)).rejects.toThrow(
112
+ 'Desctructuring the intent from the intent hash failed',
113
+ )
114
+ })
115
+
116
+ it('should throw error if its returned', async () => {
117
+ const msg = 'Error from getIntentProcessData'
118
+ utilsIntentService.getIntentProcessData.mockResolvedValueOnce({
119
+ err: new Error(msg),
120
+ } as any)
121
+ await expect(validateIntentService['destructureIntent'](zeroHash)).rejects.toThrow('Error')
122
+ })
123
+
124
+ it('should throw generic error if no error returned', async () => {
125
+ utilsIntentService.getIntentProcessData.mockResolvedValueOnce({} as any)
126
+ await expect(validateIntentService['destructureIntent'](zeroHash)).rejects.toThrow(
127
+ 'Desctructuring the intent from the intent hash failed',
128
+ )
129
+ })
130
+
131
+ it('should succeed and return data', async () => {
132
+ const dataIn = { model: {}, solver: {} } as any
133
+ utilsIntentService.getIntentProcessData.mockResolvedValueOnce(dataIn)
134
+ const dataOut = await validateIntentService['destructureIntent'](zeroHash)
135
+ expect(dataOut).toBe(dataIn)
136
+ })
137
+ })
138
+
139
+ describe('on validateIntent entrypoint', () => {
140
+ it('should log when entering function and return on failed destructure', async () => {
141
+ const intentHash = '0x1'
142
+ validateIntentService['destructureIntent'] = jest
143
+ .fn()
144
+ .mockReturnValueOnce({ model: undefined, solver: undefined })
145
+ expect(await validateIntentService.validateIntent(intentHash)).toBe(false)
146
+ expect(mockLogDebug).toHaveBeenCalledTimes(1)
147
+ expect(mockLogDebug).toHaveBeenCalledWith({ msg: `validateIntent ${intentHash}`, intentHash })
148
+ })
149
+
150
+ it('should return on failed assertions', async () => {
151
+ const intentHash = '0x1'
152
+ validateIntentService['destructureIntent'] = jest
153
+ .fn()
154
+ .mockReturnValueOnce({ model: {}, solver: {} })
155
+ validateIntentService['assertValidations'] = jest.fn().mockReturnValueOnce(false)
156
+ expect(await validateIntentService.validateIntent(intentHash)).toBe(false)
157
+ expect(mockLogDebug).toHaveBeenCalledTimes(1)
158
+ })
159
+
160
+ it('should log, create a job and enque it', async () => {
161
+ const intentHash = '0x1'
162
+ const model = { intent: { logIndex: 10 } }
163
+ const config = { a: 1 } as any
164
+ validateIntentService['destructureIntent'] = jest
165
+ .fn()
166
+ .mockReturnValueOnce({ model, solver: {} })
167
+ validateIntentService['assertValidations'] = jest.fn().mockReturnValueOnce(true)
168
+ validateIntentService['intentJobConfig'] = config
169
+ const mockAddQueue = jest.fn()
170
+ queue.add = mockAddQueue
171
+ const jobId = 'validate-asdf-0'
172
+ mockGetIntentJobId.mockReturnValueOnce(jobId)
173
+ expect(await validateIntentService.validateIntent(intentHash)).toBe(true)
174
+ expect(mockGetIntentJobId).toHaveBeenCalledTimes(1)
175
+ expect(mockAddQueue).toHaveBeenCalledTimes(1)
176
+ expect(mockLogDebug).toHaveBeenCalledTimes(2)
177
+ expect(mockGetIntentJobId).toHaveBeenCalledWith('validate', intentHash, model.intent.logIndex)
178
+ expect(mockAddQueue).toHaveBeenCalledWith(
179
+ QUEUES.SOURCE_INTENT.jobs.feasable_intent,
180
+ intentHash,
181
+ {
182
+ jobId,
183
+ ...validateIntentService['intentJobConfig'],
184
+ },
185
+ )
186
+ expect(mockLogDebug).toHaveBeenCalledWith({
187
+ msg: `validateIntent ${intentHash}`,
188
+ intentHash,
189
+ jobId,
190
+ })
191
+ })
192
+ })
193
+
194
+ describe('on assertValidations', () => {
195
+ const model = { intent: { hash: '0x12' } } as any
196
+ const solver = {} as any
197
+ const validations = { isIntentFunded: false, supportedSelectors: true } as any
198
+ const validValidations = { isIntentFunded: true, supportedSelectors: true } as any
199
+ let mockValidations: jest.Mock
200
+ let mockIntentFunded: jest.Mock
201
+ let mockUpdateModel: jest.Mock
202
+
203
+ beforeEach(() => {
204
+ mockValidations = jest.fn().mockResolvedValueOnce(validations)
205
+ mockIntentFunded = jest.fn().mockResolvedValueOnce(true)
206
+ mockUpdateModel = jest.fn()
207
+
208
+ validationService.assertValidations = mockValidations
209
+ validateIntentService.intentFunded = mockIntentFunded
210
+ utilsIntentService.updateInvalidIntentModel = mockUpdateModel
211
+ })
212
+
213
+ it('should return false if ValidationService is false', async () => {
214
+ expect(await validateIntentService.assertValidations(model, solver)).toBe(false)
215
+ expect(mockValidations).toHaveBeenCalledTimes(1)
216
+ expect(mockIntentFunded).toHaveBeenCalledTimes(1)
217
+ expect(mockUpdateModel).toHaveBeenCalledTimes(1)
218
+ expect(mockLog).toHaveBeenCalledTimes(1)
219
+ expect(mockLog).toHaveBeenCalledWith({
220
+ msg: EcoError.IntentValidationFailed(model.intent.hash).message,
221
+ model,
222
+ validations,
223
+ })
224
+ expect(mockUpdateModel).toHaveBeenCalledWith(model, validations)
225
+ })
226
+
227
+ it('should return false if intentFunded returns false', async () => {
228
+ mockValidations = jest.fn().mockResolvedValueOnce(validValidations)
229
+ mockIntentFunded = jest.fn().mockResolvedValueOnce(false)
230
+ validationService.assertValidations = mockValidations
231
+ validateIntentService.intentFunded = mockIntentFunded
232
+
233
+ expect(await validateIntentService.assertValidations(model, solver)).toBe(false)
234
+ expect(mockValidations).toHaveBeenCalledTimes(1)
235
+ expect(mockIntentFunded).toHaveBeenCalledTimes(1)
236
+ expect(mockUpdateModel).toHaveBeenCalledTimes(1)
237
+ expect(mockLog).toHaveBeenCalledTimes(1)
238
+ expect(mockLog).toHaveBeenCalledWith({
239
+ msg: EcoError.IntentValidationFailed(model.intent.hash).message,
240
+ model,
241
+ validations: validValidations,
242
+ })
243
+ expect(mockUpdateModel).toHaveBeenCalledWith(model, validValidations)
244
+ })
245
+
246
+ it('should return true if intentFunded and ValidationService are all true ', async () => {
247
+ mockValidations = jest.fn().mockResolvedValueOnce(validValidations)
248
+ mockIntentFunded = jest.fn().mockResolvedValueOnce(true)
249
+ validationService.assertValidations = mockValidations
250
+ validateIntentService.intentFunded = mockIntentFunded
251
+
252
+ expect(await validateIntentService.assertValidations(model, solver)).toBe(true)
253
+ expect(mockValidations).toHaveBeenCalledTimes(1)
254
+ expect(mockIntentFunded).toHaveBeenCalledTimes(1)
255
+ expect(mockUpdateModel).toHaveBeenCalledTimes(0)
256
+ expect(mockLog).toHaveBeenCalledTimes(0)
257
+ })
258
+ })
259
+
260
+ describe('on intentFunded', () => {
261
+ const chainID = 1
262
+ const model = { intent: { hash: '0x12', route: { source: chainID } } } as any
263
+
264
+ it('should return false if no intentSource for intent', async () => {
265
+ jest.spyOn(ecoConfigService, 'getIntentSource').mockReturnValue(undefined)
266
+
267
+ expect(await validateIntentService.intentFunded(model)).toBe(false)
268
+ expect(mockLogError).toHaveBeenCalledTimes(1)
269
+ expect(mockLogError).toHaveBeenCalledWith({
270
+ msg: EcoError.IntentSourceNotFound(chainID).message,
271
+ model,
272
+ })
273
+ })
274
+
275
+ it('should return false if isIntentFunded contract call returns false', async () => {
276
+ jest.spyOn(ecoConfigService, 'getIntentSource').mockReturnValue({ face: 'face' } as any)
277
+ const mockRead = jest.fn().mockReturnValue(false)
278
+ const client = { readContract: mockRead }
279
+ multichainPublicClientService.getClient = jest.fn().mockReturnValue(client)
280
+
281
+ expect(await validateIntentService.intentFunded(model)).toBe(false)
282
+ expect(mockLogError).toHaveBeenCalledTimes(0)
283
+ expect(mockRead).toHaveBeenCalledTimes(1)
284
+ })
285
+
286
+ it('should return true if isIntentFunded contract call returns true', async () => {
287
+ jest.spyOn(ecoConfigService, 'getIntentSource').mockReturnValue({ face: 'face' } as any)
288
+ const mockRead = jest.fn().mockReturnValue(true)
289
+ const client = { readContract: mockRead }
290
+ multichainPublicClientService.getClient = jest.fn().mockReturnValue(client)
291
+
292
+ expect(await validateIntentService.intentFunded(model)).toBe(true)
293
+ expect(mockLogError).toHaveBeenCalledTimes(0)
294
+ expect(mockRead).toHaveBeenCalledTimes(1)
295
+ })
296
+ })
297
+ })