eco-solver 0.0.1-security → 1.5.2

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 +43 -0
  17. package/jest.config.ts +14 -0
  18. package/nest-cli.json +8 -0
  19. package/package.json +117 -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,564 @@
1
+ const mockEncodeFunctionData = jest.fn()
2
+ const mockGetTransactionTargetData = jest.fn()
3
+ import { Test, TestingModule } from '@nestjs/testing'
4
+ import { Hex, zeroAddress } from 'viem'
5
+ import { InboxAbi } from '@eco-foundation/routes-ts'
6
+ import { createMock, DeepMocked } from '@golevelup/ts-jest'
7
+ import { EcoConfigService } from '@/eco-configs/eco-config.service'
8
+ import { EcoError } from '@/common/errors/eco-error'
9
+ import { ProofService } from '@/prover/proof.service'
10
+ import { UtilsIntentService } from '../utils-intent.service'
11
+ import { FulfillIntentService } from '../fulfill-intent.service'
12
+ import { KernelAccountClientService } from '@/transaction/smart-wallets/kernel/kernel-account-client.service'
13
+ import { FeeService } from '@/fee/fee.service'
14
+ import { IntentDataModel } from '@/intent/schemas/intent-data.schema'
15
+
16
+ jest.mock('viem', () => {
17
+ return {
18
+ ...jest.requireActual('viem'),
19
+ encodeFunctionData: mockEncodeFunctionData,
20
+ }
21
+ })
22
+
23
+ jest.mock('@/intent/utils', () => {
24
+ return {
25
+ ...jest.requireActual('@/intent/utils'),
26
+ getTransactionTargetData: mockGetTransactionTargetData,
27
+ }
28
+ })
29
+ describe('FulfillIntentService', () => {
30
+ const address1 = '0x1111111111111111111111111111111111111111'
31
+ const address2 = '0x2222222222222222222222222222222222222222'
32
+
33
+ let fulfillIntentService: FulfillIntentService
34
+ let accountClientService: DeepMocked<KernelAccountClientService>
35
+ let proofService: DeepMocked<ProofService>
36
+ let feeService: DeepMocked<FeeService>
37
+ let utilsIntentService: DeepMocked<UtilsIntentService>
38
+ let ecoConfigService: DeepMocked<EcoConfigService>
39
+ let mockFinalFeasibilityCheck: jest.SpyInstance<Promise<void>, [intent: IntentDataModel], any>
40
+ const mockLogDebug = jest.fn()
41
+ const mockLogLog = jest.fn()
42
+ const mockLogError = jest.fn()
43
+
44
+ beforeEach(async () => {
45
+ const chainMod: TestingModule = await Test.createTestingModule({
46
+ providers: [
47
+ FulfillIntentService,
48
+ { provide: KernelAccountClientService, useValue: createMock<KernelAccountClientService>() },
49
+ { provide: ProofService, useValue: createMock<ProofService>() },
50
+ { provide: FeeService, useValue: createMock<FeeService>() },
51
+ { provide: UtilsIntentService, useValue: createMock<UtilsIntentService>() },
52
+ { provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
53
+ ],
54
+ }).compile()
55
+
56
+ fulfillIntentService = chainMod.get(FulfillIntentService)
57
+ accountClientService = chainMod.get(KernelAccountClientService)
58
+ proofService = chainMod.get(ProofService)
59
+ feeService = chainMod.get(FeeService)
60
+ utilsIntentService = chainMod.get(UtilsIntentService)
61
+ ecoConfigService = chainMod.get(EcoConfigService)
62
+
63
+ fulfillIntentService['logger'].debug = mockLogDebug
64
+ fulfillIntentService['logger'].log = mockLogLog
65
+ fulfillIntentService['logger'].error = mockLogError
66
+
67
+ // make sure it returns something real
68
+ fulfillIntentService['getHyperlaneFee'] = jest.fn().mockReturnValue(0n)
69
+ })
70
+ const hash = address1
71
+ const claimant = address2
72
+ const solver = { inboxAddress: address1, chainID: 1 }
73
+ const model = {
74
+ intent: {
75
+ route: { hash, destination: 85432, getHash: () => '0x6543' },
76
+ reward: { getHash: () => '0x123abc' },
77
+ getHash: () => {
78
+ return { intentHash: '0xaaaa999' }
79
+ },
80
+ },
81
+ event: { sourceChainID: 11111 },
82
+ }
83
+ const emptyTxs = [{ data: undefined, to: hash, value: 0n }]
84
+
85
+ beforeEach(async () => {
86
+ //dont have it throw
87
+ mockFinalFeasibilityCheck = jest
88
+ .spyOn(fulfillIntentService, 'finalFeasibilityCheck')
89
+ .mockResolvedValue()
90
+ })
91
+ afterEach(async () => {
92
+ // restore the spy created with spyOn
93
+ jest.restoreAllMocks()
94
+ mockLogDebug.mockClear()
95
+ mockLogLog.mockClear()
96
+ mockLogError.mockClear()
97
+ mockEncodeFunctionData.mockClear()
98
+ delete (model as any).status
99
+ delete (model as any).receipt
100
+ })
101
+
102
+ describe('on executeFulfillIntent', () => {
103
+ describe('on setup', () => {
104
+ it('should throw if data can`t be destructured', async () => {
105
+ //when error
106
+ const error = new Error('stuff went bad')
107
+ utilsIntentService.getIntentProcessData = jest.fn().mockResolvedValue({ err: error })
108
+ await expect(() => fulfillIntentService.executeFulfillIntent(hash)).rejects.toThrow(error)
109
+ })
110
+
111
+ it('should set the claimant for the fulfill', async () => {
112
+ jest.spyOn(accountClientService, 'getClient').mockImplementation((): any =>
113
+ Promise.resolve({
114
+ execute: jest.fn().mockResolvedValue(hash),
115
+ waitForTransactionReceipt: jest.fn().mockResolvedValue({ transactionHash: hash }),
116
+ }),
117
+ )
118
+
119
+ utilsIntentService.getIntentProcessData = jest.fn().mockResolvedValue({ model, solver })
120
+ const mockGetFulfillIntentTx = jest.fn()
121
+ fulfillIntentService['getFulfillIntentTx'] = mockGetFulfillIntentTx
122
+ fulfillIntentService['getTransactionsForTargets'] = jest.fn().mockReturnValue([])
123
+ jest.spyOn(ecoConfigService, 'getEth').mockReturnValue({ claimant } as any)
124
+ expect(await fulfillIntentService.executeFulfillIntent(hash)).toBeUndefined()
125
+ expect(mockGetFulfillIntentTx).toHaveBeenCalledWith(solver.inboxAddress, model)
126
+ })
127
+
128
+ it('should throw if the finalFeasibilityCheck throws', async () => {
129
+ const error = new Error('stuff went bad')
130
+ utilsIntentService.getIntentProcessData = jest.fn().mockResolvedValue({ model, solver })
131
+ const mockGetFulfillIntentTx = jest.fn()
132
+ fulfillIntentService['getFulfillIntentTx'] = mockGetFulfillIntentTx
133
+ fulfillIntentService['getTransactionsForTargets'] = jest.fn().mockReturnValue([])
134
+ jest.spyOn(ecoConfigService, 'getEth').mockReturnValue({ claimant } as any)
135
+ jest.spyOn(fulfillIntentService, 'finalFeasibilityCheck').mockImplementation(async () => {
136
+ throw error
137
+ })
138
+
139
+ await expect(() => fulfillIntentService.executeFulfillIntent(hash)).rejects.toThrow(error)
140
+ })
141
+ })
142
+
143
+ describe('on failed execution', () => {
144
+ it('should bubble up the thrown error', async () => {
145
+ const error = new Error('stuff went bad')
146
+ utilsIntentService.getIntentProcessData = jest.fn().mockResolvedValue({ model, solver })
147
+ fulfillIntentService['getFulfillIntentData'] = jest.fn()
148
+ fulfillIntentService['getTransactionsForTargets'] = jest.fn().mockReturnValue([])
149
+ jest.spyOn(ecoConfigService, 'getEth').mockReturnValue({ claimant } as any)
150
+ jest.spyOn(accountClientService, 'getClient').mockImplementation(async () => {
151
+ return {
152
+ execute: () => {
153
+ throw error
154
+ },
155
+ } as any
156
+ })
157
+ await expect(() => fulfillIntentService.executeFulfillIntent(hash)).rejects.toThrow(error)
158
+ })
159
+
160
+ it('should fail on receipt status reverted', async () => {
161
+ const receipt = { status: 'reverted' }
162
+ const error = EcoError.FulfillIntentRevertError(receipt as any)
163
+ utilsIntentService.getIntentProcessData = jest.fn().mockResolvedValue({ model, solver })
164
+ fulfillIntentService['getFulfillIntentData'] = jest.fn()
165
+ fulfillIntentService['getTransactionsForTargets'] = jest.fn().mockReturnValue([])
166
+ jest.spyOn(ecoConfigService, 'getEth').mockReturnValue({ claimant } as any)
167
+ jest.spyOn(accountClientService, 'getClient').mockImplementation(async () => {
168
+ return {
169
+ execute: () => {
170
+ return '0x33'
171
+ },
172
+ waitForTransactionReceipt: () => {
173
+ return receipt
174
+ },
175
+ } as any
176
+ })
177
+ await expect(() => fulfillIntentService.executeFulfillIntent(hash)).rejects.toThrow(error)
178
+ expect(mockLogError).toHaveBeenCalledTimes(1)
179
+ expect((model as any).status).toBe('FAILED')
180
+ expect(mockLogError).toHaveBeenCalledWith({
181
+ msg: `fulfillIntent: Invalid transaction`,
182
+ error: EcoError.FulfillIntentBatchError.toString(),
183
+ model,
184
+ errorPassed: error,
185
+ flatExecuteData: emptyTxs,
186
+ })
187
+ })
188
+
189
+ it('should log error', async () => {
190
+ const error = new Error('stuff went bad')
191
+ utilsIntentService.getIntentProcessData = jest.fn().mockResolvedValue({ model, solver })
192
+ fulfillIntentService['getFulfillIntentData'] = jest.fn()
193
+ fulfillIntentService['getTransactionsForTargets'] = jest.fn().mockReturnValue([])
194
+ jest.spyOn(ecoConfigService, 'getEth').mockReturnValue({ claimant } as any)
195
+ jest.spyOn(accountClientService, 'getClient').mockImplementation(async () => {
196
+ return {
197
+ execute: () => {
198
+ throw error
199
+ },
200
+ } as any
201
+ })
202
+ await expect(() => fulfillIntentService.executeFulfillIntent(hash)).rejects.toThrow(error)
203
+ expect(mockLogError).toHaveBeenCalledTimes(1)
204
+ expect(mockLogError).toHaveBeenCalledWith({
205
+ msg: `fulfillIntent: Invalid transaction`,
206
+ error: EcoError.FulfillIntentBatchError.toString(),
207
+ model,
208
+ errorPassed: error,
209
+ flatExecuteData: emptyTxs,
210
+ })
211
+ })
212
+
213
+ it('should update the db model with status and error receipt', async () => {
214
+ const error = new Error('stuff went bad')
215
+ utilsIntentService.getIntentProcessData = jest.fn().mockResolvedValue({ model, solver })
216
+ fulfillIntentService['getFulfillIntentData'] = jest.fn()
217
+ fulfillIntentService['getTransactionsForTargets'] = jest.fn().mockReturnValue([])
218
+ jest.spyOn(ecoConfigService, 'getEth').mockReturnValue({ claimant } as any)
219
+ jest.spyOn(accountClientService, 'getClient').mockImplementation(async () => {
220
+ return {
221
+ execute: () => {
222
+ throw error
223
+ },
224
+ } as any
225
+ })
226
+ await expect(() => fulfillIntentService.executeFulfillIntent(hash)).rejects.toThrow(error)
227
+ expect(utilsIntentService.updateIntentModel).toHaveBeenCalledTimes(1)
228
+ expect(utilsIntentService.updateIntentModel).toHaveBeenCalledWith({
229
+ ...model,
230
+ status: 'FAILED',
231
+ receipt: error,
232
+ })
233
+
234
+ //check error stacking
235
+ const error2 = new Error('stuff went bad a second time')
236
+ jest.spyOn(accountClientService, 'getClient').mockImplementation(async () => {
237
+ return {
238
+ execute: () => {
239
+ throw error2
240
+ },
241
+ } as any
242
+ })
243
+
244
+ await expect(() => fulfillIntentService.executeFulfillIntent(hash)).rejects.toThrow(error2)
245
+ expect(utilsIntentService.updateIntentModel).toHaveBeenCalledTimes(2)
246
+ expect(utilsIntentService.updateIntentModel).toHaveBeenLastCalledWith({
247
+ ...model,
248
+ status: 'FAILED',
249
+ receipt: { previous: error, current: error2 },
250
+ })
251
+ })
252
+ })
253
+
254
+ describe('on successful execution', () => {
255
+ const transactionHash = '0x33'
256
+ const mockExecute = jest.fn()
257
+ const mockWaitForTransactionReceipt = jest.fn()
258
+ const mockGetIntentProcessData = jest.fn()
259
+ beforeEach(async () => {
260
+ fulfillIntentService['getFulfillIntentTx'] = jest.fn().mockReturnValue(emptyTxs)
261
+ utilsIntentService.getIntentProcessData = mockGetIntentProcessData.mockResolvedValue({
262
+ model,
263
+ solver,
264
+ })
265
+ fulfillIntentService['getTransactionsForTargets'] = jest.fn().mockReturnValue([])
266
+
267
+ jest.spyOn(accountClientService, 'getClient').mockImplementation(async () => {
268
+ return {
269
+ execute: mockExecute.mockResolvedValue(transactionHash),
270
+ waitForTransactionReceipt: mockWaitForTransactionReceipt.mockResolvedValue({
271
+ transactionHash,
272
+ }),
273
+ } as any
274
+ })
275
+
276
+ expect(await fulfillIntentService.executeFulfillIntent(hash)).resolves
277
+ expect(fulfillIntentService['getTransactionsForTargets']).toHaveBeenCalledTimes(1)
278
+ expect(fulfillIntentService['getFulfillIntentTx']).toHaveBeenCalledTimes(1)
279
+ })
280
+
281
+ afterEach(() => {
282
+ jest.restoreAllMocks()
283
+ mockExecute.mockClear()
284
+ mockWaitForTransactionReceipt.mockClear()
285
+ mockGetIntentProcessData.mockClear()
286
+ })
287
+
288
+ it('should execute the transactions', async () => {
289
+ expect(mockExecute).toHaveBeenCalledTimes(1)
290
+ expect(mockExecute).toHaveBeenCalledWith([emptyTxs])
291
+ })
292
+
293
+ it('should get a receipt', async () => {
294
+ expect(mockWaitForTransactionReceipt).toHaveBeenCalledTimes(1)
295
+ expect(mockWaitForTransactionReceipt).toHaveBeenCalledWith({ hash: transactionHash })
296
+ })
297
+
298
+ it('should log', async () => {
299
+ expect(mockLogDebug).toHaveBeenCalledTimes(2)
300
+ expect(mockLogDebug).toHaveBeenNthCalledWith(2, {
301
+ msg: `Fulfilled transactionHash ${transactionHash}`,
302
+ userOPHash: { transactionHash },
303
+ destinationChainID: model.intent.route.destination,
304
+ sourceChainID: model.event.sourceChainID,
305
+ })
306
+ })
307
+
308
+ it('should update the db model with status and receipt', async () => {
309
+ expect(utilsIntentService.updateIntentModel).toHaveBeenCalledTimes(1)
310
+ expect(utilsIntentService.updateIntentModel).toHaveBeenCalledWith({
311
+ ...model,
312
+ status: 'SOLVED',
313
+ receipt: { transactionHash },
314
+ })
315
+ })
316
+ })
317
+ })
318
+
319
+ describe('on finalFeasibilityCheck', () => {
320
+ const error = new Error('stuff went bad')
321
+ beforeEach(async () => {
322
+ mockFinalFeasibilityCheck.mockRestore()
323
+ })
324
+ it('should throw if the model is not feasible', async () => {
325
+ jest.spyOn(feeService, 'isRouteFeasible').mockResolvedValue({ error })
326
+ await expect(fulfillIntentService.finalFeasibilityCheck({} as any)).rejects.toThrow(error)
327
+ })
328
+
329
+ it('should not throw if the model is feasible', async () => {
330
+ jest.spyOn(feeService, 'isRouteFeasible').mockResolvedValue({ error: undefined })
331
+ await expect(fulfillIntentService.finalFeasibilityCheck({} as any)).resolves.not.toThrow()
332
+ })
333
+ })
334
+
335
+ describe('on handleErc20', () => {
336
+ const selector = '0xa9059cbb'
337
+ const inboxAddress = '0x131'
338
+ const target = '0x9'
339
+ const amount = 100n
340
+ it('should return empty on unsupported selector', async () => {
341
+ expect(
342
+ fulfillIntentService.handleErc20(
343
+ { selector: address1, targetConfig: {} } as any,
344
+ {} as any,
345
+ '0x0',
346
+ ),
347
+ ).toEqual([])
348
+ })
349
+
350
+ it('should return the approve selector with data correctly encoded', async () => {
351
+ const transferFunctionData = '0x9911'
352
+ mockEncodeFunctionData.mockReturnValue(transferFunctionData)
353
+ expect(
354
+ fulfillIntentService.handleErc20(
355
+ { selector, decodedFunctionData: { args: [, amount] } } as any,
356
+ { inboxAddress } as any,
357
+ target,
358
+ ),
359
+ ).toEqual([{ to: target, data: transferFunctionData }])
360
+ expect(mockEncodeFunctionData).toHaveBeenCalledWith({
361
+ abi: expect.anything(),
362
+ functionName: 'approve',
363
+ args: [inboxAddress, amount],
364
+ })
365
+ })
366
+ })
367
+
368
+ describe('on getTransactionsForTargets', () => {
369
+ const model = { intent: { route: { calls: [{ target: address1, data: address2 }] } } }
370
+ const tt = { targetConfig: { contractType: 'erc20' } }
371
+ it('should return empty if input is invalid', async () => {
372
+ expect(fulfillIntentService['getTransactionsForTargets']({} as any)).toEqual([])
373
+ expect(fulfillIntentService['getTransactionsForTargets']({ model: { a: 1 } } as any)).toEqual(
374
+ [],
375
+ )
376
+ expect(
377
+ fulfillIntentService['getTransactionsForTargets']({ solver: { b: 2 } } as any),
378
+ ).toEqual([])
379
+ })
380
+
381
+ it('should return empty if no targets', async () => {
382
+ expect(
383
+ fulfillIntentService['getTransactionsForTargets']({
384
+ model: { intent: { targets: [] } },
385
+ } as any),
386
+ ).toEqual([])
387
+ })
388
+
389
+ it('should return empty item for invalid transaction target data', async () => {
390
+ mockGetTransactionTargetData.mockReturnValue(null)
391
+ expect(fulfillIntentService['getTransactionsForTargets']({ model, solver } as any)).toEqual(
392
+ [],
393
+ )
394
+ expect(mockGetTransactionTargetData).toHaveBeenCalledWith(solver, model.intent.route.calls[0])
395
+ expect(mockLogError).toHaveBeenCalledTimes(1)
396
+ expect(mockLogError).toHaveBeenCalledWith({
397
+ msg: `fulfillIntent: Invalid transaction data`,
398
+ error: EcoError.FulfillIntentNoTransactionError.toString(),
399
+ model,
400
+ })
401
+ })
402
+
403
+ it('should return empty for erc721, erc1155, or anything other than erc20', async () => {
404
+ //erc721
405
+ mockGetTransactionTargetData.mockReturnValue({ targetConfig: { contractType: 'erc721' } })
406
+ expect(fulfillIntentService['getTransactionsForTargets']({ model, solver } as any)).toEqual(
407
+ [],
408
+ )
409
+
410
+ //erc1155
411
+ mockGetTransactionTargetData.mockReturnValue({ targetConfig: { contractType: 'erc1155' } })
412
+ expect(fulfillIntentService['getTransactionsForTargets']({ model, solver } as any)).toEqual(
413
+ [],
414
+ )
415
+
416
+ //default/catch-all
417
+ mockGetTransactionTargetData.mockReturnValue({ targetConfig: { contractType: 'face' } })
418
+ expect(fulfillIntentService['getTransactionsForTargets']({ model, solver } as any)).toEqual(
419
+ [],
420
+ )
421
+ })
422
+
423
+ it('should return correct data for erc20', async () => {
424
+ const mockHandleErc20Data = [{ to: address1, data: address2 }]
425
+ mockGetTransactionTargetData.mockReturnValue(tt)
426
+ fulfillIntentService.handleErc20 = jest.fn().mockReturnValue(mockHandleErc20Data)
427
+ expect(fulfillIntentService['getTransactionsForTargets']({ model, solver } as any)).toEqual(
428
+ mockHandleErc20Data,
429
+ )
430
+ })
431
+
432
+ it('should process multiple targets', async () => {
433
+ const model = {
434
+ intent: {
435
+ route: {
436
+ calls: [
437
+ { target: address1, data: '0x3' },
438
+ { target: address2, data: '0x4' },
439
+ ],
440
+ },
441
+ },
442
+ }
443
+ const mockHandleErc20Data = [
444
+ { to: '0x11', data: '0x22' },
445
+ { to: '0x33', data: '0x44' },
446
+ ]
447
+ mockGetTransactionTargetData.mockReturnValue(tt)
448
+ fulfillIntentService.handleErc20 = jest.fn().mockImplementation((tt, solver, target) => {
449
+ if (target === model.intent.route.calls[0].target) return mockHandleErc20Data[0]
450
+ if (target === model.intent.route.calls[1].target) return mockHandleErc20Data[1]
451
+ })
452
+ expect(fulfillIntentService['getTransactionsForTargets']({ model, solver } as any)).toEqual(
453
+ mockHandleErc20Data,
454
+ )
455
+ })
456
+ })
457
+
458
+ describe('on getFulfillIntentTx', () => {
459
+ const model = {
460
+ intent: {
461
+ hash: '0x1234',
462
+ route: {
463
+ calls: [{ target: address1, data: address2 }],
464
+ deadline: '0x2233',
465
+ salt: '0x3344',
466
+ getHash: () => '0xccc',
467
+ },
468
+ reward: {
469
+ prover: '0x1122',
470
+ getHash: () => '0xab33',
471
+ },
472
+ getHash: () => {
473
+ return { intentHash: '0xaaaa999' }
474
+ },
475
+ },
476
+ event: { sourceChainID: 10 },
477
+ }
478
+ const solver = { inboxAddress: '0x9' as Hex }
479
+ let defaultArgs = [] as any
480
+ const mockFee = 10n
481
+ beforeEach(() => {
482
+ jest.spyOn(ecoConfigService, 'getEth').mockReturnValue({ claimant } as any)
483
+ fulfillIntentService['getHyperlaneFee'] = jest.fn().mockResolvedValue(mockFee)
484
+ defaultArgs = [
485
+ model.intent.route,
486
+ model.intent.reward.getHash(),
487
+ claimant,
488
+ model.intent.getHash().intentHash,
489
+ ]
490
+ })
491
+ describe('on PROOF_STORAGE', () => {
492
+ it('should use the correct function name and args', async () => {
493
+ const mockStorage = jest.fn().mockReturnValue(true)
494
+ const mockHyperlane = jest.fn().mockReturnValue(false)
495
+ proofService.isStorageProver = mockStorage
496
+ proofService.isHyperlaneProver = mockHyperlane
497
+ await fulfillIntentService['getFulfillIntentTx'](solver.inboxAddress, model as any)
498
+ expect(proofService.isStorageProver).toHaveBeenCalledTimes(1)
499
+ expect(proofService.isStorageProver).toHaveBeenCalledWith(model.intent.reward.prover)
500
+ expect(proofService.isHyperlaneProver).toHaveBeenCalledTimes(1)
501
+ expect(proofService.isHyperlaneProver).toHaveBeenCalledWith(model.intent.reward.prover)
502
+ expect(mockEncodeFunctionData).toHaveBeenCalledWith({
503
+ abi: InboxAbi,
504
+ functionName: 'fulfillStorage',
505
+ args: defaultArgs,
506
+ })
507
+ })
508
+ })
509
+
510
+ describe('on PROOF_HYPERLANE', () => {
511
+ it('should use the correct function name and args for fulfillHyperInstantWithRelayer', async () => {
512
+ const data = '0x9911'
513
+ jest.spyOn(proofService, 'isStorageProver').mockReturnValue(false)
514
+ jest.spyOn(proofService, 'isHyperlaneProver').mockReturnValue(true)
515
+ mockEncodeFunctionData.mockReturnValue(data)
516
+ fulfillIntentService['getFulfillment'] = jest
517
+ .fn()
518
+ .mockReturnValue('fulfillHyperInstantWithRelayer')
519
+ defaultArgs.push(model.intent.reward.prover)
520
+ defaultArgs.push('0x0')
521
+ defaultArgs.push(zeroAddress)
522
+ const tx = await fulfillIntentService['getFulfillIntentTx'](
523
+ solver.inboxAddress,
524
+ model as any,
525
+ )
526
+ expect(tx).toEqual({ to: solver.inboxAddress, data, value: mockFee })
527
+ expect(proofService.isStorageProver).toHaveBeenCalledTimes(1)
528
+ expect(proofService.isStorageProver).toHaveBeenCalledWith(model.intent.reward.prover)
529
+ expect(proofService.isHyperlaneProver).toHaveBeenCalledTimes(1)
530
+ expect(proofService.isHyperlaneProver).toHaveBeenCalledWith(model.intent.reward.prover)
531
+ expect(mockEncodeFunctionData).toHaveBeenCalledTimes(1)
532
+ expect(mockEncodeFunctionData).toHaveBeenCalledWith({
533
+ abi: InboxAbi,
534
+ functionName: 'fulfillHyperInstantWithRelayer',
535
+ args: defaultArgs,
536
+ })
537
+ })
538
+
539
+ it('should use the correct function name and args for fulfillHyperBatched', async () => {
540
+ const data = '0x9911'
541
+ jest.spyOn(proofService, 'isStorageProver').mockReturnValue(false)
542
+ jest.spyOn(proofService, 'isHyperlaneProver').mockReturnValue(true)
543
+ mockEncodeFunctionData.mockReturnValue(data)
544
+ fulfillIntentService['getFulfillment'] = jest.fn().mockReturnValue('fulfillHyperBatched')
545
+ defaultArgs.push(model.intent.reward.prover)
546
+ const tx = await fulfillIntentService['getFulfillIntentTx'](
547
+ solver.inboxAddress,
548
+ model as any,
549
+ )
550
+ expect(tx).toEqual({ to: solver.inboxAddress, data, value: 0n })
551
+ expect(proofService.isStorageProver).toHaveBeenCalledTimes(1)
552
+ expect(proofService.isStorageProver).toHaveBeenCalledWith(model.intent.reward.prover)
553
+ expect(proofService.isHyperlaneProver).toHaveBeenCalledTimes(1)
554
+ expect(proofService.isHyperlaneProver).toHaveBeenCalledWith(model.intent.reward.prover)
555
+ expect(mockEncodeFunctionData).toHaveBeenCalledTimes(1)
556
+ expect(mockEncodeFunctionData).toHaveBeenCalledWith({
557
+ abi: InboxAbi,
558
+ functionName: 'fulfillHyperBatched',
559
+ args: defaultArgs,
560
+ })
561
+ })
562
+ })
563
+ })
564
+ })