@agirails/sdk 2.0.0-beta
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.
- package/README.md +183 -0
- package/dist/ACTPClient.d.ts +52 -0
- package/dist/ACTPClient.d.ts.map +1 -0
- package/dist/ACTPClient.js +120 -0
- package/dist/ACTPClient.js.map +1 -0
- package/dist/abi/ACTPKernel.json +1340 -0
- package/dist/abi/ERC20.json +38 -0
- package/dist/abi/EscrowVault.json +64 -0
- package/dist/builders/DeliveryProofBuilder.d.ts +37 -0
- package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -0
- package/dist/builders/DeliveryProofBuilder.js +165 -0
- package/dist/builders/DeliveryProofBuilder.js.map +1 -0
- package/dist/builders/QuoteBuilder.d.ts +68 -0
- package/dist/builders/QuoteBuilder.d.ts.map +1 -0
- package/dist/builders/QuoteBuilder.js +255 -0
- package/dist/builders/QuoteBuilder.js.map +1 -0
- package/dist/builders/index.d.ts +3 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +10 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/config/networks.d.ts +27 -0
- package/dist/config/networks.d.ts.map +1 -0
- package/dist/config/networks.js +103 -0
- package/dist/config/networks.js.map +1 -0
- package/dist/errors/index.d.ts +38 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +87 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/ACTPKernel.d.ts +30 -0
- package/dist/protocol/ACTPKernel.d.ts.map +1 -0
- package/dist/protocol/ACTPKernel.js +261 -0
- package/dist/protocol/ACTPKernel.js.map +1 -0
- package/dist/protocol/EASHelper.d.ts +23 -0
- package/dist/protocol/EASHelper.d.ts.map +1 -0
- package/dist/protocol/EASHelper.js +106 -0
- package/dist/protocol/EASHelper.js.map +1 -0
- package/dist/protocol/EscrowVault.d.ts +24 -0
- package/dist/protocol/EscrowVault.d.ts.map +1 -0
- package/dist/protocol/EscrowVault.js +114 -0
- package/dist/protocol/EscrowVault.js.map +1 -0
- package/dist/protocol/EventMonitor.d.ts +18 -0
- package/dist/protocol/EventMonitor.d.ts.map +1 -0
- package/dist/protocol/EventMonitor.js +92 -0
- package/dist/protocol/EventMonitor.js.map +1 -0
- package/dist/protocol/MessageSigner.d.ts +23 -0
- package/dist/protocol/MessageSigner.d.ts.map +1 -0
- package/dist/protocol/MessageSigner.js +178 -0
- package/dist/protocol/MessageSigner.js.map +1 -0
- package/dist/protocol/ProofGenerator.d.ts +22 -0
- package/dist/protocol/ProofGenerator.d.ts.map +1 -0
- package/dist/protocol/ProofGenerator.js +64 -0
- package/dist/protocol/ProofGenerator.js.map +1 -0
- package/dist/protocol/QuoteBuilder.d.ts +2 -0
- package/dist/protocol/QuoteBuilder.d.ts.map +1 -0
- package/dist/protocol/QuoteBuilder.js +7 -0
- package/dist/protocol/QuoteBuilder.js.map +1 -0
- package/dist/types/eip712.d.ts +106 -0
- package/dist/types/eip712.d.ts.map +1 -0
- package/dist/types/eip712.js +84 -0
- package/dist/types/eip712.js.map +1 -0
- package/dist/types/escrow.d.ts +18 -0
- package/dist/types/escrow.d.ts.map +1 -0
- package/dist/types/escrow.js +3 -0
- package/dist/types/escrow.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +22 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/message.d.ts +109 -0
- package/dist/types/message.d.ts.map +1 -0
- package/dist/types/message.js +3 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/state.d.ts +19 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +49 -0
- package/dist/types/state.js.map +1 -0
- package/dist/types/transaction.d.ts +36 -0
- package/dist/types/transaction.d.ts.map +1 -0
- package/dist/types/transaction.js +3 -0
- package/dist/types/transaction.js.map +1 -0
- package/dist/utils/IPFSClient.d.ts +37 -0
- package/dist/utils/IPFSClient.d.ts.map +1 -0
- package/dist/utils/IPFSClient.js +128 -0
- package/dist/utils/IPFSClient.js.map +1 -0
- package/dist/utils/NonceManager.d.ts +34 -0
- package/dist/utils/NonceManager.d.ts.map +1 -0
- package/dist/utils/NonceManager.js +114 -0
- package/dist/utils/NonceManager.js.map +1 -0
- package/dist/utils/ReceivedNonceTracker.d.ts +35 -0
- package/dist/utils/ReceivedNonceTracker.d.ts.map +1 -0
- package/dist/utils/ReceivedNonceTracker.js +196 -0
- package/dist/utils/ReceivedNonceTracker.js.map +1 -0
- package/dist/utils/canonicalJson.d.ts +4 -0
- package/dist/utils/canonicalJson.d.ts.map +1 -0
- package/dist/utils/canonicalJson.js +21 -0
- package/dist/utils/canonicalJson.js.map +1 -0
- package/dist/utils/computeTypeHash.d.ts +3 -0
- package/dist/utils/computeTypeHash.d.ts.map +1 -0
- package/dist/utils/computeTypeHash.js +30 -0
- package/dist/utils/computeTypeHash.js.map +1 -0
- package/dist/utils/validation.d.ts +6 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +46 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +73 -0
- package/src/ACTPClient.ts +276 -0
- package/src/__tests__/ProofGenerator.test.ts +124 -0
- package/src/__tests__/QuoteBuilder.test.ts +516 -0
- package/src/__tests__/StateMachine.test.ts +82 -0
- package/src/__tests__/builders/DeliveryProofBuilder.test.ts +581 -0
- package/src/__tests__/integration/ACTPClient.test.ts +263 -0
- package/src/__tests__/integration.test.ts +289 -0
- package/src/__tests__/protocol/EASHelper.test.ts +472 -0
- package/src/__tests__/protocol/EventMonitor.test.ts +382 -0
- package/src/__tests__/security/ACTPKernel.security.test.ts +1167 -0
- package/src/__tests__/security/EscrowVault.security.test.ts +570 -0
- package/src/__tests__/security/MessageSigner.security.test.ts +286 -0
- package/src/__tests__/security/NonceReplay.security.test.ts +501 -0
- package/src/__tests__/security/validation.security.test.ts +376 -0
- package/src/__tests__/utils/IPFSClient.test.ts +262 -0
- package/src/__tests__/utils/NonceManager.test.ts +205 -0
- package/src/__tests__/utils/canonicalJson.test.ts +153 -0
- package/src/abi/ACTPKernel.json +1340 -0
- package/src/abi/ERC20.json +40 -0
- package/src/abi/EscrowVault.json +66 -0
- package/src/builders/DeliveryProofBuilder.ts +326 -0
- package/src/builders/QuoteBuilder.ts +483 -0
- package/src/builders/index.ts +17 -0
- package/src/config/networks.ts +165 -0
- package/src/errors/index.ts +130 -0
- package/src/index.ts +108 -0
- package/src/protocol/ACTPKernel.ts +625 -0
- package/src/protocol/EASHelper.ts +197 -0
- package/src/protocol/EscrowVault.ts +237 -0
- package/src/protocol/EventMonitor.ts +161 -0
- package/src/protocol/MessageSigner.ts +336 -0
- package/src/protocol/ProofGenerator.ts +119 -0
- package/src/protocol/QuoteBuilder.ts +15 -0
- package/src/types/eip712.ts +175 -0
- package/src/types/escrow.ts +26 -0
- package/src/types/index.ts +10 -0
- package/src/types/message.ts +145 -0
- package/src/types/state.ts +77 -0
- package/src/types/transaction.ts +54 -0
- package/src/utils/IPFSClient.ts +248 -0
- package/src/utils/NonceManager.ts +293 -0
- package/src/utils/ReceivedNonceTracker.ts +397 -0
- package/src/utils/canonicalJson.ts +38 -0
- package/src/utils/computeTypeHash.ts +50 -0
- package/src/utils/validation.ts +82 -0
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACTPKernel Security Test Suite
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: This module manages transaction state machine and escrow lifecycle
|
|
5
|
+
* Coverage Target: 90%+ (statements, functions, lines), 85%+ (branches)
|
|
6
|
+
*
|
|
7
|
+
* Security Test Categories:
|
|
8
|
+
* 1. State Transition Security (15 tests)
|
|
9
|
+
* 2. Access Control Enforcement (10 tests)
|
|
10
|
+
*
|
|
11
|
+
* References:
|
|
12
|
+
* - Security Analysis: /Testnet/tests/SDK_SECURITY_ANALYSIS-Ultra-Think.md
|
|
13
|
+
* - V2: ACTPKernel State Transition TOCTOU vulnerability
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { ACTPKernel } from '../../protocol/ACTPKernel';
|
|
17
|
+
import { State } from '../../types';
|
|
18
|
+
import { AbiCoder } from 'ethers';
|
|
19
|
+
|
|
20
|
+
// Mock ethers Contract
|
|
21
|
+
const mockContract = {
|
|
22
|
+
estimateGas: {
|
|
23
|
+
createTransaction: jest.fn().mockResolvedValue(BigInt(85000)),
|
|
24
|
+
transitionState: jest.fn().mockResolvedValue(BigInt(50000)),
|
|
25
|
+
linkEscrow: jest.fn().mockResolvedValue(BigInt(45000)),
|
|
26
|
+
releaseEscrow: jest.fn().mockResolvedValue(BigInt(50000)),
|
|
27
|
+
releaseMilestone: jest.fn().mockResolvedValue(BigInt(45000))
|
|
28
|
+
},
|
|
29
|
+
createTransaction: jest.fn().mockResolvedValue({
|
|
30
|
+
wait: jest.fn().mockResolvedValue({
|
|
31
|
+
transactionHash: '0x' + '5'.repeat(64),
|
|
32
|
+
logs: [{
|
|
33
|
+
topics: [
|
|
34
|
+
'0x' + '6'.repeat(64), // Event signature hash
|
|
35
|
+
'0x' + '1'.repeat(64) // Transaction ID (indexed param)
|
|
36
|
+
],
|
|
37
|
+
data: '0x',
|
|
38
|
+
address: '0x' + 'a'.repeat(40)
|
|
39
|
+
}]
|
|
40
|
+
})
|
|
41
|
+
}),
|
|
42
|
+
transitionState: jest.fn().mockResolvedValue({
|
|
43
|
+
wait: jest.fn().mockResolvedValue({})
|
|
44
|
+
}),
|
|
45
|
+
linkEscrow: jest.fn().mockResolvedValue({
|
|
46
|
+
wait: jest.fn().mockResolvedValue({})
|
|
47
|
+
}),
|
|
48
|
+
releaseEscrow: jest.fn().mockResolvedValue({
|
|
49
|
+
wait: jest.fn().mockResolvedValue({})
|
|
50
|
+
}),
|
|
51
|
+
releaseMilestone: jest.fn().mockResolvedValue({
|
|
52
|
+
wait: jest.fn().mockResolvedValue({})
|
|
53
|
+
}),
|
|
54
|
+
transactions: jest.fn().mockResolvedValue({
|
|
55
|
+
transactionId: '0x' + '1'.repeat(64),
|
|
56
|
+
requester: '0x' + 'c'.repeat(40), // REQUESTER_ADDRESS
|
|
57
|
+
provider: '0x' + 'b'.repeat(40), // PROVIDER_ADDRESS
|
|
58
|
+
amount: BigInt('100000000'),
|
|
59
|
+
state: State.INITIATED,
|
|
60
|
+
createdAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
61
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
62
|
+
disputeWindow: BigInt(7200),
|
|
63
|
+
escrowContract: '0x' + 'd'.repeat(40), // ESCROW_ADDRESS
|
|
64
|
+
escrowId: '0x' + '2'.repeat(64), // ESCROW_ID
|
|
65
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
66
|
+
}),
|
|
67
|
+
getTransaction: jest.fn().mockResolvedValue({
|
|
68
|
+
transactionId: '0x' + '1'.repeat(64),
|
|
69
|
+
requester: '0x' + 'c'.repeat(40), // REQUESTER_ADDRESS
|
|
70
|
+
provider: '0x' + 'b'.repeat(40), // PROVIDER_ADDRESS
|
|
71
|
+
amount: BigInt('100000000'),
|
|
72
|
+
state: State.INITIATED,
|
|
73
|
+
createdAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
74
|
+
updatedAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
75
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
76
|
+
disputeWindow: BigInt(7200),
|
|
77
|
+
escrowContract: '0x' + 'd'.repeat(40), // ESCROW_ADDRESS
|
|
78
|
+
escrowId: '0x' + '2'.repeat(64), // ESCROW_ID
|
|
79
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
80
|
+
contentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
81
|
+
attestationUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
82
|
+
platformFeeBpsLocked: BigInt(100)
|
|
83
|
+
}),
|
|
84
|
+
getEconomicParams: jest.fn().mockResolvedValue([
|
|
85
|
+
BigInt(100), // platformFeeBps (1%)
|
|
86
|
+
BigInt(500), // requesterPenaltyBps (5%)
|
|
87
|
+
'0x' + 'f'.repeat(40) // feeRecipient
|
|
88
|
+
]),
|
|
89
|
+
// ethers v6 requires getFunction
|
|
90
|
+
getFunction: jest.fn((name: string) => {
|
|
91
|
+
const functions: any = {
|
|
92
|
+
createTransaction: mockContract.createTransaction,
|
|
93
|
+
transitionState: mockContract.transitionState,
|
|
94
|
+
linkEscrow: mockContract.linkEscrow,
|
|
95
|
+
releaseEscrow: mockContract.releaseEscrow,
|
|
96
|
+
releaseMilestone: mockContract.releaseMilestone,
|
|
97
|
+
getTransaction: mockContract.getTransaction,
|
|
98
|
+
raiseDispute: jest.fn().mockResolvedValue({ wait: jest.fn().mockResolvedValue({}) }),
|
|
99
|
+
resolveDispute: jest.fn().mockResolvedValue({ wait: jest.fn().mockResolvedValue({}) })
|
|
100
|
+
};
|
|
101
|
+
const func = functions[name] || jest.fn().mockResolvedValue({ wait: jest.fn().mockResolvedValue({}) });
|
|
102
|
+
const estimateGasMap: any = mockContract.estimateGas;
|
|
103
|
+
func.estimateGas = estimateGasMap[name] || jest.fn().mockResolvedValue(BigInt(100000));
|
|
104
|
+
return func;
|
|
105
|
+
}),
|
|
106
|
+
// ethers v6 interface.parseLog mock
|
|
107
|
+
interface: {
|
|
108
|
+
parseLog: jest.fn((log: any) => {
|
|
109
|
+
// Mock parsing TransactionCreated event
|
|
110
|
+
if (log.topics && log.topics[0] === '0x' + '6'.repeat(64)) {
|
|
111
|
+
return {
|
|
112
|
+
name: 'TransactionCreated',
|
|
113
|
+
args: {
|
|
114
|
+
transactionId: log.topics[1] || '0x' + '1'.repeat(64),
|
|
115
|
+
0: log.topics[1] || '0x' + '1'.repeat(64) // Positional access
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Unknown event - throw to simulate parseLog failure
|
|
120
|
+
throw new Error('Unknown event signature');
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Mock signer
|
|
126
|
+
const mockSigner = {
|
|
127
|
+
provider: {}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Mock Contract constructor
|
|
131
|
+
jest.mock('ethers', () => {
|
|
132
|
+
const actual = jest.requireActual('ethers');
|
|
133
|
+
return {
|
|
134
|
+
...actual,
|
|
135
|
+
Contract: jest.fn().mockImplementation(() => mockContract)
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('ACTPKernel - Security Tests', () => {
|
|
140
|
+
let kernel: ACTPKernel;
|
|
141
|
+
|
|
142
|
+
const KERNEL_ADDRESS = '0x' + 'a'.repeat(40);
|
|
143
|
+
const PROVIDER_ADDRESS = '0x' + 'b'.repeat(40);
|
|
144
|
+
const REQUESTER_ADDRESS = '0x' + 'c'.repeat(40);
|
|
145
|
+
const ESCROW_ADDRESS = '0x' + 'd'.repeat(40);
|
|
146
|
+
const TX_ID = '0x' + '1'.repeat(64);
|
|
147
|
+
const ESCROW_ID = '0x' + '2'.repeat(64);
|
|
148
|
+
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
jest.clearAllMocks();
|
|
151
|
+
kernel = new ACTPKernel(KERNEL_ADDRESS, mockSigner as any);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('createTransaction - Input Validation', () => {
|
|
155
|
+
it('should successfully create transaction with valid params', async () => {
|
|
156
|
+
const params = {
|
|
157
|
+
provider: PROVIDER_ADDRESS,
|
|
158
|
+
requester: REQUESTER_ADDRESS,
|
|
159
|
+
amount: BigInt('100000000'),
|
|
160
|
+
deadline: Math.floor(Date.now() / 1000) + 86400, // 24 hours
|
|
161
|
+
disputeWindow: 7200 // 2 hours
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const txId = await kernel.createTransaction(params);
|
|
165
|
+
|
|
166
|
+
expect(txId).toBe(TX_ID);
|
|
167
|
+
expect(mockContract.createTransaction).toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should reject zero address provider', async () => {
|
|
171
|
+
const params = {
|
|
172
|
+
provider: '0x0000000000000000000000000000000000000000',
|
|
173
|
+
requester: REQUESTER_ADDRESS,
|
|
174
|
+
amount: BigInt('100000000'),
|
|
175
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
176
|
+
disputeWindow: 7200
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
await expect(kernel.createTransaction(params)).rejects.toThrow('zero address');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should reject zero address requester', async () => {
|
|
183
|
+
const params = {
|
|
184
|
+
provider: PROVIDER_ADDRESS,
|
|
185
|
+
requester: '0x0000000000000000000000000000000000000000',
|
|
186
|
+
amount: BigInt('100000000'),
|
|
187
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
188
|
+
disputeWindow: 7200
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
await expect(kernel.createTransaction(params)).rejects.toThrow('zero address');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should reject zero amount', async () => {
|
|
195
|
+
const params = {
|
|
196
|
+
provider: PROVIDER_ADDRESS,
|
|
197
|
+
requester: REQUESTER_ADDRESS,
|
|
198
|
+
amount: BigInt(0),
|
|
199
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
200
|
+
disputeWindow: 7200
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
await expect(kernel.createTransaction(params)).rejects.toThrow('Invalid amount');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should reject past deadline', async () => {
|
|
207
|
+
const params = {
|
|
208
|
+
provider: PROVIDER_ADDRESS,
|
|
209
|
+
requester: REQUESTER_ADDRESS,
|
|
210
|
+
amount: BigInt('100000000'),
|
|
211
|
+
deadline: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
|
|
212
|
+
disputeWindow: 7200
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
await expect(kernel.createTransaction(params)).rejects.toThrow('must be in the future');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should reject negative dispute window', async () => {
|
|
219
|
+
const params = {
|
|
220
|
+
provider: PROVIDER_ADDRESS,
|
|
221
|
+
requester: REQUESTER_ADDRESS,
|
|
222
|
+
amount: BigInt('100000000'),
|
|
223
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
224
|
+
disputeWindow: -1
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
await expect(kernel.createTransaction(params)).rejects.toThrow('cannot be negative');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should reject dispute window exceeding 30 days', async () => {
|
|
231
|
+
const params = {
|
|
232
|
+
provider: PROVIDER_ADDRESS,
|
|
233
|
+
requester: REQUESTER_ADDRESS,
|
|
234
|
+
amount: BigInt('100000000'),
|
|
235
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
236
|
+
disputeWindow: 31 * 24 * 60 * 60 // 31 days
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
await expect(kernel.createTransaction(params)).rejects.toThrow('exceeds maximum');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should handle TransactionCreated event extraction failure', async () => {
|
|
243
|
+
mockContract.createTransaction.mockResolvedValueOnce({
|
|
244
|
+
wait: jest.fn().mockResolvedValue({
|
|
245
|
+
transactionHash: '0x' + '5'.repeat(64),
|
|
246
|
+
logs: [] // Empty logs - no events emitted
|
|
247
|
+
})
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const params = {
|
|
251
|
+
provider: PROVIDER_ADDRESS,
|
|
252
|
+
requester: REQUESTER_ADDRESS,
|
|
253
|
+
amount: BigInt('100000000'),
|
|
254
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
255
|
+
disputeWindow: 7200
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
await expect(kernel.createTransaction(params)).rejects.toThrow('TransactionCreated event not found');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should include custom metadata in transaction', async () => {
|
|
262
|
+
const customMetadata = '0x' + '5'.repeat(64);
|
|
263
|
+
const params = {
|
|
264
|
+
provider: PROVIDER_ADDRESS,
|
|
265
|
+
requester: REQUESTER_ADDRESS,
|
|
266
|
+
amount: BigInt('100000000'),
|
|
267
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
268
|
+
disputeWindow: 7200,
|
|
269
|
+
metadata: customMetadata
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
await kernel.createTransaction(params);
|
|
273
|
+
|
|
274
|
+
expect(mockContract.createTransaction).toHaveBeenCalledWith(
|
|
275
|
+
PROVIDER_ADDRESS,
|
|
276
|
+
REQUESTER_ADDRESS,
|
|
277
|
+
expect.any(BigInt),
|
|
278
|
+
expect.any(Number),
|
|
279
|
+
expect.any(Number),
|
|
280
|
+
customMetadata,
|
|
281
|
+
expect.any(Object)
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should use default metadata when not provided', async () => {
|
|
286
|
+
const params = {
|
|
287
|
+
provider: PROVIDER_ADDRESS,
|
|
288
|
+
requester: REQUESTER_ADDRESS,
|
|
289
|
+
amount: BigInt('100000000'),
|
|
290
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
291
|
+
disputeWindow: 7200
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
await kernel.createTransaction(params);
|
|
295
|
+
|
|
296
|
+
expect(mockContract.createTransaction).toHaveBeenCalledWith(
|
|
297
|
+
PROVIDER_ADDRESS,
|
|
298
|
+
REQUESTER_ADDRESS,
|
|
299
|
+
expect.any(BigInt),
|
|
300
|
+
expect.any(Number),
|
|
301
|
+
expect.any(Number),
|
|
302
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
303
|
+
expect.any(Object)
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('transitionState - State Machine Validation', () => {
|
|
309
|
+
it('should allow valid state transition INITIATED -> QUOTED', async () => {
|
|
310
|
+
mockContract.transactions.mockResolvedValueOnce({
|
|
311
|
+
transactionId: TX_ID,
|
|
312
|
+
requester: REQUESTER_ADDRESS,
|
|
313
|
+
provider: PROVIDER_ADDRESS,
|
|
314
|
+
amount: BigInt('100000000'),
|
|
315
|
+
state: State.INITIATED,
|
|
316
|
+
createdAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
317
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
318
|
+
disputeWindow: BigInt(7200),
|
|
319
|
+
escrowContract: ESCROW_ADDRESS,
|
|
320
|
+
escrowId: ESCROW_ID,
|
|
321
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await kernel.transitionState(TX_ID, State.QUOTED);
|
|
325
|
+
|
|
326
|
+
expect(mockContract.transitionState).toHaveBeenCalledWith(
|
|
327
|
+
TX_ID,
|
|
328
|
+
State.QUOTED,
|
|
329
|
+
'0x',
|
|
330
|
+
expect.any(Object)
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should reject invalid state transition INITIATED -> DELIVERED', async () => {
|
|
335
|
+
mockContract.transactions.mockResolvedValueOnce({
|
|
336
|
+
transactionId: TX_ID,
|
|
337
|
+
requester: REQUESTER_ADDRESS,
|
|
338
|
+
provider: PROVIDER_ADDRESS,
|
|
339
|
+
amount: BigInt('100000000'),
|
|
340
|
+
state: State.INITIATED,
|
|
341
|
+
createdAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
342
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
343
|
+
disputeWindow: BigInt(7200),
|
|
344
|
+
escrowContract: ESCROW_ADDRESS,
|
|
345
|
+
escrowId: ESCROW_ID,
|
|
346
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await expect(kernel.transitionState(TX_ID, State.DELIVERED))
|
|
350
|
+
.rejects.toThrow('Invalid state transition');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should reject backwards state transition DELIVERED -> COMMITTED', async () => {
|
|
354
|
+
mockContract.getTransaction.mockResolvedValueOnce({
|
|
355
|
+
txId: TX_ID,
|
|
356
|
+
transactionId: TX_ID,
|
|
357
|
+
requester: REQUESTER_ADDRESS,
|
|
358
|
+
provider: PROVIDER_ADDRESS,
|
|
359
|
+
amount: BigInt('100000000'),
|
|
360
|
+
state: State.DELIVERED,
|
|
361
|
+
createdAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
362
|
+
updatedAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
363
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
364
|
+
disputeWindow: BigInt(7200),
|
|
365
|
+
escrowContract: ESCROW_ADDRESS,
|
|
366
|
+
escrowId: ESCROW_ID,
|
|
367
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
368
|
+
contentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
369
|
+
attestationUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
370
|
+
platformFeeBpsLocked: BigInt(100)
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
await expect(kernel.transitionState(TX_ID, State.COMMITTED))
|
|
374
|
+
.rejects.toThrow('Invalid state transition');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should reject transition to same state', async () => {
|
|
378
|
+
mockContract.transactions.mockResolvedValueOnce({
|
|
379
|
+
transactionId: TX_ID,
|
|
380
|
+
requester: REQUESTER_ADDRESS,
|
|
381
|
+
provider: PROVIDER_ADDRESS,
|
|
382
|
+
amount: BigInt('100000000'),
|
|
383
|
+
state: State.INITIATED,
|
|
384
|
+
createdAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
385
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
386
|
+
disputeWindow: BigInt(7200),
|
|
387
|
+
escrowContract: ESCROW_ADDRESS,
|
|
388
|
+
escrowId: ESCROW_ID,
|
|
389
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
await expect(kernel.transitionState(TX_ID, State.INITIATED))
|
|
393
|
+
.rejects.toThrow('Invalid state transition');
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('should accept proof data for DELIVERED state', async () => {
|
|
397
|
+
mockContract.getTransaction.mockResolvedValueOnce({
|
|
398
|
+
transactionId: TX_ID,
|
|
399
|
+
requester: REQUESTER_ADDRESS,
|
|
400
|
+
provider: PROVIDER_ADDRESS,
|
|
401
|
+
amount: BigInt('100000000'),
|
|
402
|
+
state: State.IN_PROGRESS,
|
|
403
|
+
createdAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
404
|
+
updatedAt: BigInt(Math.floor(Date.now() / 1000)),
|
|
405
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
406
|
+
disputeWindow: BigInt(7200),
|
|
407
|
+
escrowContract: ESCROW_ADDRESS,
|
|
408
|
+
escrowId: ESCROW_ID,
|
|
409
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
410
|
+
contentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
411
|
+
attestationUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
412
|
+
platformFeeBpsLocked: BigInt(100)
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
416
|
+
const proofData = abiCoder.encode(
|
|
417
|
+
['string', 'string'],
|
|
418
|
+
['https://ipfs.io/ipfs/Qm...', '0x' + '5'.repeat(64)]
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
await kernel.transitionState(TX_ID, State.DELIVERED, proofData);
|
|
422
|
+
|
|
423
|
+
expect(mockContract.transitionState).toHaveBeenCalledWith(
|
|
424
|
+
TX_ID,
|
|
425
|
+
State.DELIVERED,
|
|
426
|
+
proofData,
|
|
427
|
+
expect.any(Object)
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should validate transaction ID format', async () => {
|
|
432
|
+
await expect(kernel.transitionState('invalid-tx-id', State.QUOTED))
|
|
433
|
+
.rejects.toThrow('Invalid transaction ID format');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should throw error when transaction not found', async () => {
|
|
437
|
+
mockContract.getTransaction.mockResolvedValueOnce({
|
|
438
|
+
transactionId: TX_ID,
|
|
439
|
+
requester: REQUESTER_ADDRESS,
|
|
440
|
+
provider: PROVIDER_ADDRESS,
|
|
441
|
+
amount: BigInt('100000000'),
|
|
442
|
+
state: State.INITIATED,
|
|
443
|
+
createdAt: BigInt(0), // Indicates transaction doesn't exist
|
|
444
|
+
updatedAt: BigInt(0),
|
|
445
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
|
|
446
|
+
disputeWindow: BigInt(7200),
|
|
447
|
+
escrowContract: ESCROW_ADDRESS,
|
|
448
|
+
escrowId: ESCROW_ID,
|
|
449
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
450
|
+
contentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
451
|
+
attestationUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
452
|
+
platformFeeBpsLocked: BigInt(0)
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await expect(kernel.transitionState(TX_ID, State.QUOTED))
|
|
456
|
+
.rejects.toThrow('Transaction');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should handle contract revert errors gracefully', async () => {
|
|
460
|
+
mockContract.transitionState.mockRejectedValueOnce({
|
|
461
|
+
transactionHash: '0x' + 'f'.repeat(64),
|
|
462
|
+
reason: 'Unauthorized caller',
|
|
463
|
+
message: 'execution reverted: Unauthorized caller'
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
await expect(kernel.transitionState(TX_ID, State.QUOTED))
|
|
467
|
+
.rejects.toThrow('Transaction reverted');
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('linkEscrow - Escrow Integration', () => {
|
|
472
|
+
it('should successfully link escrow', async () => {
|
|
473
|
+
await kernel.linkEscrow(TX_ID, ESCROW_ADDRESS, ESCROW_ID);
|
|
474
|
+
|
|
475
|
+
expect(mockContract.linkEscrow).toHaveBeenCalledWith(
|
|
476
|
+
TX_ID,
|
|
477
|
+
ESCROW_ADDRESS,
|
|
478
|
+
ESCROW_ID,
|
|
479
|
+
expect.any(Object)
|
|
480
|
+
);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should reject invalid transaction ID', async () => {
|
|
484
|
+
await expect(kernel.linkEscrow('invalid', ESCROW_ADDRESS, ESCROW_ID))
|
|
485
|
+
.rejects.toThrow('Invalid transaction ID format');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should reject zero address escrow contract', async () => {
|
|
489
|
+
await expect(kernel.linkEscrow(TX_ID, '0x0000000000000000000000000000000000000000', ESCROW_ID))
|
|
490
|
+
.rejects.toThrow('zero address');
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('should reject invalid escrow ID format', async () => {
|
|
494
|
+
await expect(kernel.linkEscrow(TX_ID, ESCROW_ADDRESS, 'invalid-escrow-id'))
|
|
495
|
+
.rejects.toThrow('Invalid transaction ID format');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should handle link escrow revert', async () => {
|
|
499
|
+
mockContract.linkEscrow.mockRejectedValueOnce({
|
|
500
|
+
transactionHash: '0x' + 'f'.repeat(64),
|
|
501
|
+
reason: 'Escrow already linked',
|
|
502
|
+
message: 'execution reverted: Escrow already linked'
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
await expect(kernel.linkEscrow(TX_ID, ESCROW_ADDRESS, ESCROW_ID))
|
|
506
|
+
.rejects.toThrow('Transaction reverted');
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe('releaseEscrow - Fund Distribution', () => {
|
|
511
|
+
it('should successfully release escrow', async () => {
|
|
512
|
+
await kernel.releaseEscrow(TX_ID);
|
|
513
|
+
|
|
514
|
+
expect(mockContract.releaseEscrow).toHaveBeenCalledWith(
|
|
515
|
+
TX_ID,
|
|
516
|
+
expect.any(Object)
|
|
517
|
+
);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should reject invalid transaction ID', async () => {
|
|
521
|
+
await expect(kernel.releaseEscrow('invalid-id'))
|
|
522
|
+
.rejects.toThrow('Invalid transaction ID format');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should handle release revert', async () => {
|
|
526
|
+
mockContract.releaseEscrow.mockRejectedValueOnce({
|
|
527
|
+
transactionHash: '0x' + 'f'.repeat(64),
|
|
528
|
+
reason: 'Not in DELIVERED state',
|
|
529
|
+
message: 'execution reverted: Not in DELIVERED state'
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
await expect(kernel.releaseEscrow(TX_ID))
|
|
533
|
+
.rejects.toThrow('Transaction reverted');
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('releaseMilestone - Partial Payments', () => {
|
|
538
|
+
it('should successfully release milestone', async () => {
|
|
539
|
+
const milestoneId = 1;
|
|
540
|
+
const amount = BigInt('50000000');
|
|
541
|
+
|
|
542
|
+
await kernel.releaseMilestone(TX_ID, milestoneId, amount);
|
|
543
|
+
|
|
544
|
+
expect(mockContract.releaseMilestone).toHaveBeenCalledWith(
|
|
545
|
+
TX_ID,
|
|
546
|
+
milestoneId,
|
|
547
|
+
amount,
|
|
548
|
+
expect.any(Object)
|
|
549
|
+
);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('should reject negative milestone ID', async () => {
|
|
553
|
+
const amount = BigInt('50000000');
|
|
554
|
+
|
|
555
|
+
await expect(kernel.releaseMilestone(TX_ID, -1, amount))
|
|
556
|
+
.rejects.toThrow('cannot be negative');
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should reject zero amount', async () => {
|
|
560
|
+
await expect(kernel.releaseMilestone(TX_ID, 1, BigInt(0)))
|
|
561
|
+
.rejects.toThrow('Invalid amount');
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('should reject invalid transaction ID', async () => {
|
|
565
|
+
await expect(kernel.releaseMilestone('invalid', 1, BigInt('50000000')))
|
|
566
|
+
.rejects.toThrow('Invalid transaction ID format');
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
describe('raiseDispute - Dispute Mechanism', () => {
|
|
571
|
+
it('should successfully raise dispute with reason and evidence', async () => {
|
|
572
|
+
const reason = 'Work not delivered as specified';
|
|
573
|
+
const evidence = 'ipfs://Qm...';
|
|
574
|
+
|
|
575
|
+
await kernel.raiseDispute(TX_ID, reason, evidence);
|
|
576
|
+
|
|
577
|
+
expect(mockContract.transitionState).toHaveBeenCalledWith(
|
|
578
|
+
TX_ID,
|
|
579
|
+
State.DISPUTED,
|
|
580
|
+
expect.any(String), // Encoded proof
|
|
581
|
+
expect.any(Object)
|
|
582
|
+
);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('should reject invalid transaction ID', async () => {
|
|
586
|
+
await expect(kernel.raiseDispute('invalid', 'reason', 'evidence'))
|
|
587
|
+
.rejects.toThrow('Invalid transaction ID format');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should encode dispute proof correctly', async () => {
|
|
591
|
+
const reason = 'Test reason';
|
|
592
|
+
const evidence = 'Test evidence';
|
|
593
|
+
|
|
594
|
+
await kernel.raiseDispute(TX_ID, reason, evidence);
|
|
595
|
+
|
|
596
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
597
|
+
const expectedProof = abiCoder.encode(
|
|
598
|
+
['string', 'string'],
|
|
599
|
+
[reason, evidence]
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
expect(mockContract.transitionState).toHaveBeenCalledWith(
|
|
603
|
+
TX_ID,
|
|
604
|
+
State.DISPUTED,
|
|
605
|
+
expectedProof,
|
|
606
|
+
expect.any(Object)
|
|
607
|
+
);
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
describe('resolveDispute - Dispute Resolution', () => {
|
|
612
|
+
it('should successfully resolve dispute with split', async () => {
|
|
613
|
+
const resolution = {
|
|
614
|
+
requesterAmount: BigInt('30000000'),
|
|
615
|
+
providerAmount: BigInt('60000000'),
|
|
616
|
+
mediatorAmount: BigInt('10000000'),
|
|
617
|
+
mediator: '0x' + 'e'.repeat(40)
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
await kernel.resolveDispute(TX_ID, resolution);
|
|
621
|
+
|
|
622
|
+
expect(mockContract.transitionState).toHaveBeenCalledWith(
|
|
623
|
+
TX_ID,
|
|
624
|
+
State.SETTLED,
|
|
625
|
+
expect.any(String),
|
|
626
|
+
expect.any(Object)
|
|
627
|
+
);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should reject negative requester amount', async () => {
|
|
631
|
+
const resolution = {
|
|
632
|
+
requesterAmount: BigInt(-1),
|
|
633
|
+
providerAmount: BigInt('60000000'),
|
|
634
|
+
mediatorAmount: BigInt('10000000'),
|
|
635
|
+
mediator: '0x' + 'e'.repeat(40)
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
await expect(kernel.resolveDispute(TX_ID, resolution))
|
|
639
|
+
.rejects.toThrow('cannot be negative');
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('should reject negative provider amount', async () => {
|
|
643
|
+
const resolution = {
|
|
644
|
+
requesterAmount: BigInt('30000000'),
|
|
645
|
+
providerAmount: BigInt(-1),
|
|
646
|
+
mediatorAmount: BigInt('10000000'),
|
|
647
|
+
mediator: '0x' + 'e'.repeat(40)
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
await expect(kernel.resolveDispute(TX_ID, resolution))
|
|
651
|
+
.rejects.toThrow('cannot be negative');
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('should reject negative mediator amount', async () => {
|
|
655
|
+
const resolution = {
|
|
656
|
+
requesterAmount: BigInt('30000000'),
|
|
657
|
+
providerAmount: BigInt('60000000'),
|
|
658
|
+
mediatorAmount: BigInt(-1),
|
|
659
|
+
mediator: '0x' + 'e'.repeat(40)
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
await expect(kernel.resolveDispute(TX_ID, resolution))
|
|
663
|
+
.rejects.toThrow('cannot be negative');
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it('should require mediator address when mediator amount > 0', async () => {
|
|
667
|
+
const resolution = {
|
|
668
|
+
requesterAmount: BigInt('30000000'),
|
|
669
|
+
providerAmount: BigInt('60000000'),
|
|
670
|
+
mediatorAmount: BigInt('10000000'),
|
|
671
|
+
mediator: undefined
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
await expect(kernel.resolveDispute(TX_ID, resolution))
|
|
675
|
+
.rejects.toThrow('Mediator address required');
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('should allow zero mediator amount without mediator address', async () => {
|
|
679
|
+
const resolution = {
|
|
680
|
+
requesterAmount: BigInt('50000000'),
|
|
681
|
+
providerAmount: BigInt('50000000'),
|
|
682
|
+
mediatorAmount: BigInt(0),
|
|
683
|
+
mediator: undefined
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
await kernel.resolveDispute(TX_ID, resolution);
|
|
687
|
+
|
|
688
|
+
expect(mockContract.transitionState).toHaveBeenCalled();
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
describe('getTransaction - Data Retrieval', () => {
|
|
693
|
+
it('should retrieve transaction details', async () => {
|
|
694
|
+
const tx = await kernel.getTransaction(TX_ID);
|
|
695
|
+
|
|
696
|
+
expect(tx.txId).toBe(TX_ID);
|
|
697
|
+
expect(tx.requester).toBe(REQUESTER_ADDRESS);
|
|
698
|
+
expect(tx.provider).toBe(PROVIDER_ADDRESS);
|
|
699
|
+
expect(tx.state).toBe(State.INITIATED);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should throw error when transaction not found', async () => {
|
|
703
|
+
mockContract.getTransaction.mockResolvedValueOnce({
|
|
704
|
+
transactionId: TX_ID,
|
|
705
|
+
requester: REQUESTER_ADDRESS,
|
|
706
|
+
provider: PROVIDER_ADDRESS,
|
|
707
|
+
amount: BigInt('100000000'),
|
|
708
|
+
state: State.INITIATED,
|
|
709
|
+
createdAt: BigInt(0), // Indicates transaction doesn't exist
|
|
710
|
+
updatedAt: BigInt(0),
|
|
711
|
+
deadline: BigInt(0),
|
|
712
|
+
disputeWindow: BigInt(0),
|
|
713
|
+
escrowContract: '0x0000000000000000000000000000000000000000',
|
|
714
|
+
escrowId: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
715
|
+
serviceHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
716
|
+
contentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
717
|
+
attestationUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
718
|
+
platformFeeBpsLocked: BigInt(0)
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
await expect(kernel.getTransaction(TX_ID))
|
|
722
|
+
.rejects.toThrow('not found');
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe('getEconomicParams - Fee Structure', () => {
|
|
727
|
+
it('should retrieve economic parameters', async () => {
|
|
728
|
+
const params = await kernel.getEconomicParams();
|
|
729
|
+
|
|
730
|
+
expect(params.baseFeeNumerator).toBe(100); // 1% = 100 bps
|
|
731
|
+
expect(params.baseFeeDenominator).toBe(10000);
|
|
732
|
+
expect(params.requesterPenaltyBps).toBe(500);
|
|
733
|
+
expect(params.feeRecipient).toBe('0x' + 'f'.repeat(40));
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it('should handle array-based response format', async () => {
|
|
737
|
+
mockContract.getEconomicParams.mockResolvedValueOnce([
|
|
738
|
+
BigInt(100),
|
|
739
|
+
BigInt(500),
|
|
740
|
+
'0x' + 'f'.repeat(40)
|
|
741
|
+
]);
|
|
742
|
+
|
|
743
|
+
const params = await kernel.getEconomicParams();
|
|
744
|
+
|
|
745
|
+
expect(params.baseFeeNumerator).toBe(100);
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
describe('Gas Estimation - V6 Dynamic Buffers', () => {
|
|
750
|
+
it('should apply 15% gas buffer to createTransaction', async () => {
|
|
751
|
+
const params = {
|
|
752
|
+
provider: PROVIDER_ADDRESS,
|
|
753
|
+
requester: REQUESTER_ADDRESS,
|
|
754
|
+
amount: BigInt('100000000'),
|
|
755
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
756
|
+
disputeWindow: 7200
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
await kernel.createTransaction(params);
|
|
760
|
+
|
|
761
|
+
expect(mockContract.createTransaction).toHaveBeenCalledWith(
|
|
762
|
+
PROVIDER_ADDRESS,
|
|
763
|
+
REQUESTER_ADDRESS,
|
|
764
|
+
BigInt('100000000'),
|
|
765
|
+
expect.any(Number),
|
|
766
|
+
7200,
|
|
767
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
768
|
+
expect.objectContaining({
|
|
769
|
+
gasLimit: BigInt(97750) // 85000 * 1.15 (15% buffer for simple state init)
|
|
770
|
+
})
|
|
771
|
+
);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('should apply 20% gas buffer to transitionState', async () => {
|
|
775
|
+
const txId = TX_ID;
|
|
776
|
+
const newState = State.QUOTED; // Valid transition from INITIATED → QUOTED
|
|
777
|
+
|
|
778
|
+
await kernel.transitionState(txId, newState);
|
|
779
|
+
|
|
780
|
+
expect(mockContract.transitionState).toHaveBeenCalledWith(
|
|
781
|
+
txId,
|
|
782
|
+
newState,
|
|
783
|
+
'0x',
|
|
784
|
+
expect.objectContaining({
|
|
785
|
+
gasLimit: BigInt(60000) // 50000 * 1.20 (20% buffer for standard state change)
|
|
786
|
+
})
|
|
787
|
+
);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
it('should apply 30% gas buffer to releaseEscrow', async () => {
|
|
791
|
+
const txId = TX_ID;
|
|
792
|
+
|
|
793
|
+
await kernel.releaseEscrow(txId);
|
|
794
|
+
|
|
795
|
+
expect(mockContract.releaseEscrow).toHaveBeenCalledWith(
|
|
796
|
+
txId,
|
|
797
|
+
expect.objectContaining({
|
|
798
|
+
gasLimit: BigInt(65000) // 50000 * 1.30 (30% buffer for multi-recipient disbursement)
|
|
799
|
+
})
|
|
800
|
+
);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('should apply 30% gas buffer to releaseMilestone', async () => {
|
|
804
|
+
const txId = TX_ID;
|
|
805
|
+
const milestoneId = 1;
|
|
806
|
+
const amount = BigInt('50000000');
|
|
807
|
+
|
|
808
|
+
await kernel.releaseMilestone(txId, milestoneId, amount);
|
|
809
|
+
|
|
810
|
+
expect(mockContract.releaseMilestone).toHaveBeenCalledWith(
|
|
811
|
+
txId,
|
|
812
|
+
milestoneId,
|
|
813
|
+
amount,
|
|
814
|
+
expect.objectContaining({
|
|
815
|
+
gasLimit: BigInt(58500) // 45000 * 1.30 (30% buffer for escrow release)
|
|
816
|
+
})
|
|
817
|
+
);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it('should apply gas settings when provided', async () => {
|
|
821
|
+
const gasSettings = {
|
|
822
|
+
maxFeePerGas: BigInt('2000000000'),
|
|
823
|
+
maxPriorityFeePerGas: BigInt('1000000000')
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const kernelWithGas = new ACTPKernel(KERNEL_ADDRESS, mockSigner as any, gasSettings);
|
|
827
|
+
|
|
828
|
+
const params = {
|
|
829
|
+
provider: PROVIDER_ADDRESS,
|
|
830
|
+
requester: REQUESTER_ADDRESS,
|
|
831
|
+
amount: BigInt('100000000'),
|
|
832
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
833
|
+
disputeWindow: 7200
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
await kernelWithGas.createTransaction(params);
|
|
837
|
+
|
|
838
|
+
expect(mockContract.createTransaction).toHaveBeenCalledWith(
|
|
839
|
+
PROVIDER_ADDRESS,
|
|
840
|
+
REQUESTER_ADDRESS,
|
|
841
|
+
BigInt('100000000'),
|
|
842
|
+
expect.any(Number),
|
|
843
|
+
7200,
|
|
844
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
845
|
+
expect.objectContaining({
|
|
846
|
+
maxFeePerGas: gasSettings.maxFeePerGas,
|
|
847
|
+
maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas
|
|
848
|
+
})
|
|
849
|
+
);
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
describe('getAddress', () => {
|
|
854
|
+
it('should return kernel contract address', () => {
|
|
855
|
+
expect(kernel.getAddress()).toBe(KERNEL_ADDRESS);
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
describe('estimateCreateTransaction', () => {
|
|
860
|
+
it('should estimate gas for transaction creation', async () => {
|
|
861
|
+
const params = {
|
|
862
|
+
provider: PROVIDER_ADDRESS,
|
|
863
|
+
requester: REQUESTER_ADDRESS,
|
|
864
|
+
amount: BigInt('100000000'),
|
|
865
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
866
|
+
disputeWindow: 7200
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
const estimatedGas = await kernel.estimateCreateTransaction(params);
|
|
870
|
+
|
|
871
|
+
expect(estimatedGas).toEqual(BigInt(85000));
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
describe('Event Parsing Edge Cases - Coverage Gap 1 (Lines 156-173)', () => {
|
|
876
|
+
it('should handle malformed log that causes parseLog error', async () => {
|
|
877
|
+
mockContract.createTransaction.mockResolvedValueOnce({
|
|
878
|
+
wait: jest.fn().mockResolvedValue({
|
|
879
|
+
transactionHash: '0x' + '5'.repeat(64),
|
|
880
|
+
logs: [
|
|
881
|
+
{
|
|
882
|
+
topics: ['0xINVALID_TOPIC'], // Malformed topic
|
|
883
|
+
data: '0xGARBAGE_DATA',
|
|
884
|
+
address: KERNEL_ADDRESS
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
topics: ['0x' + '6'.repeat(64), '0x' + '1'.repeat(64)],
|
|
888
|
+
data: '0x',
|
|
889
|
+
address: KERNEL_ADDRESS
|
|
890
|
+
}
|
|
891
|
+
]
|
|
892
|
+
})
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
// Mock parseLog to throw on first log, succeed on second
|
|
896
|
+
mockContract.interface.parseLog
|
|
897
|
+
.mockImplementationOnce(() => {
|
|
898
|
+
throw new Error('Invalid log data format');
|
|
899
|
+
})
|
|
900
|
+
.mockReturnValueOnce({
|
|
901
|
+
name: 'TransactionCreated',
|
|
902
|
+
args: {
|
|
903
|
+
transactionId: '0x' + '1'.repeat(64),
|
|
904
|
+
0: '0x' + '1'.repeat(64)
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
const params = {
|
|
909
|
+
provider: PROVIDER_ADDRESS,
|
|
910
|
+
requester: REQUESTER_ADDRESS,
|
|
911
|
+
amount: BigInt('100000000'),
|
|
912
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
913
|
+
disputeWindow: 7200
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
const txId = await kernel.createTransaction(params);
|
|
917
|
+
|
|
918
|
+
// Should skip malformed log via catch block (line 169) and find valid event
|
|
919
|
+
expect(txId).toBe('0x' + '1'.repeat(64));
|
|
920
|
+
expect(mockContract.interface.parseLog).toHaveBeenCalledTimes(2);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it('should skip non-matching events and find TransactionCreated in multiple logs', async () => {
|
|
924
|
+
mockContract.createTransaction.mockResolvedValueOnce({
|
|
925
|
+
wait: jest.fn().mockResolvedValue({
|
|
926
|
+
transactionHash: '0x' + '5'.repeat(64),
|
|
927
|
+
logs: [
|
|
928
|
+
{
|
|
929
|
+
topics: ['0x' + '7'.repeat(64)], // Different event signature
|
|
930
|
+
data: '0x',
|
|
931
|
+
address: KERNEL_ADDRESS
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
topics: ['0x' + '8'.repeat(64)], // Another different event
|
|
935
|
+
data: '0x',
|
|
936
|
+
address: KERNEL_ADDRESS
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
topics: ['0x' + '6'.repeat(64), '0x' + '1'.repeat(64)], // TransactionCreated
|
|
940
|
+
data: '0x',
|
|
941
|
+
address: KERNEL_ADDRESS
|
|
942
|
+
}
|
|
943
|
+
]
|
|
944
|
+
})
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
mockContract.interface.parseLog
|
|
948
|
+
.mockReturnValueOnce({
|
|
949
|
+
name: 'SomeOtherEvent',
|
|
950
|
+
args: {
|
|
951
|
+
transactionId: null,
|
|
952
|
+
0: null
|
|
953
|
+
}
|
|
954
|
+
})
|
|
955
|
+
.mockReturnValueOnce({
|
|
956
|
+
name: 'AnotherEvent',
|
|
957
|
+
args: {
|
|
958
|
+
transactionId: null,
|
|
959
|
+
0: null
|
|
960
|
+
}
|
|
961
|
+
})
|
|
962
|
+
.mockReturnValueOnce({
|
|
963
|
+
name: 'TransactionCreated',
|
|
964
|
+
args: {
|
|
965
|
+
transactionId: '0x' + '1'.repeat(64),
|
|
966
|
+
0: '0x' + '1'.repeat(64)
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
const params = {
|
|
971
|
+
provider: PROVIDER_ADDRESS,
|
|
972
|
+
requester: REQUESTER_ADDRESS,
|
|
973
|
+
amount: BigInt('100000000'),
|
|
974
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
975
|
+
disputeWindow: 7200
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
const txId = await kernel.createTransaction(params);
|
|
979
|
+
|
|
980
|
+
expect(txId).toBe('0x' + '1'.repeat(64));
|
|
981
|
+
expect(mockContract.interface.parseLog).toHaveBeenCalledTimes(3);
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
it('should throw error when no TransactionCreated event found in receipt (line 173)', async () => {
|
|
985
|
+
mockContract.createTransaction.mockResolvedValueOnce({
|
|
986
|
+
wait: jest.fn().mockResolvedValue({
|
|
987
|
+
transactionHash: '0x' + '5'.repeat(64),
|
|
988
|
+
logs: [
|
|
989
|
+
{
|
|
990
|
+
topics: ['0x' + '7'.repeat(64)],
|
|
991
|
+
data: '0x',
|
|
992
|
+
address: KERNEL_ADDRESS
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
topics: ['0x' + '8'.repeat(64)],
|
|
996
|
+
data: '0x',
|
|
997
|
+
address: KERNEL_ADDRESS
|
|
998
|
+
}
|
|
999
|
+
]
|
|
1000
|
+
})
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
// Mock parseLog to return non-matching events
|
|
1004
|
+
mockContract.interface.parseLog
|
|
1005
|
+
.mockReturnValueOnce({
|
|
1006
|
+
name: 'SomeOtherEvent',
|
|
1007
|
+
args: {
|
|
1008
|
+
transactionId: null,
|
|
1009
|
+
0: null
|
|
1010
|
+
}
|
|
1011
|
+
})
|
|
1012
|
+
.mockReturnValueOnce({
|
|
1013
|
+
name: 'YetAnotherEvent',
|
|
1014
|
+
args: {
|
|
1015
|
+
transactionId: null,
|
|
1016
|
+
0: null
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
const params = {
|
|
1021
|
+
provider: PROVIDER_ADDRESS,
|
|
1022
|
+
requester: REQUESTER_ADDRESS,
|
|
1023
|
+
amount: BigInt('100000000'),
|
|
1024
|
+
deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
1025
|
+
disputeWindow: 7200
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// Should throw "TransactionCreated event not found" (line 173)
|
|
1029
|
+
await expect(kernel.createTransaction(params))
|
|
1030
|
+
.rejects.toThrow('TransactionCreated event not found in receipt');
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
describe('anchorAttestation - Attestation Anchoring (Lines 502-530)', () => {
|
|
1035
|
+
beforeEach(() => {
|
|
1036
|
+
// Setup anchorAttestation mock
|
|
1037
|
+
const anchorAttestationFunc: any = jest.fn().mockResolvedValue({
|
|
1038
|
+
wait: jest.fn().mockResolvedValue({
|
|
1039
|
+
transactionHash: '0x' + 'a'.repeat(64),
|
|
1040
|
+
logs: []
|
|
1041
|
+
})
|
|
1042
|
+
});
|
|
1043
|
+
anchorAttestationFunc.estimateGas = jest.fn().mockResolvedValue(BigInt(50000));
|
|
1044
|
+
|
|
1045
|
+
mockContract.getFunction.mockImplementation((name: string) => {
|
|
1046
|
+
if (name === 'anchorAttestation') {
|
|
1047
|
+
return anchorAttestationFunc;
|
|
1048
|
+
}
|
|
1049
|
+
// Fallback to existing mock implementation
|
|
1050
|
+
const functions: any = {
|
|
1051
|
+
createTransaction: mockContract.createTransaction,
|
|
1052
|
+
transitionState: mockContract.transitionState,
|
|
1053
|
+
linkEscrow: mockContract.linkEscrow,
|
|
1054
|
+
releaseEscrow: mockContract.releaseEscrow,
|
|
1055
|
+
releaseMilestone: mockContract.releaseMilestone,
|
|
1056
|
+
getTransaction: mockContract.getTransaction,
|
|
1057
|
+
raiseDispute: jest.fn().mockResolvedValue({ wait: jest.fn().mockResolvedValue({}) }),
|
|
1058
|
+
resolveDispute: jest.fn().mockResolvedValue({ wait: jest.fn().mockResolvedValue({}) })
|
|
1059
|
+
};
|
|
1060
|
+
const func = functions[name] || jest.fn().mockResolvedValue({ wait: jest.fn().mockResolvedValue({}) });
|
|
1061
|
+
const estimateGasMap: any = mockContract.estimateGas;
|
|
1062
|
+
func.estimateGas = estimateGasMap[name] || jest.fn().mockResolvedValue(BigInt(100000));
|
|
1063
|
+
return func;
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
it('should successfully anchor valid attestation', async () => {
|
|
1068
|
+
const txId = '0x' + '1'.repeat(64);
|
|
1069
|
+
const attestationUID = '0x' + '2'.repeat(64);
|
|
1070
|
+
|
|
1071
|
+
await kernel.anchorAttestation(txId, attestationUID);
|
|
1072
|
+
|
|
1073
|
+
const anchorFunc = mockContract.getFunction('anchorAttestation');
|
|
1074
|
+
expect(anchorFunc).toHaveBeenCalledWith(
|
|
1075
|
+
txId,
|
|
1076
|
+
attestationUID,
|
|
1077
|
+
expect.objectContaining({
|
|
1078
|
+
gasLimit: expect.any(BigInt)
|
|
1079
|
+
})
|
|
1080
|
+
);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
it('should validate attestationUID format - missing 0x prefix', async () => {
|
|
1084
|
+
const txId = '0x' + '1'.repeat(64);
|
|
1085
|
+
const invalidUID = '1'.repeat(64); // Missing 0x prefix
|
|
1086
|
+
|
|
1087
|
+
await expect(kernel.anchorAttestation(txId, invalidUID))
|
|
1088
|
+
.rejects.toThrow('Must be 32-byte hex string');
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
it('should validate attestationUID format - too short', async () => {
|
|
1092
|
+
const txId = '0x' + '1'.repeat(64);
|
|
1093
|
+
const invalidUID = '0x1234'; // Too short
|
|
1094
|
+
|
|
1095
|
+
await expect(kernel.anchorAttestation(txId, invalidUID))
|
|
1096
|
+
.rejects.toThrow('Must be 32-byte hex string');
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it('should validate attestationUID format - invalid hex characters', async () => {
|
|
1100
|
+
const txId = '0x' + '1'.repeat(64);
|
|
1101
|
+
const invalidUID = '0x' + 'G'.repeat(64); // Invalid hex
|
|
1102
|
+
|
|
1103
|
+
await expect(kernel.anchorAttestation(txId, invalidUID))
|
|
1104
|
+
.rejects.toThrow('Must be 32-byte hex string');
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('should validate attestationUID format - empty string', async () => {
|
|
1108
|
+
const txId = '0x' + '1'.repeat(64);
|
|
1109
|
+
const invalidUID = '';
|
|
1110
|
+
|
|
1111
|
+
await expect(kernel.anchorAttestation(txId, invalidUID))
|
|
1112
|
+
.rejects.toThrow('Must be 32-byte hex string');
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
it('should apply 15% gas buffer for anchorAttestation', async () => {
|
|
1116
|
+
const txId = '0x' + '1'.repeat(64);
|
|
1117
|
+
const attestationUID = '0x' + '2'.repeat(64);
|
|
1118
|
+
|
|
1119
|
+
const estimateGasMock = jest.fn().mockResolvedValue(BigInt(100000));
|
|
1120
|
+
const anchorFunc: any = jest.fn().mockResolvedValue({
|
|
1121
|
+
wait: jest.fn().mockResolvedValue({
|
|
1122
|
+
transactionHash: '0x' + 'a'.repeat(64),
|
|
1123
|
+
logs: []
|
|
1124
|
+
})
|
|
1125
|
+
});
|
|
1126
|
+
anchorFunc.estimateGas = estimateGasMock;
|
|
1127
|
+
|
|
1128
|
+
mockContract.getFunction.mockReturnValueOnce(anchorFunc);
|
|
1129
|
+
|
|
1130
|
+
await kernel.anchorAttestation(txId, attestationUID);
|
|
1131
|
+
|
|
1132
|
+
// 15% gas buffer for anchorAttestation (simple attestation anchoring)
|
|
1133
|
+
expect(anchorFunc).toHaveBeenCalledWith(
|
|
1134
|
+
txId,
|
|
1135
|
+
attestationUID,
|
|
1136
|
+
expect.objectContaining({
|
|
1137
|
+
gasLimit: BigInt(115000) // 100k * 1.15
|
|
1138
|
+
})
|
|
1139
|
+
);
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
it('should handle anchorAttestation contract revert', async () => {
|
|
1143
|
+
const txId = '0x' + '1'.repeat(64);
|
|
1144
|
+
const attestationUID = '0x' + '2'.repeat(64);
|
|
1145
|
+
|
|
1146
|
+
const anchorFunc: any = jest.fn().mockRejectedValueOnce({
|
|
1147
|
+
transactionHash: '0x' + 'f'.repeat(64),
|
|
1148
|
+
reason: 'Attestation already anchored',
|
|
1149
|
+
message: 'execution reverted: Attestation already anchored'
|
|
1150
|
+
});
|
|
1151
|
+
anchorFunc.estimateGas = jest.fn().mockResolvedValue(BigInt(50000));
|
|
1152
|
+
|
|
1153
|
+
mockContract.getFunction.mockReturnValueOnce(anchorFunc);
|
|
1154
|
+
|
|
1155
|
+
await expect(kernel.anchorAttestation(txId, attestationUID))
|
|
1156
|
+
.rejects.toThrow('Transaction reverted');
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
it('should validate transaction ID format in anchorAttestation', async () => {
|
|
1160
|
+
const invalidTxId = 'not-a-valid-tx-id';
|
|
1161
|
+
const attestationUID = '0x' + '2'.repeat(64);
|
|
1162
|
+
|
|
1163
|
+
await expect(kernel.anchorAttestation(invalidTxId, attestationUID))
|
|
1164
|
+
.rejects.toThrow('Invalid transaction ID format');
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1167
|
+
});
|