@velocitycareerlabs/metadata-registry-contract 1.25.0-dev-build.12642c864
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/.openzeppelin/unknown-1480.json +1336 -0
- package/.openzeppelin/unknown-1481.json +1199 -0
- package/.openzeppelin/unknown-1482.json +1198 -0
- package/.openzeppelin/unknown-1483.json +702 -0
- package/LICENSE +248 -0
- package/build/contracts/Migrations.json +1104 -0
- package/contracts/MetadataRegistry.sol +351 -0
- package/contracts/Migrations.sol +19 -0
- package/migration-status.js +42 -0
- package/migrations/10_vl-5092-use-check-operator-credential-type.js +19 -0
- package/migrations/11_vl-8185-allow-unknown-types.js +19 -0
- package/migrations/1_initial_migration.js +5 -0
- package/migrations/2_deploy_metadata.js +29 -0
- package/migrations/3_upgrade_metadata.js +44 -0
- package/migrations/4_upgrade_metadata.js +21 -0
- package/migrations/5_upgrade_metadata_send_msg_sender_burn.js +21 -0
- package/migrations/6_upgrade_metadata_add_permissions.js +18 -0
- package/migrations/7_vl-5477-signatures.js +19 -0
- package/migrations/8_vl-5480-signatures.js +19 -0
- package/migrations/9_vl-5092-new-permissions.js +19 -0
- package/package.json +43 -0
- package/test/.gitkeep +0 -0
- package/test/metadata-registry.test.js +1512 -0
- package/truffle-config.js +31 -0
@@ -0,0 +1,1512 @@
|
|
1
|
+
const truffleAssert = require('truffle-assertions');
|
2
|
+
const { keccak256, AbiCoder, Wallet } = require('ethers');
|
3
|
+
const { signAddress } = require('@velocitycareerlabs/blockchain-functions');
|
4
|
+
|
5
|
+
const MetadataRegistry = artifacts.require('../contracts/MetadataRegistry.sol');
|
6
|
+
const VerificationCoupon = artifacts.require(
|
7
|
+
'../../verification-coupon/contracts/VerificationCoupon.sol'
|
8
|
+
);
|
9
|
+
const Permissions = artifacts.require(
|
10
|
+
'../../permissions/contracts/Permissions'
|
11
|
+
);
|
12
|
+
|
13
|
+
const { map } = require('lodash/fp');
|
14
|
+
const { createHash } = require('crypto');
|
15
|
+
|
16
|
+
const get2BytesHash = (value) => {
|
17
|
+
return `0x${createHash('sha256').update(value).digest('hex').slice(0, 4)}`;
|
18
|
+
};
|
19
|
+
|
20
|
+
const oneDaySeconds = 60 * 60 * 24;
|
21
|
+
const expirationTime = Math.floor(Date.now() / 1000) + 90 * oneDaySeconds;
|
22
|
+
const expiredTime = expirationTime - 180 * oneDaySeconds;
|
23
|
+
const testListAlgType = '0x6733';
|
24
|
+
const testListVersion = '0x6733';
|
25
|
+
const nonExistentCredentialTypeHash = '0x6732';
|
26
|
+
const regularIssuingCredentialTypeHash = '0xaf29';
|
27
|
+
const identityIssuingCredentialTypeHash = '0xeea2';
|
28
|
+
const contactIssuingCredentialTypeHash = '0x4ffb';
|
29
|
+
|
30
|
+
const bytes =
|
31
|
+
'0xa6626964787d657468657265756d3a2f2f30783931333231303338323138333134303931322f6765745f63726564656e7469616c5f6d657461646174615f6c6973745f6973737565725f76633f6c69737449643d3930323133393132333231303339323130333231266163636f756e7449643d307831383339313233383231333231336474797065827456657269666961626c6543726564656e7469616c781c43726564656e7469616c4d657461646174614c6973744865616465726669737375657278186469643a76656c6f636974793a30783932313331323331326669737375656474323031392d30382d33325430393a34343a30305a7163726564656e7469616c5375626a656374a262696478186469643a76656c6f636974793a3078393231333132333132666c6973744964fb44138fe0cd20f71c6570726f6f66a5656e6f6e6365fb4429e9f10d9813d9676372656174656474323031392d30382d33325430393a34343a30305a6c70726f6f66507572706f73656f617373657274696f6e4d6574686f6472766572696669636174696f6e4d6574686f64781e6469643a76656c6f636974793a3078393231333132333132236b65792d31636a7773652e2e2e2e2e';
|
32
|
+
const sampleEntry = [testListVersion, regularIssuingCredentialTypeHash, testListAlgType, bytes, bytes];
|
33
|
+
const traceId = 'trackingId';
|
34
|
+
const caoDid = 'did:velocity:42';
|
35
|
+
const burnerDid = 'did:velocity:321';
|
36
|
+
const ownerDid = 'did:velocity:123';
|
37
|
+
const freeCredentialTypesList = [
|
38
|
+
'Email',
|
39
|
+
'EmailV1.0',
|
40
|
+
'Phone',
|
41
|
+
'PhoneV1.0',
|
42
|
+
'IdDocument',
|
43
|
+
'IdDocumentV1.0',
|
44
|
+
'PassportV1.0',
|
45
|
+
'DrivingLicenseV1.0',
|
46
|
+
'NationalIdCardV1.0',
|
47
|
+
'ProofOfAgeV1.0',
|
48
|
+
'ResidentPermitV1.0',
|
49
|
+
];
|
50
|
+
const freeCredentialTypesBytes2 = map(get2BytesHash, freeCredentialTypesList);
|
51
|
+
|
52
|
+
const setupContracts = async (primaryAccount, operatorAccount) => {
|
53
|
+
const permissionsInstance = await Permissions.new();
|
54
|
+
await permissionsInstance.initialize();
|
55
|
+
|
56
|
+
const verificationCouponInstance = await VerificationCoupon.new();
|
57
|
+
await verificationCouponInstance.initialize(
|
58
|
+
'Velocity Verification Coupon',
|
59
|
+
'https://www.velocitynetwork.foundation/'
|
60
|
+
);
|
61
|
+
await verificationCouponInstance.setPermissionsAddress(
|
62
|
+
permissionsInstance.address
|
63
|
+
);
|
64
|
+
const metadataRegistryInstance = await MetadataRegistry.new();
|
65
|
+
await metadataRegistryInstance.initialize(
|
66
|
+
verificationCouponInstance.address,
|
67
|
+
freeCredentialTypesBytes2
|
68
|
+
);
|
69
|
+
await permissionsInstance.addAddressScope(
|
70
|
+
metadataRegistryInstance.address,
|
71
|
+
'coupon:burn'
|
72
|
+
);
|
73
|
+
|
74
|
+
await metadataRegistryInstance.setPermissionsAddress(
|
75
|
+
permissionsInstance.address
|
76
|
+
);
|
77
|
+
|
78
|
+
await permissionsInstance.addPrimary(
|
79
|
+
primaryAccount,
|
80
|
+
primaryAccount,
|
81
|
+
primaryAccount
|
82
|
+
);
|
83
|
+
await permissionsInstance.addAddressScope(
|
84
|
+
primaryAccount,
|
85
|
+
'transactions:write'
|
86
|
+
);
|
87
|
+
await permissionsInstance.addOperatorKey(primaryAccount, operatorAccount, {
|
88
|
+
from: primaryAccount,
|
89
|
+
});
|
90
|
+
|
91
|
+
return {
|
92
|
+
verificationCouponInstance,
|
93
|
+
metadataRegistryInstance,
|
94
|
+
permissionsInstance,
|
95
|
+
};
|
96
|
+
};
|
97
|
+
contract('MetadataRegistry', async (accounts) => {
|
98
|
+
const deployerAccount = accounts[0];
|
99
|
+
const primaryAccount = accounts[1];
|
100
|
+
const operatorAccount = accounts[2];
|
101
|
+
const randomTxAccount = accounts[3];
|
102
|
+
const randomNonTxAccount = accounts[4];
|
103
|
+
const operatorWallet = new Wallet(
|
104
|
+
'0x33f46d353f191f8067dc7d256e9d9ee7a2a3300649ff7c70fe1cd7e5d5237da5'
|
105
|
+
);
|
106
|
+
const impersonatorWallet = new Wallet(
|
107
|
+
'0x4c30c0c2c34f080b4d7dd150f7afa66c3fe000fb037592516f9b85c031e4b6b3'
|
108
|
+
);
|
109
|
+
|
110
|
+
describe('Validate the create list and set entry functions', async () => {
|
111
|
+
let verificationCouponInstance;
|
112
|
+
let metadataRegistryInstance;
|
113
|
+
let permissionsInstance;
|
114
|
+
|
115
|
+
before(async () => {
|
116
|
+
({verificationCouponInstance, metadataRegistryInstance, permissionsInstance} = await setupContracts(primaryAccount, operatorAccount))
|
117
|
+
});
|
118
|
+
beforeEach(async () => {
|
119
|
+
await permissionsInstance.addAddressScope(
|
120
|
+
primaryAccount,
|
121
|
+
'credential:issue'
|
122
|
+
);
|
123
|
+
await permissionsInstance.addAddressScope(
|
124
|
+
primaryAccount,
|
125
|
+
'credential:identityissue'
|
126
|
+
);
|
127
|
+
await permissionsInstance.addAddressScope(
|
128
|
+
primaryAccount,
|
129
|
+
'credential:contactissue'
|
130
|
+
);
|
131
|
+
});
|
132
|
+
it('Deploy Verification Coupon & Metadata Registry', async () => {
|
133
|
+
verificationCouponInstance = await VerificationCoupon.new();
|
134
|
+
await verificationCouponInstance.initialize(
|
135
|
+
'Velocity Verification Coupon',
|
136
|
+
'https://www.velocitynetwork.foundation/'
|
137
|
+
);
|
138
|
+
metadataRegistryInstance = await MetadataRegistry.new();
|
139
|
+
await metadataRegistryInstance.initialize(
|
140
|
+
verificationCouponInstance.address,
|
141
|
+
freeCredentialTypesBytes2
|
142
|
+
);
|
143
|
+
await metadataRegistryInstance.setPermissionsAddress(
|
144
|
+
permissionsInstance.address
|
145
|
+
);
|
146
|
+
});
|
147
|
+
it('Method isExistMetadataList returns false when the account and list were not created', async () => {
|
148
|
+
const result = await metadataRegistryInstance.isExistMetadataList(
|
149
|
+
'0x0000000000000000000000000000000000000000',
|
150
|
+
42
|
151
|
+
);
|
152
|
+
assert.equal(result, false);
|
153
|
+
});
|
154
|
+
it('Create new metadata list should fail if not an operator', async () => {
|
155
|
+
await truffleAssert.fails(
|
156
|
+
metadataRegistryInstance.newMetadataList(
|
157
|
+
1,
|
158
|
+
testListAlgType,
|
159
|
+
testListVersion,
|
160
|
+
bytes,
|
161
|
+
traceId,
|
162
|
+
caoDid
|
163
|
+
),
|
164
|
+
'Permissions: operator not pointing to a primary'
|
165
|
+
);
|
166
|
+
});
|
167
|
+
it('Create new metadata list', async () => {
|
168
|
+
const result = await metadataRegistryInstance.newMetadataList(
|
169
|
+
1,
|
170
|
+
testListAlgType,
|
171
|
+
testListVersion,
|
172
|
+
bytes,
|
173
|
+
traceId,
|
174
|
+
caoDid,
|
175
|
+
{ from: operatorAccount }
|
176
|
+
);
|
177
|
+
truffleAssert.eventEmitted(result, 'CreatedMetadataList');
|
178
|
+
});
|
179
|
+
it('newMetadataListSigned should fail with empty signature', async () => {
|
180
|
+
await truffleAssert.fails(
|
181
|
+
metadataRegistryInstance.newMetadataListSigned(
|
182
|
+
1,
|
183
|
+
nonExistentCredentialTypeHash,
|
184
|
+
nonExistentCredentialTypeHash,
|
185
|
+
bytes,
|
186
|
+
traceId,
|
187
|
+
caoDid,
|
188
|
+
'',
|
189
|
+
{ from: randomTxAccount }
|
190
|
+
),
|
191
|
+
'invalid arrayify value'
|
192
|
+
);
|
193
|
+
});
|
194
|
+
it('newMetadataListSigned should fail with bad signature length', async () => {
|
195
|
+
await truffleAssert.fails(
|
196
|
+
metadataRegistryInstance.newMetadataListSigned(
|
197
|
+
1,
|
198
|
+
nonExistentCredentialTypeHash,
|
199
|
+
nonExistentCredentialTypeHash,
|
200
|
+
bytes,
|
201
|
+
traceId,
|
202
|
+
caoDid,
|
203
|
+
'0x90c082e8de5b2f45aab09bcf5d00e27a19d87a2de31536e6c',
|
204
|
+
{ from: randomTxAccount }
|
205
|
+
),
|
206
|
+
'invalid signature length'
|
207
|
+
);
|
208
|
+
});
|
209
|
+
it('newMetadataListSigned should fail with arbitrary signature', async () => {
|
210
|
+
await truffleAssert.fails(
|
211
|
+
metadataRegistryInstance.newMetadataListSigned(
|
212
|
+
1,
|
213
|
+
nonExistentCredentialTypeHash,
|
214
|
+
nonExistentCredentialTypeHash,
|
215
|
+
bytes,
|
216
|
+
traceId,
|
217
|
+
caoDid,
|
218
|
+
'0x90c082e8de5b2f45aab09bcf5d00e27a19d87a2de31536e6cda19da761c0f6845aea70eb9946fe47a4549b1ff205e098994a5bd2db772d9bfc407142e97081e11c',
|
219
|
+
{ from: randomTxAccount }
|
220
|
+
),
|
221
|
+
'Permissions: operator not pointing to a primary'
|
222
|
+
);
|
223
|
+
});
|
224
|
+
it('newMetadataListSigned should fail when wrong address payload is signed', async () => {
|
225
|
+
const signature = signAddress({
|
226
|
+
address: randomNonTxAccount,
|
227
|
+
signerWallet: operatorWallet,
|
228
|
+
});
|
229
|
+
await truffleAssert.fails(
|
230
|
+
metadataRegistryInstance.newMetadataListSigned(
|
231
|
+
1,
|
232
|
+
nonExistentCredentialTypeHash,
|
233
|
+
nonExistentCredentialTypeHash,
|
234
|
+
bytes,
|
235
|
+
traceId,
|
236
|
+
caoDid,
|
237
|
+
signature,
|
238
|
+
{ from: randomTxAccount }
|
239
|
+
),
|
240
|
+
'Permissions: operator not pointing to a primary'
|
241
|
+
);
|
242
|
+
});
|
243
|
+
it('newMetadataListSigned should fail when wrong payload type is signed', async () => {
|
244
|
+
const encodedArgs = AbiCoder.defaultAbiCoder().encode(['uint256'], [10]);
|
245
|
+
const hash = keccak256(encodedArgs);
|
246
|
+
const signature = operatorWallet.signingKey.sign(hash).serialized;
|
247
|
+
await truffleAssert.fails(
|
248
|
+
metadataRegistryInstance.newMetadataListSigned(
|
249
|
+
1,
|
250
|
+
nonExistentCredentialTypeHash,
|
251
|
+
nonExistentCredentialTypeHash,
|
252
|
+
bytes,
|
253
|
+
traceId,
|
254
|
+
caoDid,
|
255
|
+
signature,
|
256
|
+
{ from: randomTxAccount }
|
257
|
+
),
|
258
|
+
'Permissions: operator not pointing to a primary'
|
259
|
+
);
|
260
|
+
});
|
261
|
+
it('newMetadataListSigned should fail when not signed by the operator', async () => {
|
262
|
+
const signature = signAddress({
|
263
|
+
address: randomNonTxAccount,
|
264
|
+
signerWallet: impersonatorWallet,
|
265
|
+
});
|
266
|
+
await truffleAssert.fails(
|
267
|
+
metadataRegistryInstance.newMetadataListSigned(
|
268
|
+
1,
|
269
|
+
nonExistentCredentialTypeHash,
|
270
|
+
nonExistentCredentialTypeHash,
|
271
|
+
bytes,
|
272
|
+
traceId,
|
273
|
+
caoDid,
|
274
|
+
signature,
|
275
|
+
{ from: randomTxAccount }
|
276
|
+
),
|
277
|
+
'Permissions: operator not pointing to a primary'
|
278
|
+
);
|
279
|
+
});
|
280
|
+
it('Create new metadata list with newMetadataListSigned', async () => {
|
281
|
+
const signature = signAddress({
|
282
|
+
address: randomTxAccount,
|
283
|
+
signerWallet: operatorWallet,
|
284
|
+
});
|
285
|
+
const result = await metadataRegistryInstance.newMetadataListSigned(
|
286
|
+
2,
|
287
|
+
nonExistentCredentialTypeHash,
|
288
|
+
nonExistentCredentialTypeHash,
|
289
|
+
bytes,
|
290
|
+
traceId,
|
291
|
+
caoDid,
|
292
|
+
signature,
|
293
|
+
{ from: randomTxAccount }
|
294
|
+
);
|
295
|
+
truffleAssert.eventEmitted(result, 'CreatedMetadataList');
|
296
|
+
});
|
297
|
+
it('Check if the metadata list exist', async () => {
|
298
|
+
const result = await metadataRegistryInstance.isExistMetadataList(
|
299
|
+
primaryAccount,
|
300
|
+
1
|
301
|
+
);
|
302
|
+
assert.equal(result, true);
|
303
|
+
});
|
304
|
+
it('Method isExistMetadataList returns false when only the list were not created', async () => {
|
305
|
+
const result = await metadataRegistryInstance.isExistMetadataList(
|
306
|
+
accounts[0],
|
307
|
+
42
|
308
|
+
);
|
309
|
+
assert.equal(result, false);
|
310
|
+
});
|
311
|
+
it('Create list with the created already listId throws an error', async () => {
|
312
|
+
await truffleAssert.fails(
|
313
|
+
metadataRegistryInstance.newMetadataList(
|
314
|
+
1,
|
315
|
+
testListAlgType,
|
316
|
+
testListVersion,
|
317
|
+
bytes,
|
318
|
+
traceId,
|
319
|
+
caoDid,
|
320
|
+
{ from: operatorAccount }
|
321
|
+
),
|
322
|
+
'List id already used'
|
323
|
+
);
|
324
|
+
});
|
325
|
+
it('setEntry should set entries on the existing list using operator as transactor', async () => {
|
326
|
+
const results = [];
|
327
|
+
for (let i = 0; i <= 2; i++) {
|
328
|
+
results.push(
|
329
|
+
metadataRegistryInstance.setEntry(
|
330
|
+
regularIssuingCredentialTypeHash,
|
331
|
+
bytes,
|
332
|
+
1,
|
333
|
+
i,
|
334
|
+
traceId,
|
335
|
+
caoDid,
|
336
|
+
{ from: operatorAccount }
|
337
|
+
)
|
338
|
+
);
|
339
|
+
}
|
340
|
+
|
341
|
+
const txs = await Promise.all(results);
|
342
|
+
txs.forEach((result, i) => {
|
343
|
+
truffleAssert.eventEmitted(result, 'AddedCredentialMetadata', {
|
344
|
+
sender: primaryAccount,
|
345
|
+
listId: web3.utils.toBN('1'),
|
346
|
+
index: web3.utils.toBN(i),
|
347
|
+
traceId,
|
348
|
+
caoDid,
|
349
|
+
});
|
350
|
+
});
|
351
|
+
});
|
352
|
+
it('setEntrySigned should set 3 entries on the existing list using operator as signer', async () => {
|
353
|
+
const results = [];
|
354
|
+
const signature = signAddress({
|
355
|
+
address: randomTxAccount,
|
356
|
+
signerWallet: operatorWallet,
|
357
|
+
});
|
358
|
+
|
359
|
+
for (let i = 3; i <= 5; i++) {
|
360
|
+
results.push(
|
361
|
+
metadataRegistryInstance.setEntrySigned(
|
362
|
+
regularIssuingCredentialTypeHash,
|
363
|
+
bytes,
|
364
|
+
1,
|
365
|
+
i,
|
366
|
+
traceId,
|
367
|
+
caoDid,
|
368
|
+
signature,
|
369
|
+
{ from: randomTxAccount }
|
370
|
+
)
|
371
|
+
);
|
372
|
+
}
|
373
|
+
|
374
|
+
const txs = await Promise.all(results);
|
375
|
+
txs.forEach((result, i) => {
|
376
|
+
truffleAssert.eventEmitted(result, 'AddedCredentialMetadata', {
|
377
|
+
sender: primaryAccount,
|
378
|
+
listId: web3.utils.toBN('1'),
|
379
|
+
index: web3.utils.toBN(i + 3),
|
380
|
+
traceId,
|
381
|
+
caoDid,
|
382
|
+
});
|
383
|
+
});
|
384
|
+
});
|
385
|
+
it('setEntrySigned should fallback to regular issuing permission if credentialType is not known', async () => {
|
386
|
+
const signature = signAddress({
|
387
|
+
address: randomTxAccount,
|
388
|
+
signerWallet: operatorWallet,
|
389
|
+
});
|
390
|
+
const tx = await metadataRegistryInstance.setEntrySigned(
|
391
|
+
nonExistentCredentialTypeHash,
|
392
|
+
bytes,
|
393
|
+
1,
|
394
|
+
6,
|
395
|
+
traceId,
|
396
|
+
caoDid,
|
397
|
+
signature,
|
398
|
+
{ from: randomTxAccount }
|
399
|
+
)
|
400
|
+
truffleAssert.eventEmitted(tx, 'AddedCredentialMetadata', {
|
401
|
+
sender: primaryAccount,
|
402
|
+
listId: web3.utils.toBN('1'),
|
403
|
+
index: web3.utils.toBN('6'),
|
404
|
+
traceId,
|
405
|
+
caoDid,
|
406
|
+
});
|
407
|
+
});
|
408
|
+
it('setEntrySigned should fail if primary lacks regular issuing permissions', async () => {
|
409
|
+
await permissionsInstance.removeAddressScope(
|
410
|
+
primaryAccount,
|
411
|
+
'credential:issue'
|
412
|
+
);
|
413
|
+
|
414
|
+
const signature = signAddress({
|
415
|
+
address: randomTxAccount,
|
416
|
+
signerWallet: operatorWallet,
|
417
|
+
});
|
418
|
+
await truffleAssert.fails(metadataRegistryInstance.setEntrySigned(
|
419
|
+
regularIssuingCredentialTypeHash,
|
420
|
+
bytes,
|
421
|
+
1,
|
422
|
+
0,
|
423
|
+
traceId,
|
424
|
+
caoDid,
|
425
|
+
signature,
|
426
|
+
{ from: randomTxAccount }
|
427
|
+
), 'Permissions: primary of operator lacks credential:issue permission')
|
428
|
+
});
|
429
|
+
it('setEntrySigned should fail if primary lacks contact issuing permissions', async () => {
|
430
|
+
await permissionsInstance.removeAddressScope(
|
431
|
+
primaryAccount,
|
432
|
+
'credential:contactissue'
|
433
|
+
);
|
434
|
+
|
435
|
+
const signature = signAddress({
|
436
|
+
address: randomTxAccount,
|
437
|
+
signerWallet: operatorWallet,
|
438
|
+
});
|
439
|
+
await truffleAssert.fails(metadataRegistryInstance.setEntrySigned(
|
440
|
+
contactIssuingCredentialTypeHash,
|
441
|
+
bytes,
|
442
|
+
1,
|
443
|
+
0,
|
444
|
+
traceId,
|
445
|
+
caoDid,
|
446
|
+
signature,
|
447
|
+
{ from: randomTxAccount }
|
448
|
+
), 'Permissions: primary of operator lacks credential:contactissue permission')
|
449
|
+
});
|
450
|
+
it('setEntrySigned should fail if primary lacks identity issuing permissions', async () => {
|
451
|
+
await permissionsInstance.removeAddressScope(
|
452
|
+
primaryAccount,
|
453
|
+
'credential:identityissue'
|
454
|
+
);
|
455
|
+
|
456
|
+
const signature = signAddress({
|
457
|
+
address: randomTxAccount,
|
458
|
+
signerWallet: operatorWallet,
|
459
|
+
});
|
460
|
+
await truffleAssert.fails(metadataRegistryInstance.setEntrySigned(
|
461
|
+
identityIssuingCredentialTypeHash,
|
462
|
+
bytes,
|
463
|
+
1,
|
464
|
+
0,
|
465
|
+
traceId,
|
466
|
+
caoDid,
|
467
|
+
signature,
|
468
|
+
{ from: randomTxAccount }
|
469
|
+
), 'Permissions: primary of operator lacks credential:identityissue permission')
|
470
|
+
});
|
471
|
+
it('setEntrySigned should fail with empty signature', async () => {
|
472
|
+
await truffleAssert.fails(
|
473
|
+
metadataRegistryInstance.setEntrySigned(
|
474
|
+
nonExistentCredentialTypeHash,
|
475
|
+
bytes,
|
476
|
+
1,
|
477
|
+
7,
|
478
|
+
traceId,
|
479
|
+
caoDid,
|
480
|
+
'',
|
481
|
+
{ from: randomTxAccount }
|
482
|
+
),
|
483
|
+
'invalid arrayify value'
|
484
|
+
);
|
485
|
+
});
|
486
|
+
it('setEntrySigned should fail with bad signature length', async () => {
|
487
|
+
await truffleAssert.fails(
|
488
|
+
metadataRegistryInstance.setEntrySigned(
|
489
|
+
nonExistentCredentialTypeHash,
|
490
|
+
bytes,
|
491
|
+
1,
|
492
|
+
7,
|
493
|
+
traceId,
|
494
|
+
caoDid,
|
495
|
+
'0x90c082e8de5b2f45aab09bcf5d00e27a19d87a2de31536e6c',
|
496
|
+
{ from: randomTxAccount }
|
497
|
+
),
|
498
|
+
'invalid signature length'
|
499
|
+
);
|
500
|
+
});
|
501
|
+
it('setEntrySigned should fail with arbitrary signature', async () => {
|
502
|
+
await truffleAssert.fails(
|
503
|
+
metadataRegistryInstance.setEntrySigned(
|
504
|
+
nonExistentCredentialTypeHash,
|
505
|
+
bytes,
|
506
|
+
1,
|
507
|
+
7,
|
508
|
+
traceId,
|
509
|
+
caoDid,
|
510
|
+
'0x90c082e8de5b2f45aab09bcf5d00e27a19d87a2de31536e6cda19da761c0f6845aea70eb9946fe47a4549b1ff205e098994a5bd2db772d9bfc407142e97081e11c',
|
511
|
+
{ from: randomTxAccount }
|
512
|
+
),
|
513
|
+
'Permissions: operator not pointing to a primary'
|
514
|
+
);
|
515
|
+
});
|
516
|
+
it('setEntrySigned should fail when wrong address payload is signed', async () => {
|
517
|
+
const signature = signAddress({
|
518
|
+
address: randomNonTxAccount,
|
519
|
+
signerWallet: operatorWallet,
|
520
|
+
});
|
521
|
+
await truffleAssert.fails(
|
522
|
+
metadataRegistryInstance.setEntrySigned(
|
523
|
+
nonExistentCredentialTypeHash,
|
524
|
+
bytes,
|
525
|
+
1,
|
526
|
+
7,
|
527
|
+
traceId,
|
528
|
+
caoDid,
|
529
|
+
signature,
|
530
|
+
{ from: randomTxAccount }
|
531
|
+
),
|
532
|
+
'Permissions: operator not pointing to a primary'
|
533
|
+
);
|
534
|
+
});
|
535
|
+
it('setEntrySigned should fail when wrong payload type is signed', async () => {
|
536
|
+
const encodedArgs = AbiCoder.defaultAbiCoder().encode(['uint256'], [10]);
|
537
|
+
const hash = keccak256(encodedArgs);
|
538
|
+
const signature = operatorWallet.signingKey.sign(hash).serialized;
|
539
|
+
await truffleAssert.fails(
|
540
|
+
metadataRegistryInstance.setEntrySigned(
|
541
|
+
nonExistentCredentialTypeHash,
|
542
|
+
bytes,
|
543
|
+
1,
|
544
|
+
7,
|
545
|
+
traceId,
|
546
|
+
caoDid,
|
547
|
+
signature,
|
548
|
+
{ from: randomTxAccount }
|
549
|
+
),
|
550
|
+
'Permissions: operator not pointing to a primary'
|
551
|
+
);
|
552
|
+
});
|
553
|
+
it('setEntrySigned should fail when not signed by the operator', async () => {
|
554
|
+
const signature = signAddress({
|
555
|
+
address: randomNonTxAccount,
|
556
|
+
signerWallet: impersonatorWallet,
|
557
|
+
});
|
558
|
+
await truffleAssert.fails(
|
559
|
+
metadataRegistryInstance.setEntrySigned(
|
560
|
+
nonExistentCredentialTypeHash,
|
561
|
+
bytes,
|
562
|
+
1,
|
563
|
+
7,
|
564
|
+
traceId,
|
565
|
+
caoDid,
|
566
|
+
signature,
|
567
|
+
{ from: randomTxAccount }
|
568
|
+
),
|
569
|
+
'Permissions: operator not pointing to a primary'
|
570
|
+
);
|
571
|
+
});
|
572
|
+
it('setEntry should fail to a non-existent list', async () => {
|
573
|
+
await truffleAssert.fails(
|
574
|
+
metadataRegistryInstance.setEntry(
|
575
|
+
regularIssuingCredentialTypeHash,
|
576
|
+
bytes,
|
577
|
+
3,
|
578
|
+
1,
|
579
|
+
traceId,
|
580
|
+
caoDid,
|
581
|
+
{ from: operatorAccount }
|
582
|
+
),
|
583
|
+
'List Id not aveliable'
|
584
|
+
);
|
585
|
+
});
|
586
|
+
it('setEntry should fail if not operator', async () => {
|
587
|
+
await truffleAssert.fails(
|
588
|
+
metadataRegistryInstance.setEntry(nonExistentCredentialTypeHash, bytes, 2, 1, traceId, caoDid),
|
589
|
+
'Permissions: operator not pointing to a primary'
|
590
|
+
);
|
591
|
+
});
|
592
|
+
it('setEntry should fail with an invalid index of an existing list', async () => {
|
593
|
+
await truffleAssert.fails(
|
594
|
+
metadataRegistryInstance.setEntry(
|
595
|
+
regularIssuingCredentialTypeHash,
|
596
|
+
bytes,
|
597
|
+
1,
|
598
|
+
10001,
|
599
|
+
traceId,
|
600
|
+
caoDid,
|
601
|
+
{ from: operatorAccount }
|
602
|
+
),
|
603
|
+
'Invalid index'
|
604
|
+
);
|
605
|
+
});
|
606
|
+
it('setEntry should fail with a previously used index of an existing list', async () => {
|
607
|
+
await truffleAssert.fails(
|
608
|
+
metadataRegistryInstance.setEntry(
|
609
|
+
regularIssuingCredentialTypeHash,
|
610
|
+
bytes,
|
611
|
+
1,
|
612
|
+
1,
|
613
|
+
traceId,
|
614
|
+
caoDid,
|
615
|
+
{ from: operatorAccount }
|
616
|
+
),
|
617
|
+
'Index already used'
|
618
|
+
);
|
619
|
+
});
|
620
|
+
});
|
621
|
+
|
622
|
+
describe('Set permission address', async () => {
|
623
|
+
let verificationCouponInstance;
|
624
|
+
let permissionsInstance;
|
625
|
+
|
626
|
+
before(async () => {
|
627
|
+
({verificationCouponInstance, permissionsInstance} = await setupContracts(primaryAccount, operatorAccount))
|
628
|
+
});
|
629
|
+
|
630
|
+
it('Should allow setup permission if there is no address', async () => {
|
631
|
+
const metadataRegistryInstance = await MetadataRegistry.new();
|
632
|
+
await metadataRegistryInstance.initialize(
|
633
|
+
verificationCouponInstance.address,
|
634
|
+
freeCredentialTypesBytes2
|
635
|
+
);
|
636
|
+
|
637
|
+
await metadataRegistryInstance.setPermissionsAddress(
|
638
|
+
permissionsInstance.address,
|
639
|
+
{ from: operatorAccount }
|
640
|
+
);
|
641
|
+
|
642
|
+
await truffleAssert.fails(
|
643
|
+
metadataRegistryInstance.setPermissionsAddress(permissionsInstance, {
|
644
|
+
from: operatorAccount,
|
645
|
+
}),
|
646
|
+
'Permissions: caller is not VNF'
|
647
|
+
);
|
648
|
+
});
|
649
|
+
|
650
|
+
it('Should not allow setup permission if caller is not VNF', async () => {
|
651
|
+
const metadataRegistryInstance = await MetadataRegistry.new();
|
652
|
+
await metadataRegistryInstance.initialize(
|
653
|
+
verificationCouponInstance.address,
|
654
|
+
freeCredentialTypesBytes2
|
655
|
+
);
|
656
|
+
|
657
|
+
await metadataRegistryInstance.setPermissionsAddress(
|
658
|
+
permissionsInstance.address,
|
659
|
+
{ from: primaryAccount }
|
660
|
+
);
|
661
|
+
|
662
|
+
await truffleAssert.fails(
|
663
|
+
metadataRegistryInstance.setPermissionsAddress(
|
664
|
+
permissionsInstance.address,
|
665
|
+
{ from: operatorAccount }
|
666
|
+
),
|
667
|
+
'Permissions: caller is not VNF'
|
668
|
+
);
|
669
|
+
});
|
670
|
+
|
671
|
+
it('Should allow setup permission if caller is VNF', async () => {
|
672
|
+
const metadataRegistryInstance = await MetadataRegistry.new();
|
673
|
+
await metadataRegistryInstance.initialize(
|
674
|
+
verificationCouponInstance.address,
|
675
|
+
freeCredentialTypesBytes2
|
676
|
+
);
|
677
|
+
|
678
|
+
await metadataRegistryInstance.setPermissionsAddress(
|
679
|
+
permissionsInstance.address,
|
680
|
+
{ from: primaryAccount }
|
681
|
+
);
|
682
|
+
expect(await metadataRegistryInstance.getPermissionsAddress()).equal(
|
683
|
+
permissionsInstance.address
|
684
|
+
);
|
685
|
+
|
686
|
+
await metadataRegistryInstance.setPermissionsAddress(accounts[3], {
|
687
|
+
from: deployerAccount,
|
688
|
+
});
|
689
|
+
expect(await metadataRegistryInstance.getPermissionsAddress()).equal(
|
690
|
+
accounts[3]
|
691
|
+
);
|
692
|
+
});
|
693
|
+
});
|
694
|
+
|
695
|
+
describe('Get entries with coupon', async () => {
|
696
|
+
let verificationCouponInstance;
|
697
|
+
let metadataRegistryInstance;
|
698
|
+
let permissionsInstance;
|
699
|
+
let couponId;
|
700
|
+
before(async () => {
|
701
|
+
({verificationCouponInstance, metadataRegistryInstance, permissionsInstance} = await setupContracts(primaryAccount, operatorAccount))
|
702
|
+
await permissionsInstance.addAddressScope(
|
703
|
+
primaryAccount,
|
704
|
+
'credential:issue'
|
705
|
+
);
|
706
|
+
await permissionsInstance.addAddressScope(
|
707
|
+
primaryAccount,
|
708
|
+
'credential:contactissue'
|
709
|
+
);
|
710
|
+
await verificationCouponInstance.mint(
|
711
|
+
primaryAccount,
|
712
|
+
expirationTime,
|
713
|
+
100,
|
714
|
+
traceId,
|
715
|
+
ownerDid,
|
716
|
+
{
|
717
|
+
from: deployerAccount,
|
718
|
+
}
|
719
|
+
);
|
720
|
+
couponId = (
|
721
|
+
await verificationCouponInstance.getTokenId(operatorAccount)
|
722
|
+
).toNumber();
|
723
|
+
await metadataRegistryInstance.newMetadataList(
|
724
|
+
1,
|
725
|
+
testListAlgType,
|
726
|
+
testListVersion,
|
727
|
+
bytes,
|
728
|
+
traceId,
|
729
|
+
caoDid,
|
730
|
+
{ from: operatorAccount }
|
731
|
+
);
|
732
|
+
await metadataRegistryInstance.setEntry(
|
733
|
+
regularIssuingCredentialTypeHash,
|
734
|
+
bytes,
|
735
|
+
1,
|
736
|
+
1,
|
737
|
+
traceId,
|
738
|
+
caoDid,
|
739
|
+
{ from: operatorAccount }
|
740
|
+
);
|
741
|
+
await metadataRegistryInstance.setEntry(
|
742
|
+
regularIssuingCredentialTypeHash,
|
743
|
+
bytes,
|
744
|
+
1,
|
745
|
+
2,
|
746
|
+
traceId,
|
747
|
+
caoDid,
|
748
|
+
{ from: operatorAccount }
|
749
|
+
);
|
750
|
+
await metadataRegistryInstance.setEntry(
|
751
|
+
freeCredentialTypesBytes2[0],
|
752
|
+
bytes,
|
753
|
+
1,
|
754
|
+
3,
|
755
|
+
traceId,
|
756
|
+
caoDid,
|
757
|
+
{ from: operatorAccount }
|
758
|
+
);
|
759
|
+
});
|
760
|
+
it('getPaidEntriesSigned should fail with empty signature', async () => {
|
761
|
+
await truffleAssert.fails(
|
762
|
+
metadataRegistryInstance.getPaidEntriesSigned(
|
763
|
+
[[primaryAccount, 1, 1]],
|
764
|
+
traceId,
|
765
|
+
caoDid,
|
766
|
+
burnerDid,
|
767
|
+
'',
|
768
|
+
{ from: randomTxAccount }
|
769
|
+
),
|
770
|
+
'invalid arrayify value'
|
771
|
+
);
|
772
|
+
});
|
773
|
+
it('getPaidEntriesSigned should fail with bad signature length', async () => {
|
774
|
+
await truffleAssert.fails(
|
775
|
+
metadataRegistryInstance.getPaidEntriesSigned(
|
776
|
+
[[primaryAccount, 1, 1]],
|
777
|
+
traceId,
|
778
|
+
caoDid,
|
779
|
+
burnerDid,
|
780
|
+
'0x90c082e8de5b2f45aab09bcf5d00e27a19d87a2de31536e6c',
|
781
|
+
{ from: randomTxAccount }
|
782
|
+
),
|
783
|
+
'invalid signature length'
|
784
|
+
);
|
785
|
+
});
|
786
|
+
it('getPaidEntriesSigned should fail with arbitrary signature', async () => {
|
787
|
+
await truffleAssert.fails(
|
788
|
+
metadataRegistryInstance.getPaidEntriesSigned(
|
789
|
+
[[primaryAccount, 1, 1]],
|
790
|
+
traceId,
|
791
|
+
caoDid,
|
792
|
+
burnerDid,
|
793
|
+
'0x90c082e8de5b2f45aab09bcf5d00e27a19d87a2de31536e6cda19da761c0f6845aea70eb9946fe47a4549b1ff205e098994a5bd2db772d9bfc407142e97081e11c',
|
794
|
+
{ from: randomTxAccount }
|
795
|
+
),
|
796
|
+
'Permissions: operator not pointing to a primary'
|
797
|
+
);
|
798
|
+
});
|
799
|
+
it('getPaidEntriesSigned should fail when wrong address payload is signed', async () => {
|
800
|
+
const signature = signAddress({
|
801
|
+
address: randomNonTxAccount,
|
802
|
+
signerWallet: operatorWallet,
|
803
|
+
});
|
804
|
+
await truffleAssert.fails(
|
805
|
+
metadataRegistryInstance.getPaidEntriesSigned(
|
806
|
+
[[primaryAccount, 1, 1]],
|
807
|
+
traceId,
|
808
|
+
caoDid,
|
809
|
+
burnerDid,
|
810
|
+
signature,
|
811
|
+
{ from: randomTxAccount }
|
812
|
+
),
|
813
|
+
'Permissions: operator not pointing to a primary'
|
814
|
+
);
|
815
|
+
});
|
816
|
+
it('getPaidEntriesSigned should fail when wrong payload type is signed', async () => {
|
817
|
+
const encodedArgs = AbiCoder.defaultAbiCoder().encode(['uint256'], [10]);
|
818
|
+
const hash = keccak256(encodedArgs);
|
819
|
+
const signature = operatorWallet.signingKey.sign(hash).serialized;
|
820
|
+
await truffleAssert.fails(
|
821
|
+
metadataRegistryInstance.getPaidEntriesSigned(
|
822
|
+
[[primaryAccount, 1, 1]],
|
823
|
+
traceId,
|
824
|
+
caoDid,
|
825
|
+
burnerDid,
|
826
|
+
signature,
|
827
|
+
{ from: randomTxAccount }
|
828
|
+
),
|
829
|
+
'Permissions: operator not pointing to a primary'
|
830
|
+
);
|
831
|
+
});
|
832
|
+
it('getPaidEntriesSigned should fail when not signed by the operator', async () => {
|
833
|
+
const signature = signAddress({
|
834
|
+
address: randomNonTxAccount,
|
835
|
+
signerWallet: impersonatorWallet,
|
836
|
+
});
|
837
|
+
await truffleAssert.fails(
|
838
|
+
metadataRegistryInstance.getPaidEntriesSigned(
|
839
|
+
[[primaryAccount, 1, 1]],
|
840
|
+
traceId,
|
841
|
+
caoDid,
|
842
|
+
burnerDid,
|
843
|
+
signature,
|
844
|
+
{ from: randomTxAccount }
|
845
|
+
),
|
846
|
+
'Permissions: operator not pointing to a primary'
|
847
|
+
);
|
848
|
+
});
|
849
|
+
it('getPaidEntriesSigned should get one entry', async () => {
|
850
|
+
assert.equal(
|
851
|
+
await verificationCouponInstance.isExpired(couponId),
|
852
|
+
false,
|
853
|
+
'Coupon should not be expired'
|
854
|
+
);
|
855
|
+
const signature = signAddress({
|
856
|
+
address: randomTxAccount,
|
857
|
+
signerWallet: operatorWallet,
|
858
|
+
});
|
859
|
+
const result = await metadataRegistryInstance.getPaidEntriesSigned(
|
860
|
+
[[primaryAccount, 1, 1]],
|
861
|
+
traceId,
|
862
|
+
caoDid,
|
863
|
+
burnerDid,
|
864
|
+
signature,
|
865
|
+
{ from: randomTxAccount }
|
866
|
+
);
|
867
|
+
|
868
|
+
assert.deepEqual(JSON.parse(JSON.stringify(result.logs[0].args[0])), [
|
869
|
+
sampleEntry,
|
870
|
+
]);
|
871
|
+
const balance = await verificationCouponInstance.balanceOf(
|
872
|
+
primaryAccount,
|
873
|
+
couponId
|
874
|
+
);
|
875
|
+
assert.equal(balance.toNumber(), 99);
|
876
|
+
});
|
877
|
+
|
878
|
+
it('Get one entry from to the existing list and check that the coupon was burned', async () => {
|
879
|
+
assert.equal(
|
880
|
+
await verificationCouponInstance.isExpired(couponId),
|
881
|
+
false,
|
882
|
+
'Coupon should not be expired'
|
883
|
+
);
|
884
|
+
const result = await metadataRegistryInstance.getPaidEntries(
|
885
|
+
[[primaryAccount, 1, 1]],
|
886
|
+
traceId,
|
887
|
+
caoDid,
|
888
|
+
burnerDid,
|
889
|
+
{ from: operatorAccount }
|
890
|
+
);
|
891
|
+
assert.deepEqual(JSON.parse(JSON.stringify(result.logs[0].args[0])), [
|
892
|
+
sampleEntry,
|
893
|
+
]);
|
894
|
+
const balance = await verificationCouponInstance.balanceOf(
|
895
|
+
primaryAccount,
|
896
|
+
couponId
|
897
|
+
);
|
898
|
+
assert.equal(balance.toNumber(), 98);
|
899
|
+
});
|
900
|
+
it('Get multiple entries from to the existing list and check that the coupon was burned', async () => {
|
901
|
+
assert.equal(
|
902
|
+
await verificationCouponInstance.isExpired(0),
|
903
|
+
false,
|
904
|
+
'Coupon should not be expired'
|
905
|
+
);
|
906
|
+
|
907
|
+
const result = await metadataRegistryInstance.getPaidEntries(
|
908
|
+
[
|
909
|
+
[primaryAccount, 1, 1],
|
910
|
+
[primaryAccount, 1, 2],
|
911
|
+
[primaryAccount, 1, 3],
|
912
|
+
],
|
913
|
+
traceId,
|
914
|
+
caoDid,
|
915
|
+
burnerDid,
|
916
|
+
{ from: operatorAccount }
|
917
|
+
);
|
918
|
+
assert.deepEqual(JSON.parse(JSON.stringify(result.logs[0].args[0])), [
|
919
|
+
...[0, 1].map(() => sampleEntry),
|
920
|
+
[testListVersion, freeCredentialTypesBytes2[0], testListAlgType, bytes, bytes],
|
921
|
+
]);
|
922
|
+
const balance = await verificationCouponInstance.balanceOf(
|
923
|
+
primaryAccount,
|
924
|
+
couponId
|
925
|
+
);
|
926
|
+
assert.equal(balance.toNumber(), 97);
|
927
|
+
});
|
928
|
+
it('Get duplicated entries if two duplicated Credential Identifiers were requested', async () => {
|
929
|
+
assert.equal(
|
930
|
+
await verificationCouponInstance.isExpired(0),
|
931
|
+
false,
|
932
|
+
'Coupon should not be expired'
|
933
|
+
);
|
934
|
+
|
935
|
+
const result = await metadataRegistryInstance.getPaidEntries(
|
936
|
+
[
|
937
|
+
[primaryAccount, 1, 1],
|
938
|
+
[primaryAccount, 1, 1],
|
939
|
+
],
|
940
|
+
traceId,
|
941
|
+
caoDid,
|
942
|
+
burnerDid,
|
943
|
+
{ from: operatorAccount }
|
944
|
+
);
|
945
|
+
assert.deepEqual(
|
946
|
+
JSON.parse(JSON.stringify(result.logs[0].args[0])),
|
947
|
+
[1, 2].map(() => sampleEntry)
|
948
|
+
);
|
949
|
+
const balance = await verificationCouponInstance.balanceOf(
|
950
|
+
primaryAccount,
|
951
|
+
couponId
|
952
|
+
);
|
953
|
+
assert.equal(balance.toNumber(), 96);
|
954
|
+
});
|
955
|
+
it('Get an entry throws error on condition: accountId is not exist', async () => {
|
956
|
+
assert.equal(
|
957
|
+
await verificationCouponInstance.isExpired(0),
|
958
|
+
false,
|
959
|
+
'Coupon should not be expired'
|
960
|
+
);
|
961
|
+
await truffleAssert.fails(
|
962
|
+
metadataRegistryInstance.getPaidEntries(
|
963
|
+
[
|
964
|
+
[primaryAccount, 1, 4],
|
965
|
+
[primaryAccount, 1, 2],
|
966
|
+
[primaryAccount, 1, 3],
|
967
|
+
],
|
968
|
+
traceId,
|
969
|
+
caoDid,
|
970
|
+
burnerDid,
|
971
|
+
{ from: operatorAccount }
|
972
|
+
),
|
973
|
+
'Index not used'
|
974
|
+
);
|
975
|
+
const balance = await verificationCouponInstance.balanceOf(
|
976
|
+
primaryAccount,
|
977
|
+
couponId
|
978
|
+
);
|
979
|
+
assert.equal(balance.toNumber(), 96);
|
980
|
+
});
|
981
|
+
it('Get an entry throws error on condition: listId is not exist', async () => {
|
982
|
+
assert.equal(
|
983
|
+
await verificationCouponInstance.isExpired(0),
|
984
|
+
false,
|
985
|
+
'Coupon should not be expired'
|
986
|
+
);
|
987
|
+
await truffleAssert.fails(
|
988
|
+
metadataRegistryInstance.getPaidEntries(
|
989
|
+
[
|
990
|
+
[primaryAccount, 2, 1],
|
991
|
+
[primaryAccount, 1, 2],
|
992
|
+
[primaryAccount, 1, 3],
|
993
|
+
],
|
994
|
+
traceId,
|
995
|
+
caoDid,
|
996
|
+
burnerDid,
|
997
|
+
{ from: operatorAccount }
|
998
|
+
),
|
999
|
+
'List id not used'
|
1000
|
+
);
|
1001
|
+
const balance = await verificationCouponInstance.balanceOf(
|
1002
|
+
primaryAccount,
|
1003
|
+
couponId
|
1004
|
+
);
|
1005
|
+
assert.equal(balance.toNumber(), 96);
|
1006
|
+
});
|
1007
|
+
it('Get an entry throws error on condition: index is not exist', async () => {
|
1008
|
+
assert.equal(
|
1009
|
+
await verificationCouponInstance.isExpired(0),
|
1010
|
+
false,
|
1011
|
+
'Coupon should not be expired'
|
1012
|
+
);
|
1013
|
+
|
1014
|
+
await truffleAssert.fails(
|
1015
|
+
metadataRegistryInstance.getPaidEntries(
|
1016
|
+
[
|
1017
|
+
[accounts[0], 1, 1],
|
1018
|
+
[primaryAccount, 1, 2],
|
1019
|
+
[primaryAccount, 1, 3],
|
1020
|
+
],
|
1021
|
+
traceId,
|
1022
|
+
caoDid,
|
1023
|
+
burnerDid,
|
1024
|
+
{ from: operatorAccount }
|
1025
|
+
),
|
1026
|
+
'List id not used'
|
1027
|
+
);
|
1028
|
+
const balance = await verificationCouponInstance.balanceOf(
|
1029
|
+
primaryAccount,
|
1030
|
+
couponId
|
1031
|
+
);
|
1032
|
+
assert.equal(balance.toNumber(), 96);
|
1033
|
+
});
|
1034
|
+
});
|
1035
|
+
|
1036
|
+
describe('Get entries with coupon fails', async () => {
|
1037
|
+
let verificationCouponInstance;
|
1038
|
+
let metadataRegistryInstance;
|
1039
|
+
|
1040
|
+
before(async () => {
|
1041
|
+
const contracts = await setupContracts(primaryAccount, operatorAccount);
|
1042
|
+
({verificationCouponInstance, metadataRegistryInstance} = contracts);
|
1043
|
+
|
1044
|
+
const {permissionsInstance} = contracts;
|
1045
|
+
await permissionsInstance.addAddressScope(
|
1046
|
+
primaryAccount,
|
1047
|
+
'credential:issue'
|
1048
|
+
);
|
1049
|
+
await permissionsInstance.addAddressScope(
|
1050
|
+
primaryAccount,
|
1051
|
+
'credential:contactissue'
|
1052
|
+
);
|
1053
|
+
|
1054
|
+
await metadataRegistryInstance.newMetadataList(
|
1055
|
+
1,
|
1056
|
+
testListAlgType,
|
1057
|
+
testListVersion,
|
1058
|
+
bytes,
|
1059
|
+
traceId,
|
1060
|
+
caoDid,
|
1061
|
+
{ from: operatorAccount }
|
1062
|
+
);
|
1063
|
+
await metadataRegistryInstance.setEntry(
|
1064
|
+
regularIssuingCredentialTypeHash,
|
1065
|
+
bytes,
|
1066
|
+
1,
|
1067
|
+
1,
|
1068
|
+
traceId,
|
1069
|
+
caoDid,
|
1070
|
+
{ from: operatorAccount }
|
1071
|
+
);
|
1072
|
+
await metadataRegistryInstance.setEntry(
|
1073
|
+
regularIssuingCredentialTypeHash,
|
1074
|
+
bytes,
|
1075
|
+
1,
|
1076
|
+
2,
|
1077
|
+
traceId,
|
1078
|
+
caoDid,
|
1079
|
+
{ from: operatorAccount }
|
1080
|
+
);
|
1081
|
+
await metadataRegistryInstance.setEntry(
|
1082
|
+
freeCredentialTypesBytes2[0],
|
1083
|
+
bytes,
|
1084
|
+
1,
|
1085
|
+
3,
|
1086
|
+
traceId,
|
1087
|
+
caoDid,
|
1088
|
+
{ from: operatorAccount }
|
1089
|
+
);
|
1090
|
+
});
|
1091
|
+
|
1092
|
+
it('Throws an error when the coupon was not exist', async () => {
|
1093
|
+
await truffleAssert.fails(
|
1094
|
+
verificationCouponInstance.getTokenId(operatorAccount),
|
1095
|
+
'No available tokens'
|
1096
|
+
);
|
1097
|
+
await truffleAssert.fails(
|
1098
|
+
metadataRegistryInstance.getPaidEntries(
|
1099
|
+
[[primaryAccount, 1, 1]],
|
1100
|
+
traceId,
|
1101
|
+
caoDid,
|
1102
|
+
burnerDid,
|
1103
|
+
{ from: operatorAccount }
|
1104
|
+
),
|
1105
|
+
'No available tokens'
|
1106
|
+
);
|
1107
|
+
});
|
1108
|
+
it('Throws an error when the coupon was burned', async () => {
|
1109
|
+
await verificationCouponInstance.mint(
|
1110
|
+
primaryAccount,
|
1111
|
+
expirationTime,
|
1112
|
+
2,
|
1113
|
+
traceId,
|
1114
|
+
ownerDid,
|
1115
|
+
{
|
1116
|
+
from: accounts[0],
|
1117
|
+
}
|
1118
|
+
);
|
1119
|
+
await metadataRegistryInstance.getPaidEntries(
|
1120
|
+
[[primaryAccount, 1, 1]],
|
1121
|
+
traceId,
|
1122
|
+
caoDid,
|
1123
|
+
burnerDid,
|
1124
|
+
{ from: operatorAccount }
|
1125
|
+
);
|
1126
|
+
await metadataRegistryInstance.getPaidEntries(
|
1127
|
+
[[primaryAccount, 1, 1]],
|
1128
|
+
traceId,
|
1129
|
+
caoDid,
|
1130
|
+
burnerDid,
|
1131
|
+
{ from: operatorAccount }
|
1132
|
+
);
|
1133
|
+
|
1134
|
+
await truffleAssert.fails(
|
1135
|
+
verificationCouponInstance.getTokenId(operatorAccount),
|
1136
|
+
'No available tokens'
|
1137
|
+
);
|
1138
|
+
await truffleAssert.fails(
|
1139
|
+
metadataRegistryInstance.getPaidEntries(
|
1140
|
+
[[primaryAccount, 1, 1]],
|
1141
|
+
traceId,
|
1142
|
+
caoDid,
|
1143
|
+
burnerDid,
|
1144
|
+
{ from: operatorAccount }
|
1145
|
+
),
|
1146
|
+
'No available tokens'
|
1147
|
+
);
|
1148
|
+
});
|
1149
|
+
|
1150
|
+
it('Should burn all expired coupons and use a valid coupon after that', async () => {
|
1151
|
+
await verificationCouponInstance.mint(
|
1152
|
+
accounts[1],
|
1153
|
+
expiredTime,
|
1154
|
+
100,
|
1155
|
+
traceId,
|
1156
|
+
ownerDid,
|
1157
|
+
{
|
1158
|
+
from: accounts[0],
|
1159
|
+
}
|
1160
|
+
);
|
1161
|
+
await verificationCouponInstance.mint(
|
1162
|
+
accounts[1],
|
1163
|
+
expiredTime,
|
1164
|
+
100,
|
1165
|
+
traceId,
|
1166
|
+
ownerDid,
|
1167
|
+
{
|
1168
|
+
from: accounts[0],
|
1169
|
+
}
|
1170
|
+
);
|
1171
|
+
await verificationCouponInstance.mint(
|
1172
|
+
accounts[1],
|
1173
|
+
expirationTime,
|
1174
|
+
100,
|
1175
|
+
traceId,
|
1176
|
+
ownerDid,
|
1177
|
+
{
|
1178
|
+
from: accounts[0],
|
1179
|
+
}
|
1180
|
+
);
|
1181
|
+
const couponId = (
|
1182
|
+
await verificationCouponInstance.getTokenId(operatorAccount)
|
1183
|
+
).toNumber();
|
1184
|
+
await assert.equal(
|
1185
|
+
await verificationCouponInstance.isExpired(couponId),
|
1186
|
+
false
|
1187
|
+
);
|
1188
|
+
await metadataRegistryInstance.getPaidEntries(
|
1189
|
+
[
|
1190
|
+
[primaryAccount, 1, 1],
|
1191
|
+
[primaryAccount, 1, 1],
|
1192
|
+
],
|
1193
|
+
traceId,
|
1194
|
+
caoDid,
|
1195
|
+
burnerDid,
|
1196
|
+
{
|
1197
|
+
from: operatorAccount,
|
1198
|
+
}
|
1199
|
+
);
|
1200
|
+
});
|
1201
|
+
});
|
1202
|
+
|
1203
|
+
describe('Get entries with free types', async () => {
|
1204
|
+
let verificationCouponInstance;
|
1205
|
+
let metadataRegistryInstance;
|
1206
|
+
|
1207
|
+
before(async () => {
|
1208
|
+
const contracts = await setupContracts(primaryAccount, operatorAccount);
|
1209
|
+
({verificationCouponInstance, metadataRegistryInstance} = contracts);
|
1210
|
+
|
1211
|
+
const {permissionsInstance} = contracts;
|
1212
|
+
await permissionsInstance.addAddressScope(
|
1213
|
+
primaryAccount,
|
1214
|
+
'credential:issue'
|
1215
|
+
);
|
1216
|
+
await permissionsInstance.addAddressScope(
|
1217
|
+
primaryAccount,
|
1218
|
+
'credential:contactissue'
|
1219
|
+
);
|
1220
|
+
|
1221
|
+
await verificationCouponInstance.mint(
|
1222
|
+
accounts[0],
|
1223
|
+
expirationTime,
|
1224
|
+
100,
|
1225
|
+
traceId,
|
1226
|
+
ownerDid,
|
1227
|
+
{
|
1228
|
+
from: accounts[0],
|
1229
|
+
}
|
1230
|
+
);
|
1231
|
+
await metadataRegistryInstance.newMetadataList(
|
1232
|
+
1,
|
1233
|
+
testListAlgType,
|
1234
|
+
testListVersion,
|
1235
|
+
bytes,
|
1236
|
+
traceId,
|
1237
|
+
caoDid,
|
1238
|
+
{ from: operatorAccount }
|
1239
|
+
);
|
1240
|
+
await metadataRegistryInstance.setEntry(
|
1241
|
+
freeCredentialTypesBytes2[0],
|
1242
|
+
bytes,
|
1243
|
+
1,
|
1244
|
+
1,
|
1245
|
+
traceId,
|
1246
|
+
caoDid,
|
1247
|
+
{ from: operatorAccount }
|
1248
|
+
);
|
1249
|
+
await metadataRegistryInstance.setEntry(
|
1250
|
+
freeCredentialTypesBytes2[1],
|
1251
|
+
bytes,
|
1252
|
+
1,
|
1253
|
+
2,
|
1254
|
+
traceId,
|
1255
|
+
caoDid,
|
1256
|
+
{ from: operatorAccount }
|
1257
|
+
);
|
1258
|
+
await metadataRegistryInstance.setEntry(
|
1259
|
+
freeCredentialTypesBytes2[2],
|
1260
|
+
bytes,
|
1261
|
+
1,
|
1262
|
+
3,
|
1263
|
+
traceId,
|
1264
|
+
caoDid,
|
1265
|
+
{ from: operatorAccount }
|
1266
|
+
);
|
1267
|
+
await metadataRegistryInstance.setEntry(
|
1268
|
+
regularIssuingCredentialTypeHash,
|
1269
|
+
bytes,
|
1270
|
+
1,
|
1271
|
+
4,
|
1272
|
+
traceId,
|
1273
|
+
caoDid,
|
1274
|
+
{ from: operatorAccount }
|
1275
|
+
);
|
1276
|
+
});
|
1277
|
+
it('Get one entry from the existing list ', async () => {
|
1278
|
+
const entries = await metadataRegistryInstance.getFreeEntries.call(
|
1279
|
+
[[primaryAccount, 1, 1]],
|
1280
|
+
{ from: operatorAccount }
|
1281
|
+
);
|
1282
|
+
|
1283
|
+
assert.deepEqual(entries, [
|
1284
|
+
[testListVersion, freeCredentialTypesBytes2[0], testListAlgType, bytes, bytes],
|
1285
|
+
]);
|
1286
|
+
});
|
1287
|
+
it('Get multiple entries from to the existing list', async () => {
|
1288
|
+
const entries = await metadataRegistryInstance.getFreeEntries.call(
|
1289
|
+
[
|
1290
|
+
[primaryAccount, 1, 1],
|
1291
|
+
[primaryAccount, 1, 2],
|
1292
|
+
[primaryAccount, 1, 3],
|
1293
|
+
],
|
1294
|
+
{ from: operatorAccount }
|
1295
|
+
);
|
1296
|
+
assert.deepEqual(
|
1297
|
+
entries,
|
1298
|
+
[0, 1, 2].map((i) => [
|
1299
|
+
testListVersion,
|
1300
|
+
freeCredentialTypesBytes2[i],
|
1301
|
+
testListAlgType,
|
1302
|
+
bytes,
|
1303
|
+
bytes,
|
1304
|
+
])
|
1305
|
+
);
|
1306
|
+
});
|
1307
|
+
|
1308
|
+
it('Throws an error when the creadential type is not free', async () => {
|
1309
|
+
await truffleAssert.fails(
|
1310
|
+
metadataRegistryInstance.getFreeEntries.call([[primaryAccount, 1, 4]], {
|
1311
|
+
from: operatorAccount,
|
1312
|
+
}),
|
1313
|
+
'Only free creadential types is allowed without coupon'
|
1314
|
+
);
|
1315
|
+
});
|
1316
|
+
|
1317
|
+
it('Throws an error for credential types at the index', async () => {
|
1318
|
+
await truffleAssert.fails(
|
1319
|
+
metadataRegistryInstance.getPaidEntries(
|
1320
|
+
[[primaryAccount, 1, 1]],
|
1321
|
+
traceId,
|
1322
|
+
caoDid,
|
1323
|
+
burnerDid
|
1324
|
+
),
|
1325
|
+
'No paid creadential types'
|
1326
|
+
);
|
1327
|
+
});
|
1328
|
+
|
1329
|
+
it('Throws an error a caller is not an operator', async () => {
|
1330
|
+
await truffleAssert.fails(
|
1331
|
+
metadataRegistryInstance.getPaidEntries(
|
1332
|
+
[[primaryAccount, 1, 4]],
|
1333
|
+
traceId,
|
1334
|
+
caoDid,
|
1335
|
+
burnerDid
|
1336
|
+
),
|
1337
|
+
'Permissions: operator not pointing to a primary'
|
1338
|
+
);
|
1339
|
+
});
|
1340
|
+
|
1341
|
+
it('Throws an error when the creadential type is free but getPaidEntries is used', async () => {
|
1342
|
+
await truffleAssert.fails(
|
1343
|
+
metadataRegistryInstance.getPaidEntries(
|
1344
|
+
[[primaryAccount, 1, 1]],
|
1345
|
+
traceId,
|
1346
|
+
caoDid,
|
1347
|
+
burnerDid,
|
1348
|
+
{ from: operatorAccount }
|
1349
|
+
),
|
1350
|
+
'No paid creadential types'
|
1351
|
+
);
|
1352
|
+
});
|
1353
|
+
});
|
1354
|
+
|
1355
|
+
describe('Update the free types', async () => {
|
1356
|
+
let metadataRegistryInstance;
|
1357
|
+
const newFreeCredentialTypesList = ['NewType1', 'NewType2', 'NewType3'];
|
1358
|
+
const newFreeCredentialTypesBytes2 = map(
|
1359
|
+
get2BytesHash,
|
1360
|
+
newFreeCredentialTypesList
|
1361
|
+
);
|
1362
|
+
beforeEach(async () => {
|
1363
|
+
({metadataRegistryInstance} = await setupContracts(primaryAccount, operatorAccount));
|
1364
|
+
});
|
1365
|
+
it('Add new free types', async () => {
|
1366
|
+
const isFreeBefore = await metadataRegistryInstance.isFreeCredentialType(
|
1367
|
+
newFreeCredentialTypesBytes2[0]
|
1368
|
+
);
|
1369
|
+
await metadataRegistryInstance.addFreeTypes(newFreeCredentialTypesBytes2);
|
1370
|
+
const isFreeAfter = await metadataRegistryInstance.isFreeCredentialType(
|
1371
|
+
newFreeCredentialTypesBytes2[0]
|
1372
|
+
);
|
1373
|
+
|
1374
|
+
assert.equal(isFreeBefore, false);
|
1375
|
+
assert.equal(isFreeAfter, true);
|
1376
|
+
});
|
1377
|
+
it('Remove new free types', async () => {
|
1378
|
+
await metadataRegistryInstance.addFreeTypes(newFreeCredentialTypesBytes2);
|
1379
|
+
const isFreeBefore = await metadataRegistryInstance.isFreeCredentialType(
|
1380
|
+
newFreeCredentialTypesBytes2[0]
|
1381
|
+
);
|
1382
|
+
await metadataRegistryInstance.removeFreeTypes(
|
1383
|
+
newFreeCredentialTypesBytes2
|
1384
|
+
);
|
1385
|
+
const isFreeAfter = await metadataRegistryInstance.isFreeCredentialType(
|
1386
|
+
newFreeCredentialTypesBytes2[0]
|
1387
|
+
);
|
1388
|
+
|
1389
|
+
assert.equal(isFreeBefore, true);
|
1390
|
+
assert.equal(isFreeAfter, false);
|
1391
|
+
});
|
1392
|
+
it('Fail if it is not VNF to add or remove free types', async () => {
|
1393
|
+
await truffleAssert.fails(
|
1394
|
+
metadataRegistryInstance.addFreeTypes(newFreeCredentialTypesBytes2, {
|
1395
|
+
from: accounts[3],
|
1396
|
+
}),
|
1397
|
+
'The caller is not VNF'
|
1398
|
+
);
|
1399
|
+
await truffleAssert.fails(
|
1400
|
+
metadataRegistryInstance.removeFreeTypes(newFreeCredentialTypesBytes2, {
|
1401
|
+
from: accounts[3],
|
1402
|
+
}),
|
1403
|
+
'The caller is not VNF'
|
1404
|
+
);
|
1405
|
+
});
|
1406
|
+
|
1407
|
+
it('Can remove unexisting free types', async () => {
|
1408
|
+
await metadataRegistryInstance.removeFreeTypes(
|
1409
|
+
newFreeCredentialTypesBytes2
|
1410
|
+
);
|
1411
|
+
await metadataRegistryInstance.removeFreeTypes(
|
1412
|
+
newFreeCredentialTypesBytes2
|
1413
|
+
);
|
1414
|
+
});
|
1415
|
+
});
|
1416
|
+
|
1417
|
+
describe('Change verification coupon address', async () => {
|
1418
|
+
let verificationCouponInstance;
|
1419
|
+
let metadataRegistryInstance;
|
1420
|
+
before(async () => {
|
1421
|
+
const contracts = await setupContracts(primaryAccount, operatorAccount);
|
1422
|
+
({verificationCouponInstance, metadataRegistryInstance} = contracts);
|
1423
|
+
|
1424
|
+
const {permissionsInstance} = contracts;
|
1425
|
+
await permissionsInstance.addAddressScope(
|
1426
|
+
primaryAccount,
|
1427
|
+
'credential:issue'
|
1428
|
+
);
|
1429
|
+
await permissionsInstance.addAddressScope(
|
1430
|
+
primaryAccount,
|
1431
|
+
'credential:contactissue'
|
1432
|
+
);
|
1433
|
+
|
1434
|
+
await verificationCouponInstance.mint(
|
1435
|
+
accounts[0],
|
1436
|
+
expirationTime,
|
1437
|
+
100,
|
1438
|
+
traceId,
|
1439
|
+
ownerDid,
|
1440
|
+
{
|
1441
|
+
from: accounts[0],
|
1442
|
+
}
|
1443
|
+
);
|
1444
|
+
await metadataRegistryInstance.newMetadataList(
|
1445
|
+
1,
|
1446
|
+
testListAlgType,
|
1447
|
+
testListVersion,
|
1448
|
+
bytes,
|
1449
|
+
traceId,
|
1450
|
+
caoDid,
|
1451
|
+
{ from: operatorAccount }
|
1452
|
+
);
|
1453
|
+
await metadataRegistryInstance.setEntry(
|
1454
|
+
freeCredentialTypesBytes2[0],
|
1455
|
+
bytes,
|
1456
|
+
1,
|
1457
|
+
1,
|
1458
|
+
traceId,
|
1459
|
+
caoDid,
|
1460
|
+
{ from: operatorAccount }
|
1461
|
+
);
|
1462
|
+
await metadataRegistryInstance.setEntry(
|
1463
|
+
freeCredentialTypesBytes2[1],
|
1464
|
+
bytes,
|
1465
|
+
1,
|
1466
|
+
2,
|
1467
|
+
traceId,
|
1468
|
+
caoDid,
|
1469
|
+
{ from: operatorAccount }
|
1470
|
+
);
|
1471
|
+
await metadataRegistryInstance.setEntry(
|
1472
|
+
freeCredentialTypesBytes2[2],
|
1473
|
+
bytes,
|
1474
|
+
1,
|
1475
|
+
3,
|
1476
|
+
traceId,
|
1477
|
+
caoDid,
|
1478
|
+
{ from: operatorAccount }
|
1479
|
+
);
|
1480
|
+
await metadataRegistryInstance.setEntry(
|
1481
|
+
regularIssuingCredentialTypeHash,
|
1482
|
+
bytes,
|
1483
|
+
1,
|
1484
|
+
4,
|
1485
|
+
traceId,
|
1486
|
+
caoDid,
|
1487
|
+
{ from: operatorAccount }
|
1488
|
+
);
|
1489
|
+
});
|
1490
|
+
it('Set new verification coupon address', async () => {
|
1491
|
+
const verificationCouponInstance2 = await VerificationCoupon.new();
|
1492
|
+
await verificationCouponInstance2.initialize(
|
1493
|
+
'Velocity Verification Coupon',
|
1494
|
+
'https://www.velocitynetwork.foundation/'
|
1495
|
+
);
|
1496
|
+
await metadataRegistryInstance.setCouponAddress(
|
1497
|
+
verificationCouponInstance2.address
|
1498
|
+
);
|
1499
|
+
});
|
1500
|
+
it('Fails if account is not admin', async () => {
|
1501
|
+
await truffleAssert.fails(
|
1502
|
+
metadataRegistryInstance.setCouponAddress(
|
1503
|
+
verificationCouponInstance.address,
|
1504
|
+
{
|
1505
|
+
from: accounts[3],
|
1506
|
+
}
|
1507
|
+
),
|
1508
|
+
'The caller is not VNF'
|
1509
|
+
);
|
1510
|
+
});
|
1511
|
+
});
|
1512
|
+
});
|