@obolnetwork/obol-sdk 2.4.6 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +3 -1
- package/dist/cjs/src/ajv.js +67 -41
- package/dist/cjs/src/constants.js +1 -1
- package/dist/cjs/src/index.js +25 -27
- package/dist/cjs/src/schema.js +50 -23
- package/dist/cjs/src/verification/v1.8.0.js +18 -0
- package/dist/cjs/test/fixtures.js +125 -17
- package/dist/cjs/test/methods.test.js +36 -42
- package/dist/esm/package.json +3 -1
- package/dist/esm/src/ajv.js +66 -40
- package/dist/esm/src/constants.js +1 -1
- package/dist/esm/src/index.js +26 -28
- package/dist/esm/src/schema.js +50 -23
- package/dist/esm/src/verification/v1.8.0.js +18 -0
- package/dist/esm/test/fixtures.js +124 -16
- package/dist/esm/test/methods.test.js +37 -43
- package/dist/types/src/ajv.d.ts +3 -2
- package/dist/types/src/constants.d.ts +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/schema.d.ts +40 -8
- package/dist/types/src/types.d.ts +8 -2
- package/dist/types/test/fixtures.d.ts +78 -11
- package/package.json +3 -1
- package/src/ajv.ts +91 -52
- package/src/constants.ts +1 -1
- package/src/index.ts +37 -35
- package/src/schema.ts +45 -22
- package/src/types.ts +11 -2
- package/src/verification/v1.8.0.ts +24 -0
package/src/ajv.ts
CHANGED
|
@@ -1,78 +1,117 @@
|
|
|
1
|
-
import
|
|
1
|
+
import addFormats from 'ajv-formats';
|
|
2
|
+
import addKeywords from 'ajv-keywords';
|
|
2
3
|
import { parseUnits } from 'ethers';
|
|
3
4
|
import {
|
|
4
5
|
type RewardsSplitPayload,
|
|
5
6
|
type SplitRecipient,
|
|
6
7
|
type TotalSplitPayload,
|
|
7
8
|
} from './types';
|
|
9
|
+
import Ajv from 'ajv';
|
|
8
10
|
import {
|
|
9
11
|
DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
10
12
|
DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
|
|
11
13
|
} from './constants';
|
|
12
14
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
export const VALID_DEPOSIT_AMOUNTS = [
|
|
16
|
+
parseUnits('1', 'gwei').toString(),
|
|
17
|
+
parseUnits('32', 'gwei').toString(),
|
|
18
|
+
parseUnits('8', 'gwei').toString(),
|
|
19
|
+
parseUnits('256', 'gwei').toString(),
|
|
20
|
+
];
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
export const VALID_NON_COMPOUNDING_AMOUNTS = [
|
|
23
|
+
parseUnits('1', 'gwei').toString(),
|
|
24
|
+
parseUnits('32', 'gwei').toString(),
|
|
25
|
+
];
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
27
|
+
// They dont see defaults set in schema
|
|
28
|
+
const validateRewardsSplitRecipients = (
|
|
29
|
+
_: boolean,
|
|
30
|
+
data: RewardsSplitPayload,
|
|
31
|
+
): boolean => {
|
|
32
|
+
const obolRAFSplit =
|
|
33
|
+
data.ObolRAFSplit ?? DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT;
|
|
34
|
+
const splitPercentage = data.splitRecipients.reduce(
|
|
35
|
+
(acc: number, curr: SplitRecipient) => acc + curr.percentAllocation,
|
|
36
|
+
0,
|
|
37
|
+
);
|
|
38
|
+
return splitPercentage + obolRAFSplit === 100;
|
|
36
39
|
};
|
|
37
40
|
|
|
38
|
-
const
|
|
41
|
+
const validateTotalSplitRecipients = (
|
|
39
42
|
_: boolean,
|
|
40
|
-
data:
|
|
43
|
+
data: TotalSplitPayload,
|
|
41
44
|
): boolean => {
|
|
45
|
+
const obolRAFSplit =
|
|
46
|
+
data.ObolRAFSplit ?? DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT;
|
|
42
47
|
const splitPercentage = data.splitRecipients.reduce(
|
|
43
48
|
(acc: number, curr: SplitRecipient) => acc + curr.percentAllocation,
|
|
44
49
|
0,
|
|
45
50
|
);
|
|
46
|
-
|
|
47
|
-
? data.ObolRAFSplit
|
|
48
|
-
: 'principalRecipient' in data
|
|
49
|
-
? DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT
|
|
50
|
-
: DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT;
|
|
51
|
-
return splitPercentage + ObolRAFSplitParam === 100;
|
|
51
|
+
return splitPercentage + obolRAFSplit === 100;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
const validateUniqueAddresses = (
|
|
55
|
+
_: boolean,
|
|
56
|
+
operators: Array<{ address: string }>,
|
|
57
|
+
): boolean => {
|
|
58
|
+
if (!operators) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (operators.length < 4) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (operators.every(op => op.address === '')) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (operators.some(op => op.address.length !== 42)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const addresses = operators.map(op => op.address);
|
|
75
|
+
const uniqueAddresses = new Set(addresses);
|
|
76
|
+
const isUnique = uniqueAddresses.size === addresses.length;
|
|
77
|
+
return isUnique;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ajv = new Ajv({
|
|
81
|
+
allErrors: true,
|
|
82
|
+
useDefaults: true,
|
|
83
|
+
strict: false,
|
|
84
|
+
$data: true,
|
|
85
|
+
});
|
|
86
|
+
addFormats(ajv);
|
|
87
|
+
addKeywords(ajv, ['patternRequired']);
|
|
88
|
+
|
|
89
|
+
ajv.addKeyword({
|
|
90
|
+
keyword: 'validateRewardsSplitRecipients',
|
|
91
|
+
validate: validateRewardsSplitRecipients,
|
|
92
|
+
schemaType: 'boolean',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
ajv.addKeyword({
|
|
96
|
+
keyword: 'validateTotalSplitRecipients',
|
|
97
|
+
validate: validateTotalSplitRecipients,
|
|
98
|
+
schemaType: 'boolean',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
ajv.addKeyword({
|
|
102
|
+
keyword: 'validateUniqueAddresses',
|
|
103
|
+
validate: validateUniqueAddresses,
|
|
104
|
+
schemaType: 'boolean',
|
|
105
|
+
});
|
|
64
106
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Schema compilation errors', ${validate.errors?.[0].message}`,
|
|
75
|
-
);
|
|
107
|
+
export function validatePayload<T>(data: unknown, schema: object): T {
|
|
108
|
+
const validate = ajv.compile<T>(schema);
|
|
109
|
+
const valid = validate(data);
|
|
110
|
+
if (!valid) {
|
|
111
|
+
const errors = validate.errors
|
|
112
|
+
?.map(e => `${e.instancePath} ${e.message}`)
|
|
113
|
+
.join(', ');
|
|
114
|
+
throw new Error(`Validation failed: ${errors}`);
|
|
76
115
|
}
|
|
77
|
-
return
|
|
116
|
+
return data;
|
|
78
117
|
}
|
package/src/constants.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ZeroAddress } from 'ethers';
|
|
2
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
2
|
import { Base } from './base.js';
|
|
4
3
|
import {
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
AVAILABLE_SPLITTER_CHAINS,
|
|
17
16
|
CHAIN_CONFIGURATION,
|
|
18
17
|
DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
19
|
-
DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
|
|
20
18
|
OBOL_SDK_EMAIL,
|
|
21
19
|
} from './constants.js';
|
|
22
20
|
import { ConflictError } from './errors.js';
|
|
@@ -54,6 +52,7 @@ export * from './types.js';
|
|
|
54
52
|
export * from './services.js';
|
|
55
53
|
export * from './verification/signature-validator.js';
|
|
56
54
|
export * from './verification/common.js';
|
|
55
|
+
export * from './constants.js';
|
|
57
56
|
export { Incentives } from './incentives.js';
|
|
58
57
|
|
|
59
58
|
/**
|
|
@@ -174,16 +173,16 @@ export class Client extends Base {
|
|
|
174
173
|
principalRecipient,
|
|
175
174
|
etherAmount,
|
|
176
175
|
ObolRAFSplit = DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
177
|
-
distributorFee
|
|
178
|
-
controllerAddress
|
|
179
|
-
recoveryAddress
|
|
176
|
+
distributorFee,
|
|
177
|
+
controllerAddress,
|
|
178
|
+
recoveryAddress,
|
|
180
179
|
}: RewardsSplitPayload): Promise<ClusterValidator> {
|
|
181
180
|
// This method doesnt require T&C signature
|
|
182
181
|
if (!this.signer) {
|
|
183
182
|
throw new Error('Signer is required in createObolRewardsSplit');
|
|
184
183
|
}
|
|
185
184
|
|
|
186
|
-
validatePayload(
|
|
185
|
+
const validatedPayload = validatePayload<Required<RewardsSplitPayload>>(
|
|
187
186
|
{
|
|
188
187
|
splitRecipients,
|
|
189
188
|
principalRecipient,
|
|
@@ -234,10 +233,10 @@ export class Client extends Base {
|
|
|
234
233
|
const retroActiveFundingRecipient = {
|
|
235
234
|
account:
|
|
236
235
|
CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
|
|
237
|
-
percentAllocation: ObolRAFSplit,
|
|
236
|
+
percentAllocation: validatedPayload.ObolRAFSplit,
|
|
238
237
|
};
|
|
239
238
|
|
|
240
|
-
const copiedSplitRecipients = [...splitRecipients];
|
|
239
|
+
const copiedSplitRecipients = [...validatedPayload.splitRecipients];
|
|
241
240
|
copiedSplitRecipients.push(retroActiveFundingRecipient);
|
|
242
241
|
|
|
243
242
|
const { accounts, percentAllocations } = formatSplitRecipients(
|
|
@@ -249,8 +248,8 @@ export class Client extends Base {
|
|
|
249
248
|
accounts,
|
|
250
249
|
percentAllocations,
|
|
251
250
|
chainId: this.chainId,
|
|
252
|
-
distributorFee,
|
|
253
|
-
controllerAddress,
|
|
251
|
+
distributorFee: validatedPayload.distributorFee,
|
|
252
|
+
controllerAddress: validatedPayload.controllerAddress,
|
|
254
253
|
});
|
|
255
254
|
|
|
256
255
|
const isSplitterDeployed = await isContractAvailable(
|
|
@@ -265,12 +264,12 @@ export class Client extends Base {
|
|
|
265
264
|
predictedSplitterAddress,
|
|
266
265
|
accounts,
|
|
267
266
|
percentAllocations,
|
|
268
|
-
principalRecipient,
|
|
269
|
-
etherAmount,
|
|
267
|
+
principalRecipient: validatedPayload.principalRecipient,
|
|
268
|
+
etherAmount: validatedPayload.etherAmount,
|
|
270
269
|
chainId: this.chainId,
|
|
271
|
-
distributorFee,
|
|
272
|
-
controllerAddress,
|
|
273
|
-
recoveryAddress,
|
|
270
|
+
distributorFee: validatedPayload.distributorFee,
|
|
271
|
+
controllerAddress: validatedPayload.controllerAddress,
|
|
272
|
+
recoveryAddress: validatedPayload.recoveryAddress,
|
|
274
273
|
});
|
|
275
274
|
|
|
276
275
|
return { withdrawal_address, fee_recipient_address };
|
|
@@ -292,16 +291,16 @@ export class Client extends Base {
|
|
|
292
291
|
// add the example reference
|
|
293
292
|
async createObolTotalSplit({
|
|
294
293
|
splitRecipients,
|
|
295
|
-
ObolRAFSplit
|
|
296
|
-
distributorFee
|
|
297
|
-
controllerAddress
|
|
294
|
+
ObolRAFSplit,
|
|
295
|
+
distributorFee,
|
|
296
|
+
controllerAddress,
|
|
298
297
|
}: TotalSplitPayload): Promise<ClusterValidator> {
|
|
299
298
|
// This method doesnt require T&C signature
|
|
300
299
|
if (!this.signer) {
|
|
301
300
|
throw new Error('Signer is required in createObolTotalSplit');
|
|
302
301
|
}
|
|
303
302
|
|
|
304
|
-
validatePayload(
|
|
303
|
+
const validatedPayload = validatePayload<Required<TotalSplitPayload>>(
|
|
305
304
|
{
|
|
306
305
|
splitRecipients,
|
|
307
306
|
ObolRAFSplit,
|
|
@@ -333,10 +332,10 @@ export class Client extends Base {
|
|
|
333
332
|
const retroActiveFundingRecipient = {
|
|
334
333
|
account:
|
|
335
334
|
CHAIN_CONFIGURATION[this.chainId].RETROACTIVE_FUNDING_ADDRESS.address,
|
|
336
|
-
percentAllocation: ObolRAFSplit,
|
|
335
|
+
percentAllocation: validatedPayload.ObolRAFSplit,
|
|
337
336
|
};
|
|
338
337
|
|
|
339
|
-
const copiedSplitRecipients = [...splitRecipients];
|
|
338
|
+
const copiedSplitRecipients = [...validatedPayload.splitRecipients];
|
|
340
339
|
copiedSplitRecipients.push(retroActiveFundingRecipient);
|
|
341
340
|
|
|
342
341
|
const { accounts, percentAllocations } = formatSplitRecipients(
|
|
@@ -347,8 +346,8 @@ export class Client extends Base {
|
|
|
347
346
|
accounts,
|
|
348
347
|
percentAllocations,
|
|
349
348
|
chainId: this.chainId,
|
|
350
|
-
distributorFee,
|
|
351
|
-
controllerAddress,
|
|
349
|
+
distributorFee: validatedPayload.distributorFee,
|
|
350
|
+
controllerAddress: validatedPayload.controllerAddress,
|
|
352
351
|
});
|
|
353
352
|
|
|
354
353
|
const isSplitterDeployed = await isContractAvailable(
|
|
@@ -362,8 +361,8 @@ export class Client extends Base {
|
|
|
362
361
|
accounts,
|
|
363
362
|
percentAllocations,
|
|
364
363
|
chainId: this.chainId,
|
|
365
|
-
distributorFee,
|
|
366
|
-
controllerAddress,
|
|
364
|
+
distributorFee: validatedPayload.distributorFee,
|
|
365
|
+
controllerAddress: validatedPayload.controllerAddress,
|
|
367
366
|
});
|
|
368
367
|
return {
|
|
369
368
|
withdrawal_address: splitterAddress,
|
|
@@ -411,20 +410,20 @@ export class Client extends Base {
|
|
|
411
410
|
throw new Error('Signer is required in createClusterDefinition');
|
|
412
411
|
}
|
|
413
412
|
|
|
414
|
-
validatePayload(
|
|
413
|
+
const validatedCluster = validatePayload<ClusterPayload>(
|
|
414
|
+
newCluster,
|
|
415
|
+
definitionSchema,
|
|
416
|
+
);
|
|
415
417
|
|
|
416
418
|
const clusterConfig: Partial<ClusterDefinition> = {
|
|
417
|
-
...
|
|
419
|
+
...validatedCluster,
|
|
418
420
|
fork_version: this.fork_version,
|
|
419
421
|
dkg_algorithm: DKG_ALGORITHM,
|
|
420
422
|
version: CONFIG_VERSION,
|
|
421
423
|
uuid: uuidv4(),
|
|
422
424
|
timestamp: new Date().toISOString(),
|
|
423
|
-
threshold: Math.ceil((2 *
|
|
424
|
-
num_validators:
|
|
425
|
-
deposit_amounts: newCluster.deposit_amounts
|
|
426
|
-
? newCluster.deposit_amounts
|
|
427
|
-
: ['32000000000'],
|
|
425
|
+
threshold: Math.ceil((2 * validatedCluster.operators.length) / 3),
|
|
426
|
+
num_validators: validatedCluster.validators.length,
|
|
428
427
|
};
|
|
429
428
|
try {
|
|
430
429
|
const address = await this.signer.getAddress();
|
|
@@ -479,7 +478,10 @@ export class Client extends Base {
|
|
|
479
478
|
throw new Error('Signer is required in acceptClusterDefinition');
|
|
480
479
|
}
|
|
481
480
|
|
|
482
|
-
validatePayload(
|
|
481
|
+
const validatedPayload = validatePayload<OperatorPayload>(
|
|
482
|
+
operatorPayload,
|
|
483
|
+
operatorPayloadSchema,
|
|
484
|
+
);
|
|
483
485
|
|
|
484
486
|
try {
|
|
485
487
|
const address = await this.signer.getAddress();
|
|
@@ -492,11 +494,11 @@ export class Client extends Base {
|
|
|
492
494
|
const operatorENRSignature = await this.signer.signTypedData(
|
|
493
495
|
Domain(this.chainId),
|
|
494
496
|
EnrSigningTypes,
|
|
495
|
-
{ enr:
|
|
497
|
+
{ enr: validatedPayload.enr },
|
|
496
498
|
);
|
|
497
499
|
|
|
498
500
|
const operatorData: OperatorPayload = {
|
|
499
|
-
...
|
|
501
|
+
...validatedPayload,
|
|
500
502
|
address,
|
|
501
503
|
enr_signature: operatorENRSignature,
|
|
502
504
|
fork_version: this.fork_version,
|
package/src/schema.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
+
import { ZeroAddress } from 'ethers';
|
|
1
2
|
import {
|
|
2
3
|
DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
3
4
|
DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
|
|
4
5
|
} from './constants';
|
|
6
|
+
import { VALID_DEPOSIT_AMOUNTS, VALID_NON_COMPOUNDING_AMOUNTS } from './ajv';
|
|
5
7
|
|
|
6
8
|
export const operatorPayloadSchema = {
|
|
7
9
|
type: 'object',
|
|
8
10
|
properties: {
|
|
9
|
-
version: {
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
enr: {
|
|
13
|
-
type: 'string',
|
|
14
|
-
},
|
|
11
|
+
version: { type: 'string' },
|
|
12
|
+
enr: { type: 'string' },
|
|
15
13
|
},
|
|
16
14
|
required: ['version', 'enr'],
|
|
17
15
|
};
|
|
@@ -19,24 +17,20 @@ export const operatorPayloadSchema = {
|
|
|
19
17
|
export const definitionSchema = {
|
|
20
18
|
type: 'object',
|
|
21
19
|
properties: {
|
|
22
|
-
name: {
|
|
23
|
-
type: 'string',
|
|
24
|
-
},
|
|
20
|
+
name: { type: 'string' },
|
|
25
21
|
operators: {
|
|
26
22
|
type: 'array',
|
|
27
23
|
minItems: 4,
|
|
28
|
-
uniqueItems: true,
|
|
29
24
|
items: {
|
|
30
25
|
type: 'object',
|
|
31
26
|
properties: {
|
|
32
27
|
address: {
|
|
33
28
|
type: 'string',
|
|
34
|
-
minLength: 42,
|
|
35
|
-
maxLength: 42,
|
|
36
29
|
},
|
|
37
30
|
},
|
|
38
31
|
required: ['address'],
|
|
39
32
|
},
|
|
33
|
+
validateUniqueAddresses: true,
|
|
40
34
|
},
|
|
41
35
|
validators: {
|
|
42
36
|
type: 'array',
|
|
@@ -57,12 +51,39 @@ export const definitionSchema = {
|
|
|
57
51
|
},
|
|
58
52
|
},
|
|
59
53
|
deposit_amounts: {
|
|
60
|
-
type: 'array',
|
|
54
|
+
type: ['array', 'null'],
|
|
61
55
|
items: {
|
|
62
56
|
type: 'string',
|
|
63
57
|
pattern: '^[0-9]+$',
|
|
64
58
|
},
|
|
65
|
-
|
|
59
|
+
if: {
|
|
60
|
+
$data: '1/compounding',
|
|
61
|
+
},
|
|
62
|
+
then: {
|
|
63
|
+
items: {
|
|
64
|
+
enum: VALID_DEPOSIT_AMOUNTS,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
else: {
|
|
68
|
+
items: {
|
|
69
|
+
enum: VALID_NON_COMPOUNDING_AMOUNTS,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
default: null,
|
|
73
|
+
},
|
|
74
|
+
compounding: {
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
78
|
+
target_gas_limit: {
|
|
79
|
+
type: 'number',
|
|
80
|
+
minimum: 1,
|
|
81
|
+
default: 36000000,
|
|
82
|
+
},
|
|
83
|
+
consensus_protocol: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
enum: ['qbft', ''],
|
|
86
|
+
default: '',
|
|
66
87
|
},
|
|
67
88
|
},
|
|
68
89
|
required: ['name', 'operators', 'validators'],
|
|
@@ -80,9 +101,7 @@ export const totalSplitterPayloadSchema = {
|
|
|
80
101
|
type: 'string',
|
|
81
102
|
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
82
103
|
},
|
|
83
|
-
percentAllocation: {
|
|
84
|
-
type: 'number',
|
|
85
|
-
},
|
|
104
|
+
percentAllocation: { type: 'number' },
|
|
86
105
|
},
|
|
87
106
|
required: ['account', 'percentAllocation'],
|
|
88
107
|
},
|
|
@@ -90,40 +109,44 @@ export const totalSplitterPayloadSchema = {
|
|
|
90
109
|
ObolRAFSplit: {
|
|
91
110
|
type: 'number',
|
|
92
111
|
minimum: DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
|
|
112
|
+
default: DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT,
|
|
93
113
|
},
|
|
94
114
|
distributorFee: {
|
|
95
115
|
type: 'number',
|
|
96
116
|
maximum: 10,
|
|
97
117
|
multipleOf: 0.01,
|
|
118
|
+
default: 0,
|
|
98
119
|
},
|
|
99
120
|
controllerAddress: {
|
|
100
121
|
type: 'string',
|
|
101
122
|
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
123
|
+
default: ZeroAddress,
|
|
102
124
|
},
|
|
103
|
-
validateSplitRecipients: true,
|
|
104
125
|
},
|
|
126
|
+
validateTotalSplitRecipients: true,
|
|
105
127
|
required: ['splitRecipients'],
|
|
106
128
|
};
|
|
107
129
|
|
|
108
130
|
export const rewardsSplitterPayloadSchema = {
|
|
109
|
-
|
|
131
|
+
type: 'object',
|
|
110
132
|
properties: {
|
|
111
133
|
...totalSplitterPayloadSchema.properties,
|
|
112
134
|
ObolRAFSplit: {
|
|
113
135
|
type: 'number',
|
|
114
136
|
minimum: DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
137
|
+
default: DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT,
|
|
115
138
|
},
|
|
116
139
|
recoveryAddress: {
|
|
117
140
|
type: 'string',
|
|
118
141
|
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
142
|
+
default: ZeroAddress,
|
|
119
143
|
},
|
|
120
|
-
etherAmount: {
|
|
121
|
-
type: 'number',
|
|
122
|
-
},
|
|
144
|
+
etherAmount: { type: 'number' },
|
|
123
145
|
principalRecipient: {
|
|
124
146
|
type: 'string',
|
|
125
147
|
pattern: '^0x[a-fA-F0-9]{40}$',
|
|
126
148
|
},
|
|
127
149
|
},
|
|
150
|
+
validateRewardsSplitRecipients: true,
|
|
128
151
|
required: ['splitRecipients', 'principalRecipient', 'etherAmount'],
|
|
129
152
|
};
|
package/src/types.ts
CHANGED
|
@@ -105,6 +105,15 @@ export type ClusterPayload = {
|
|
|
105
105
|
|
|
106
106
|
/** The cluster partial deposits in gwei or 32000000000. */
|
|
107
107
|
deposit_amounts?: string[] | null;
|
|
108
|
+
|
|
109
|
+
/** A withdrawal mechanism with 0x02 withdrawal credentials. */
|
|
110
|
+
compounding?: boolean;
|
|
111
|
+
|
|
112
|
+
/** The target gas limit where default is 36M. */
|
|
113
|
+
target_gas_limit?: number;
|
|
114
|
+
|
|
115
|
+
/** The consensus protocol e.g qbft. */
|
|
116
|
+
consensus_protocol?: string;
|
|
108
117
|
};
|
|
109
118
|
|
|
110
119
|
/**
|
|
@@ -144,7 +153,7 @@ export interface ClusterDefinition extends ClusterPayload {
|
|
|
144
153
|
/** The consensus protocol e.g qbft. */
|
|
145
154
|
consensus_protocol?: string;
|
|
146
155
|
|
|
147
|
-
/** The target gas limit where default is
|
|
156
|
+
/** The target gas limit where default is 36M. */
|
|
148
157
|
target_gas_limit?: number;
|
|
149
158
|
|
|
150
159
|
/** A withdrawal mechanism with 0x02 withdrawal credentials. */
|
|
@@ -305,7 +314,7 @@ export type ClaimableIncentives = {
|
|
|
305
314
|
/** The amount the recipient is entitled to. */
|
|
306
315
|
amount: string;
|
|
307
316
|
|
|
308
|
-
/** The recipient
|
|
317
|
+
/** The recipient's index in the Merkle tree. */
|
|
309
318
|
index: number;
|
|
310
319
|
|
|
311
320
|
/** The Merkle proof (an array of hashes) generated for the recipient. */
|
|
@@ -251,6 +251,30 @@ export const verifyDVV1X8 = (clusterLock: ClusterLock): boolean => {
|
|
|
251
251
|
pubShares.push(fromHexString(element));
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// Check deposit amounts match exactly if they are defined
|
|
255
|
+
const depositAmounts = clusterLock.cluster_definition.deposit_amounts;
|
|
256
|
+
if (depositAmounts !== null) {
|
|
257
|
+
const partialDepositAmounts = (
|
|
258
|
+
validator.partial_deposit_data as DepositData[]
|
|
259
|
+
).map(d => d.amount);
|
|
260
|
+
if (depositAmounts?.length !== partialDepositAmounts.length) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
// Check that both arrays contain exactly the same elements
|
|
264
|
+
const sortedDepositAmounts = [...depositAmounts]
|
|
265
|
+
.map(Number)
|
|
266
|
+
.sort((a, b) => a - b);
|
|
267
|
+
const sortedPartialAmounts = [...partialDepositAmounts]
|
|
268
|
+
.map(Number)
|
|
269
|
+
.sort((a, b) => a - b);
|
|
270
|
+
if (
|
|
271
|
+
!sortedDepositAmounts.every(
|
|
272
|
+
(amount, index) => amount === sortedPartialAmounts[index],
|
|
273
|
+
)
|
|
274
|
+
) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
254
278
|
// Deposit Data Verification
|
|
255
279
|
for (const element of validator.partial_deposit_data as DepositData[]) {
|
|
256
280
|
const depositData = element;
|