genlayer 0.31.0 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/CLAUDE.md +55 -0
- package/README.md +121 -8
- package/dist/index.js +7161 -3706
- package/docs/delegator-guide.md +203 -0
- package/docs/validator-guide.md +291 -0
- package/package.json +2 -2
- package/src/commands/account/create.ts +29 -0
- package/src/commands/account/export.ts +106 -0
- package/src/commands/account/import.ts +135 -0
- package/src/commands/account/index.ts +126 -0
- package/src/commands/account/list.ts +34 -0
- package/src/commands/account/lock.ts +39 -0
- package/src/commands/account/remove.ts +30 -0
- package/src/commands/account/send.ts +156 -0
- package/src/commands/account/show.ts +74 -0
- package/src/commands/account/unlock.ts +51 -0
- package/src/commands/account/use.ts +21 -0
- package/src/commands/network/index.ts +18 -3
- package/src/commands/network/setNetwork.ts +43 -26
- package/src/commands/staking/StakingAction.ts +157 -0
- package/src/commands/staking/delegatorClaim.ts +41 -0
- package/src/commands/staking/delegatorExit.ts +50 -0
- package/src/commands/staking/delegatorJoin.ts +44 -0
- package/src/commands/staking/index.ts +251 -0
- package/src/commands/staking/setIdentity.ts +66 -0
- package/src/commands/staking/setOperator.ts +40 -0
- package/src/commands/staking/stakingInfo.ts +300 -0
- package/src/commands/staking/validatorClaim.ts +38 -0
- package/src/commands/staking/validatorDeposit.ts +35 -0
- package/src/commands/staking/validatorExit.ts +44 -0
- package/src/commands/staking/validatorJoin.ts +47 -0
- package/src/commands/staking/validatorPrime.ts +35 -0
- package/src/commands/staking/wizard.ts +802 -0
- package/src/index.ts +4 -2
- package/src/lib/actions/BaseAction.ts +114 -55
- package/src/lib/config/ConfigFileManager.ts +143 -0
- package/src/lib/config/KeychainManager.ts +23 -7
- package/tests/actions/create.test.ts +41 -21
- package/tests/actions/deploy.test.ts +7 -0
- package/tests/actions/lock.test.ts +33 -13
- package/tests/actions/setNetwork.test.ts +18 -57
- package/tests/actions/staking.test.ts +323 -0
- package/tests/actions/unlock.test.ts +51 -33
- package/tests/commands/account.test.ts +146 -0
- package/tests/commands/network.test.ts +10 -10
- package/tests/commands/staking.test.ts +333 -0
- package/tests/index.test.ts +6 -2
- package/tests/libs/baseAction.test.ts +71 -42
- package/tests/libs/configFileManager.test.ts +8 -1
- package/tests/libs/keychainManager.test.ts +56 -16
- package/src/commands/keygen/create.ts +0 -23
- package/src/commands/keygen/index.ts +0 -39
- package/src/commands/keygen/lock.ts +0 -31
- package/src/commands/keygen/unlock.ts +0 -41
- package/src/lib/interfaces/KeystoreData.ts +0 -5
- package/tests/commands/keygen.test.ts +0 -123
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import {Command} from "commander";
|
|
2
|
+
import {vi, describe, beforeEach, afterEach, test, expect} from "vitest";
|
|
3
|
+
import {initializeStakingCommands} from "../../src/commands/staking";
|
|
4
|
+
import {ValidatorJoinAction} from "../../src/commands/staking/validatorJoin";
|
|
5
|
+
import {ValidatorDepositAction} from "../../src/commands/staking/validatorDeposit";
|
|
6
|
+
import {ValidatorExitAction} from "../../src/commands/staking/validatorExit";
|
|
7
|
+
import {ValidatorClaimAction} from "../../src/commands/staking/validatorClaim";
|
|
8
|
+
import {ValidatorPrimeAction} from "../../src/commands/staking/validatorPrime";
|
|
9
|
+
import {SetOperatorAction} from "../../src/commands/staking/setOperator";
|
|
10
|
+
import {SetIdentityAction} from "../../src/commands/staking/setIdentity";
|
|
11
|
+
import {DelegatorJoinAction} from "../../src/commands/staking/delegatorJoin";
|
|
12
|
+
import {DelegatorExitAction} from "../../src/commands/staking/delegatorExit";
|
|
13
|
+
import {DelegatorClaimAction} from "../../src/commands/staking/delegatorClaim";
|
|
14
|
+
import {StakingInfoAction} from "../../src/commands/staking/stakingInfo";
|
|
15
|
+
|
|
16
|
+
vi.mock("../../src/commands/staking/validatorJoin");
|
|
17
|
+
vi.mock("../../src/commands/staking/validatorDeposit");
|
|
18
|
+
vi.mock("../../src/commands/staking/validatorExit");
|
|
19
|
+
vi.mock("../../src/commands/staking/validatorClaim");
|
|
20
|
+
vi.mock("../../src/commands/staking/validatorPrime");
|
|
21
|
+
vi.mock("../../src/commands/staking/setOperator");
|
|
22
|
+
vi.mock("../../src/commands/staking/setIdentity");
|
|
23
|
+
vi.mock("../../src/commands/staking/delegatorJoin");
|
|
24
|
+
vi.mock("../../src/commands/staking/delegatorExit");
|
|
25
|
+
vi.mock("../../src/commands/staking/delegatorClaim");
|
|
26
|
+
vi.mock("../../src/commands/staking/stakingInfo");
|
|
27
|
+
|
|
28
|
+
describe("staking commands", () => {
|
|
29
|
+
let program: Command;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
program = new Command();
|
|
33
|
+
initializeStakingCommands(program);
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("validator-join", () => {
|
|
42
|
+
test("calls ValidatorJoinAction.execute with amount", async () => {
|
|
43
|
+
program.parse(["node", "test", "staking", "validator-join", "--amount", "42000gen"]);
|
|
44
|
+
|
|
45
|
+
expect(ValidatorJoinAction).toHaveBeenCalledTimes(1);
|
|
46
|
+
expect(ValidatorJoinAction.prototype.execute).toHaveBeenCalledWith({
|
|
47
|
+
amount: "42000gen",
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("calls ValidatorJoinAction.execute with operator", async () => {
|
|
52
|
+
program.parse([
|
|
53
|
+
"node",
|
|
54
|
+
"test",
|
|
55
|
+
"staking",
|
|
56
|
+
"validator-join",
|
|
57
|
+
"--amount",
|
|
58
|
+
"42000gen",
|
|
59
|
+
"--operator",
|
|
60
|
+
"0xOperator",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
expect(ValidatorJoinAction.prototype.execute).toHaveBeenCalledWith({
|
|
64
|
+
amount: "42000gen",
|
|
65
|
+
operator: "0xOperator",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("accepts staking-address option", async () => {
|
|
70
|
+
program.parse([
|
|
71
|
+
"node",
|
|
72
|
+
"test",
|
|
73
|
+
"staking",
|
|
74
|
+
"validator-join",
|
|
75
|
+
"--amount",
|
|
76
|
+
"42000",
|
|
77
|
+
"--staking-address",
|
|
78
|
+
"0xStaking",
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
expect(ValidatorJoinAction.prototype.execute).toHaveBeenCalledWith(
|
|
82
|
+
expect.objectContaining({stakingAddress: "0xStaking"}),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("validator-deposit", () => {
|
|
88
|
+
test("calls ValidatorDepositAction.execute", async () => {
|
|
89
|
+
program.parse(["node", "test", "staking", "validator-deposit", "--amount", "1000gen"]);
|
|
90
|
+
|
|
91
|
+
expect(ValidatorDepositAction).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(ValidatorDepositAction.prototype.execute).toHaveBeenCalledWith({
|
|
93
|
+
amount: "1000gen",
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("validator-exit", () => {
|
|
99
|
+
test("calls ValidatorExitAction.execute", async () => {
|
|
100
|
+
program.parse(["node", "test", "staking", "validator-exit", "--shares", "100"]);
|
|
101
|
+
|
|
102
|
+
expect(ValidatorExitAction).toHaveBeenCalledTimes(1);
|
|
103
|
+
expect(ValidatorExitAction.prototype.execute).toHaveBeenCalledWith({
|
|
104
|
+
shares: "100",
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("validator-claim", () => {
|
|
110
|
+
test("calls ValidatorClaimAction.execute", async () => {
|
|
111
|
+
program.parse(["node", "test", "staking", "validator-claim", "--validator", "0xValidator"]);
|
|
112
|
+
|
|
113
|
+
expect(ValidatorClaimAction).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(ValidatorClaimAction.prototype.execute).toHaveBeenCalledWith({
|
|
115
|
+
validator: "0xValidator",
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("works without validator option", async () => {
|
|
120
|
+
program.parse(["node", "test", "staking", "validator-claim"]);
|
|
121
|
+
|
|
122
|
+
expect(ValidatorClaimAction.prototype.execute).toHaveBeenCalledWith({});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("delegator-join", () => {
|
|
127
|
+
test("calls DelegatorJoinAction.execute", async () => {
|
|
128
|
+
program.parse([
|
|
129
|
+
"node",
|
|
130
|
+
"test",
|
|
131
|
+
"staking",
|
|
132
|
+
"delegator-join",
|
|
133
|
+
"--validator",
|
|
134
|
+
"0xValidator",
|
|
135
|
+
"--amount",
|
|
136
|
+
"42gen",
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
expect(DelegatorJoinAction).toHaveBeenCalledTimes(1);
|
|
140
|
+
expect(DelegatorJoinAction.prototype.execute).toHaveBeenCalledWith({
|
|
141
|
+
validator: "0xValidator",
|
|
142
|
+
amount: "42gen",
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("delegator-exit", () => {
|
|
148
|
+
test("calls DelegatorExitAction.execute", async () => {
|
|
149
|
+
program.parse([
|
|
150
|
+
"node",
|
|
151
|
+
"test",
|
|
152
|
+
"staking",
|
|
153
|
+
"delegator-exit",
|
|
154
|
+
"--validator",
|
|
155
|
+
"0xValidator",
|
|
156
|
+
"--shares",
|
|
157
|
+
"50",
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
expect(DelegatorExitAction).toHaveBeenCalledTimes(1);
|
|
161
|
+
expect(DelegatorExitAction.prototype.execute).toHaveBeenCalledWith({
|
|
162
|
+
validator: "0xValidator",
|
|
163
|
+
shares: "50",
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("delegator-claim", () => {
|
|
169
|
+
test("calls DelegatorClaimAction.execute", async () => {
|
|
170
|
+
program.parse([
|
|
171
|
+
"node",
|
|
172
|
+
"test",
|
|
173
|
+
"staking",
|
|
174
|
+
"delegator-claim",
|
|
175
|
+
"--validator",
|
|
176
|
+
"0xValidator",
|
|
177
|
+
"--delegator",
|
|
178
|
+
"0xDelegator",
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
expect(DelegatorClaimAction).toHaveBeenCalledTimes(1);
|
|
182
|
+
expect(DelegatorClaimAction.prototype.execute).toHaveBeenCalledWith({
|
|
183
|
+
validator: "0xValidator",
|
|
184
|
+
delegator: "0xDelegator",
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("validator-info", () => {
|
|
190
|
+
test("calls StakingInfoAction.getValidatorInfo", async () => {
|
|
191
|
+
program.parse(["node", "test", "staking", "validator-info", "--validator", "0xValidator"]);
|
|
192
|
+
|
|
193
|
+
expect(StakingInfoAction).toHaveBeenCalledTimes(1);
|
|
194
|
+
expect(StakingInfoAction.prototype.getValidatorInfo).toHaveBeenCalledWith({
|
|
195
|
+
validator: "0xValidator",
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("epoch-info", () => {
|
|
201
|
+
test("calls StakingInfoAction.getEpochInfo", async () => {
|
|
202
|
+
program.parse(["node", "test", "staking", "epoch-info"]);
|
|
203
|
+
|
|
204
|
+
expect(StakingInfoAction).toHaveBeenCalledTimes(1);
|
|
205
|
+
expect(StakingInfoAction.prototype.getEpochInfo).toHaveBeenCalledWith({});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("active-validators", () => {
|
|
210
|
+
test("calls StakingInfoAction.listActiveValidators", async () => {
|
|
211
|
+
program.parse(["node", "test", "staking", "active-validators"]);
|
|
212
|
+
|
|
213
|
+
expect(StakingInfoAction).toHaveBeenCalledTimes(1);
|
|
214
|
+
expect(StakingInfoAction.prototype.listActiveValidators).toHaveBeenCalledWith({});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("validator-prime", () => {
|
|
219
|
+
test("calls ValidatorPrimeAction.execute", async () => {
|
|
220
|
+
program.parse(["node", "test", "staking", "validator-prime", "--validator", "0xValidator"]);
|
|
221
|
+
|
|
222
|
+
expect(ValidatorPrimeAction).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(ValidatorPrimeAction.prototype.execute).toHaveBeenCalledWith({
|
|
224
|
+
validator: "0xValidator",
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("set-operator", () => {
|
|
230
|
+
test("calls SetOperatorAction.execute", async () => {
|
|
231
|
+
program.parse([
|
|
232
|
+
"node",
|
|
233
|
+
"test",
|
|
234
|
+
"staking",
|
|
235
|
+
"set-operator",
|
|
236
|
+
"--validator",
|
|
237
|
+
"0xValidator",
|
|
238
|
+
"--operator",
|
|
239
|
+
"0xOperator",
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
expect(SetOperatorAction).toHaveBeenCalledTimes(1);
|
|
243
|
+
expect(SetOperatorAction.prototype.execute).toHaveBeenCalledWith({
|
|
244
|
+
validator: "0xValidator",
|
|
245
|
+
operator: "0xOperator",
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("set-identity", () => {
|
|
251
|
+
test("calls SetIdentityAction.execute with required fields", async () => {
|
|
252
|
+
program.parse([
|
|
253
|
+
"node",
|
|
254
|
+
"test",
|
|
255
|
+
"staking",
|
|
256
|
+
"set-identity",
|
|
257
|
+
"--validator",
|
|
258
|
+
"0xValidator",
|
|
259
|
+
"--moniker",
|
|
260
|
+
"My Validator",
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
expect(SetIdentityAction).toHaveBeenCalledTimes(1);
|
|
264
|
+
expect(SetIdentityAction.prototype.execute).toHaveBeenCalledWith({
|
|
265
|
+
validator: "0xValidator",
|
|
266
|
+
moniker: "My Validator",
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("calls SetIdentityAction.execute with all optional fields", async () => {
|
|
271
|
+
program.parse([
|
|
272
|
+
"node",
|
|
273
|
+
"test",
|
|
274
|
+
"staking",
|
|
275
|
+
"set-identity",
|
|
276
|
+
"--validator",
|
|
277
|
+
"0xValidator",
|
|
278
|
+
"--moniker",
|
|
279
|
+
"My Validator",
|
|
280
|
+
"--website",
|
|
281
|
+
"https://example.com",
|
|
282
|
+
"--twitter",
|
|
283
|
+
"myhandle",
|
|
284
|
+
"--github",
|
|
285
|
+
"mygithub",
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
expect(SetIdentityAction.prototype.execute).toHaveBeenCalledWith({
|
|
289
|
+
validator: "0xValidator",
|
|
290
|
+
moniker: "My Validator",
|
|
291
|
+
website: "https://example.com",
|
|
292
|
+
twitter: "myhandle",
|
|
293
|
+
github: "mygithub",
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe("delegation-info", () => {
|
|
299
|
+
test("calls StakingInfoAction.getStakeInfo", async () => {
|
|
300
|
+
program.parse([
|
|
301
|
+
"node",
|
|
302
|
+
"test",
|
|
303
|
+
"staking",
|
|
304
|
+
"delegation-info",
|
|
305
|
+
"--validator",
|
|
306
|
+
"0xValidator",
|
|
307
|
+
]);
|
|
308
|
+
|
|
309
|
+
expect(StakingInfoAction).toHaveBeenCalledTimes(1);
|
|
310
|
+
expect(StakingInfoAction.prototype.getStakeInfo).toHaveBeenCalledWith({
|
|
311
|
+
validator: "0xValidator",
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("calls StakingInfoAction.getStakeInfo with delegator", async () => {
|
|
316
|
+
program.parse([
|
|
317
|
+
"node",
|
|
318
|
+
"test",
|
|
319
|
+
"staking",
|
|
320
|
+
"delegation-info",
|
|
321
|
+
"--validator",
|
|
322
|
+
"0xValidator",
|
|
323
|
+
"--delegator",
|
|
324
|
+
"0xDelegator",
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
expect(StakingInfoAction.prototype.getStakeInfo).toHaveBeenCalledWith({
|
|
328
|
+
validator: "0xValidator",
|
|
329
|
+
delegator: "0xDelegator",
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
package/tests/index.test.ts
CHANGED
|
@@ -13,8 +13,8 @@ vi.mock("../src/commands/general", () => ({
|
|
|
13
13
|
initializeGeneralCommands: vi.fn(),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
-
vi.mock("../src/commands/
|
|
17
|
-
|
|
16
|
+
vi.mock("../src/commands/account", () => ({
|
|
17
|
+
initializeAccountCommands: vi.fn(),
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
20
|
vi.mock("../src/commands/contracts", () => ({
|
|
@@ -45,6 +45,10 @@ vi.mock("../src/commands/transactions", () => ({
|
|
|
45
45
|
initializeTransactionsCommands: vi.fn(),
|
|
46
46
|
}));
|
|
47
47
|
|
|
48
|
+
vi.mock("../src/commands/staking", () => ({
|
|
49
|
+
initializeStakingCommands: vi.fn(),
|
|
50
|
+
}));
|
|
51
|
+
|
|
48
52
|
describe("CLI", () => {
|
|
49
53
|
it("should initialize CLI", () => {
|
|
50
54
|
expect(initializeCLI).not.toThrow();
|
|
@@ -28,10 +28,18 @@ describe("BaseAction", () => {
|
|
|
28
28
|
let consoleErrorSpy: any;
|
|
29
29
|
let processExitSpy: any;
|
|
30
30
|
|
|
31
|
+
// Standard web3 keystore format
|
|
31
32
|
const mockKeystoreData = {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
address: "1234567890123456789012345678901234567890",
|
|
34
|
+
crypto: {
|
|
35
|
+
cipher: "aes-128-ctr",
|
|
36
|
+
ciphertext: "test",
|
|
37
|
+
cipherparams: {iv: "test"},
|
|
38
|
+
kdf: "scrypt",
|
|
39
|
+
kdfparams: {},
|
|
40
|
+
mac: "test"
|
|
41
|
+
},
|
|
42
|
+
version: 3
|
|
35
43
|
};
|
|
36
44
|
|
|
37
45
|
const mockWallet = {
|
|
@@ -81,8 +89,11 @@ describe("BaseAction", () => {
|
|
|
81
89
|
vi.spyOn(baseAction as any, "getConfigByKey").mockReturnValue("./test-keypair.json");
|
|
82
90
|
vi.spyOn(baseAction as any, "getFilePath").mockImplementation(() => "./test-keypair.json");
|
|
83
91
|
vi.spyOn(baseAction as any, "writeConfig").mockImplementation(() => {});
|
|
84
|
-
vi.spyOn(baseAction as any, "getConfig").mockReturnValue({});
|
|
85
|
-
|
|
92
|
+
vi.spyOn(baseAction as any, "getConfig").mockReturnValue({activeAccount: "default"});
|
|
93
|
+
vi.spyOn(baseAction as any, "resolveAccountName").mockReturnValue("default");
|
|
94
|
+
vi.spyOn(baseAction as any, "getKeystorePath").mockReturnValue("/mocked/home/.genlayer/keystores/default.json");
|
|
95
|
+
vi.spyOn(baseAction as any, "getActiveAccount").mockReturnValue("default");
|
|
96
|
+
|
|
86
97
|
// Mock keychainManager methods
|
|
87
98
|
vi.spyOn(baseAction["keychainManager"], "isKeychainAvailable").mockResolvedValue(false);
|
|
88
99
|
vi.spyOn(baseAction["keychainManager"], "getPrivateKey").mockResolvedValue(null);
|
|
@@ -109,7 +120,7 @@ describe("BaseAction", () => {
|
|
|
109
120
|
|
|
110
121
|
test("should fail the spinner with an error message", () => {
|
|
111
122
|
const error = new Error("Something went wrong");
|
|
112
|
-
baseAction["failSpinner"]("Failure", error);
|
|
123
|
+
baseAction["failSpinner"]("Failure", error, false); // Don't exit for test
|
|
113
124
|
|
|
114
125
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error:"));
|
|
115
126
|
expect(consoleSpy).toHaveBeenCalledWith(inspect(error, {depth: null, colors: false}));
|
|
@@ -117,6 +128,12 @@ describe("BaseAction", () => {
|
|
|
117
128
|
expect(mockSpinner.fail).toHaveBeenCalledWith(expect.stringContaining("Failure"));
|
|
118
129
|
});
|
|
119
130
|
|
|
131
|
+
test("should fail the spinner and exit by default", () => {
|
|
132
|
+
const error = new Error("Fatal error");
|
|
133
|
+
expect(() => baseAction["failSpinner"]("Fatal", error)).toThrow("process exited");
|
|
134
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
135
|
+
});
|
|
136
|
+
|
|
120
137
|
test("should stop the spinner", () => {
|
|
121
138
|
baseAction["stopSpinner"]();
|
|
122
139
|
expect(mockSpinner.stop).toHaveBeenCalled();
|
|
@@ -256,16 +273,16 @@ describe("BaseAction", () => {
|
|
|
256
273
|
const account = await baseAction["getAccount"](false);
|
|
257
274
|
|
|
258
275
|
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
259
|
-
expect(existsSync).toHaveBeenCalledWith("
|
|
260
|
-
expect(readFileSync).toHaveBeenCalledWith("
|
|
276
|
+
expect(existsSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json");
|
|
277
|
+
expect(readFileSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json", "utf-8");
|
|
261
278
|
});
|
|
262
279
|
|
|
263
280
|
test("should return address when called with readOnly=true", async () => {
|
|
264
281
|
const address = await baseAction["getAccount"](true);
|
|
265
|
-
|
|
282
|
+
|
|
266
283
|
expect(address).toBe(mockKeystoreData.address);
|
|
267
|
-
expect(existsSync).toHaveBeenCalledWith("
|
|
268
|
-
expect(readFileSync).toHaveBeenCalledWith("
|
|
284
|
+
expect(existsSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json");
|
|
285
|
+
expect(readFileSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json", "utf-8");
|
|
269
286
|
});
|
|
270
287
|
|
|
271
288
|
test("should create new keypair when keystore file does not exist", async () => {
|
|
@@ -279,7 +296,7 @@ describe("BaseAction", () => {
|
|
|
279
296
|
|
|
280
297
|
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
281
298
|
expect(inquirer.prompt).toHaveBeenCalledWith(expect.arrayContaining([
|
|
282
|
-
expect.objectContaining({message: chalk.yellow("
|
|
299
|
+
expect.objectContaining({message: chalk.yellow("Account 'default' not found. Would you like to create it?")})
|
|
283
300
|
]));
|
|
284
301
|
});
|
|
285
302
|
|
|
@@ -298,7 +315,7 @@ describe("BaseAction", () => {
|
|
|
298
315
|
const account = await baseAction["getAccount"](false);
|
|
299
316
|
|
|
300
317
|
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
301
|
-
expect(baseAction["keychainManager"].getPrivateKey).
|
|
318
|
+
expect(baseAction["keychainManager"].getPrivateKey).toHaveBeenCalledWith("default");
|
|
302
319
|
expect(inquirer.prompt).not.toHaveBeenCalled();
|
|
303
320
|
});
|
|
304
321
|
|
|
@@ -314,14 +331,14 @@ describe("BaseAction", () => {
|
|
|
314
331
|
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
315
332
|
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Invalid keystore format. Expected encrypted keystore file."));
|
|
316
333
|
expect(inquirer.prompt).toHaveBeenCalledWith(expect.arrayContaining([
|
|
317
|
-
expect.objectContaining({message:
|
|
334
|
+
expect.objectContaining({message: "Would you like to recreate account 'default'?"})
|
|
318
335
|
]));
|
|
319
336
|
});
|
|
320
337
|
|
|
321
338
|
test("should decrypt keystore successfully on first attempt", async () => {
|
|
322
339
|
vi.mocked(inquirer.prompt).mockResolvedValue({password: "correct-password"});
|
|
323
340
|
|
|
324
|
-
const result = await baseAction["decryptKeystore"](mockKeystoreData);
|
|
341
|
+
const result = await baseAction["decryptKeystore"](JSON.stringify(mockKeystoreData));
|
|
325
342
|
|
|
326
343
|
expect(result).toBe(mockWallet.privateKey);
|
|
327
344
|
expect(inquirer.prompt).toHaveBeenCalledWith(expect.arrayContaining([
|
|
@@ -333,12 +350,12 @@ describe("BaseAction", () => {
|
|
|
333
350
|
vi.mocked(ethers.Wallet.fromEncryptedJson)
|
|
334
351
|
.mockRejectedValueOnce(new Error("Incorrect password"))
|
|
335
352
|
.mockResolvedValueOnce(mockWallet as any);
|
|
336
|
-
|
|
353
|
+
|
|
337
354
|
vi.mocked(inquirer.prompt)
|
|
338
355
|
.mockResolvedValueOnce({password: "wrong-password"})
|
|
339
356
|
.mockResolvedValueOnce({password: "correct-password"});
|
|
340
357
|
|
|
341
|
-
const result = await baseAction["decryptKeystore"](mockKeystoreData);
|
|
358
|
+
const result = await baseAction["decryptKeystore"](JSON.stringify(mockKeystoreData));
|
|
342
359
|
|
|
343
360
|
expect(result).toBe(mockWallet.privateKey);
|
|
344
361
|
expect(inquirer.prompt).toHaveBeenCalledTimes(2);
|
|
@@ -351,8 +368,8 @@ describe("BaseAction", () => {
|
|
|
351
368
|
vi.mocked(ethers.Wallet.fromEncryptedJson).mockRejectedValue(new Error("Incorrect password"));
|
|
352
369
|
vi.mocked(inquirer.prompt).mockResolvedValue({password: "wrong-password"});
|
|
353
370
|
|
|
354
|
-
await expect(baseAction["decryptKeystore"](mockKeystoreData)).rejects.toThrow("process exited");
|
|
355
|
-
|
|
371
|
+
await expect(baseAction["decryptKeystore"](JSON.stringify(mockKeystoreData))).rejects.toThrow("process exited");
|
|
372
|
+
|
|
356
373
|
expect(inquirer.prompt).toHaveBeenCalledTimes(3);
|
|
357
374
|
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Maximum password attempts exceeded (3/3)."));
|
|
358
375
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
@@ -364,22 +381,22 @@ describe("BaseAction", () => {
|
|
|
364
381
|
.mockResolvedValueOnce({password: "test-password"})
|
|
365
382
|
.mockResolvedValueOnce({password: "test-password"});
|
|
366
383
|
|
|
367
|
-
const result = await baseAction["
|
|
384
|
+
const result = await baseAction["createKeypairByName"]("test-account", false);
|
|
368
385
|
|
|
369
386
|
expect(result).toBe(mockWallet.privateKey);
|
|
370
387
|
expect(ethers.Wallet.createRandom).toHaveBeenCalled();
|
|
371
388
|
expect(mockWallet.encrypt).toHaveBeenCalledWith("test-password");
|
|
372
389
|
expect(writeFileSync).toHaveBeenCalled();
|
|
373
|
-
expect(baseAction["keychainManager"].removePrivateKey).
|
|
390
|
+
expect(baseAction["keychainManager"].removePrivateKey).toHaveBeenCalledWith("test-account");
|
|
374
391
|
});
|
|
375
392
|
|
|
376
|
-
test("should fail when
|
|
393
|
+
test("should fail when account exists and overwrite is false", async () => {
|
|
377
394
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
378
395
|
|
|
379
|
-
await expect(baseAction["
|
|
380
|
-
|
|
396
|
+
await expect(baseAction["createKeypairByName"]("test-account", false)).rejects.toThrow("process exited");
|
|
397
|
+
|
|
381
398
|
expect(mockSpinner.fail).toHaveBeenCalledWith(
|
|
382
|
-
chalk.red("
|
|
399
|
+
chalk.red("Account 'test-account' already exists. Use '--overwrite' to replace it.")
|
|
383
400
|
);
|
|
384
401
|
});
|
|
385
402
|
|
|
@@ -389,8 +406,8 @@ describe("BaseAction", () => {
|
|
|
389
406
|
.mockResolvedValueOnce({password: "password1"})
|
|
390
407
|
.mockResolvedValueOnce({password: "password2"});
|
|
391
408
|
|
|
392
|
-
await expect(baseAction["
|
|
393
|
-
|
|
409
|
+
await expect(baseAction["createKeypairByName"]("test-account", false)).rejects.toThrow("process exited");
|
|
410
|
+
|
|
394
411
|
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Passwords do not match"));
|
|
395
412
|
});
|
|
396
413
|
|
|
@@ -400,50 +417,62 @@ describe("BaseAction", () => {
|
|
|
400
417
|
.mockResolvedValueOnce({password: "short"})
|
|
401
418
|
.mockResolvedValueOnce({password: "short"});
|
|
402
419
|
|
|
403
|
-
await expect(baseAction["
|
|
404
|
-
|
|
420
|
+
await expect(baseAction["createKeypairByName"]("test-account", false)).rejects.toThrow("process exited");
|
|
421
|
+
|
|
405
422
|
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Password must be at least 8 characters long"));
|
|
406
423
|
});
|
|
407
424
|
|
|
408
|
-
test("should overwrite existing
|
|
425
|
+
test("should overwrite existing account when overwrite is true", async () => {
|
|
409
426
|
vi.mocked(existsSync).mockReturnValue(true);
|
|
410
427
|
vi.mocked(inquirer.prompt)
|
|
411
428
|
.mockResolvedValueOnce({password: "test-password"})
|
|
412
429
|
.mockResolvedValueOnce({password: "test-password"});
|
|
413
430
|
|
|
414
|
-
const result = await baseAction["
|
|
431
|
+
const result = await baseAction["createKeypairByName"]("test-account", true);
|
|
415
432
|
|
|
416
433
|
expect(result).toBe(mockWallet.privateKey);
|
|
417
434
|
expect(writeFileSync).toHaveBeenCalled();
|
|
418
|
-
expect(baseAction["keychainManager"].removePrivateKey).
|
|
435
|
+
expect(baseAction["keychainManager"].removePrivateKey).toHaveBeenCalledWith("test-account");
|
|
419
436
|
});
|
|
420
437
|
|
|
421
438
|
test("should return true for valid keystore format", () => {
|
|
439
|
+
// Standard web3 keystore format
|
|
440
|
+
const validKeystore = {
|
|
441
|
+
address: "1234567890123456789012345678901234567890",
|
|
442
|
+
crypto: {cipher: "aes-128-ctr", ciphertext: "test"},
|
|
443
|
+
version: 3,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const result = baseAction["isValidKeystoreFormat"](validKeystore);
|
|
447
|
+
expect(result).toBe(true);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("should return true for keystore with uppercase Crypto field", () => {
|
|
451
|
+
// Some tools use uppercase 'Crypto'
|
|
422
452
|
const validKeystore = {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
453
|
+
address: "1234567890123456789012345678901234567890",
|
|
454
|
+
Crypto: {cipher: "aes-128-ctr", ciphertext: "test"},
|
|
455
|
+
version: 3,
|
|
426
456
|
};
|
|
427
457
|
|
|
428
458
|
const result = baseAction["isValidKeystoreFormat"](validKeystore);
|
|
429
459
|
expect(result).toBe(true);
|
|
430
460
|
});
|
|
431
461
|
|
|
432
|
-
test("should return false for
|
|
462
|
+
test("should return false for keystore missing crypto field", () => {
|
|
433
463
|
const invalidKeystore = {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
address: "0x1234567890123456789012345678901234567890",
|
|
464
|
+
address: "1234567890123456789012345678901234567890",
|
|
465
|
+
version: 3,
|
|
437
466
|
};
|
|
438
467
|
|
|
439
468
|
const result = baseAction["isValidKeystoreFormat"](invalidKeystore);
|
|
440
469
|
expect(result).toBe(false);
|
|
441
470
|
});
|
|
442
471
|
|
|
443
|
-
test("should return false for keystore missing
|
|
472
|
+
test("should return false for keystore missing address", () => {
|
|
444
473
|
const invalidKeystore = {
|
|
445
|
-
|
|
446
|
-
|
|
474
|
+
crypto: {cipher: "aes-128-ctr"},
|
|
475
|
+
version: 3,
|
|
447
476
|
};
|
|
448
477
|
|
|
449
478
|
const result = baseAction["isValidKeystoreFormat"](invalidKeystore);
|
|
@@ -10,6 +10,7 @@ vi.mock("os")
|
|
|
10
10
|
|
|
11
11
|
describe("ConfigFileManager", () => {
|
|
12
12
|
const mockFolderPath = "/mocked/home/.genlayer";
|
|
13
|
+
const mockKeystoresPath = `${mockFolderPath}/keystores`;
|
|
13
14
|
const mockConfigFilePath = `${mockFolderPath}/genlayer-config.json`;
|
|
14
15
|
|
|
15
16
|
let configFileManager: ConfigFileManager;
|
|
@@ -17,6 +18,8 @@ describe("ConfigFileManager", () => {
|
|
|
17
18
|
beforeEach(() => {
|
|
18
19
|
vi.clearAllMocks();
|
|
19
20
|
vi.mocked(os.homedir).mockReturnValue("/mocked/home");
|
|
21
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
22
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({}));
|
|
20
23
|
configFileManager = new ConfigFileManager();
|
|
21
24
|
});
|
|
22
25
|
|
|
@@ -26,12 +29,16 @@ describe("ConfigFileManager", () => {
|
|
|
26
29
|
|
|
27
30
|
test("ensures folder and config file are created if they don't exist", () => {
|
|
28
31
|
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
32
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({}));
|
|
29
33
|
|
|
30
34
|
new ConfigFileManager();
|
|
31
35
|
|
|
32
36
|
expect(fs.existsSync).toHaveBeenCalledWith(mockFolderPath);
|
|
33
37
|
expect(fs.mkdirSync).toHaveBeenCalledWith(mockFolderPath, { recursive: true });
|
|
34
38
|
|
|
39
|
+
expect(fs.existsSync).toHaveBeenCalledWith(mockKeystoresPath);
|
|
40
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(mockKeystoresPath, { recursive: true });
|
|
41
|
+
|
|
35
42
|
expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFilePath);
|
|
36
43
|
expect(fs.writeFileSync).toHaveBeenCalledWith(mockConfigFilePath, JSON.stringify({}, null, 2));
|
|
37
44
|
});
|
|
@@ -39,11 +46,11 @@ describe("ConfigFileManager", () => {
|
|
|
39
46
|
test("does not recreate folder or config file if they exist", () => {
|
|
40
47
|
vi.clearAllMocks();
|
|
41
48
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
49
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({}));
|
|
42
50
|
|
|
43
51
|
new ConfigFileManager();
|
|
44
52
|
|
|
45
53
|
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
46
|
-
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
|
47
54
|
});
|
|
48
55
|
|
|
49
56
|
test("getFolderPath returns the correct folder path", () => {
|