forked from massalabs/massa-standards
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdns.ts
446 lines (370 loc) · 13.7 KB
/
dns.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
/**
* DNS implementation by MassaLabs.
*
* This first very simple version of a DNS is designed to handle only the web-on chain.
*
* The idea is to create a link between :
* - a hostname called name or web site name
* - an address where the smart contract of the web site deployment and the chunks of the web site are located.
* This field is called resolver (needs to be renamed for clarity, feel free to update the code if you work on this).
* - a DNS record owner, which is the owner of the website name and its address.
*
* The datastore is used to persist all this information in the following way:
* - The DNS owner address
* the key is 'OWNER'
* the value is the address
* - the list of all the web-site names
* The key (web-site name) and the values (address and owner) are encoded using args.
* - the list of all owners
* The key ("owned" concatenated with the owner's address) and the value (web-site names in CSV format)
* are encoded using args.
* The key ("blackList") and the value (web-site names in CSV format)
* are encoded using args.
*/
import {
Storage,
generateEvent,
Address,
Context,
isDeployingContract,
} from '@massalabs/massa-as-sdk';
import { Args, byteToBool } from '@massalabs/as-types';
import { onlyOwner, setOwner, triggerError } from '../utils';
export const blackListKey = new Args().add('blackList').serialize();
export function dns1_isDnsValid(input: string): bool {
for (let i = 0; i < input.length; i++) {
const charCode = input.charCodeAt(i);
if (
!(
(
(charCode >= 97 && charCode <= 122) || // lowercase (a-z)
(charCode >= 48 && charCode <= 57) || // numeric (0-9)
charCode === 45 || // dash (-)
charCode === 95
) // underscore (_)
)
) {
return false; // character is not alphanumeric or dash or underscore
}
}
return true;
}
export function dns1_isDescriptionValid(description: string): boolean {
// Check if the length exceeds the maximum limit of 280 characters
if (description.length > 280) {
return false;
}
return true;
}
/**
* This function is meant to be called only one time: when the contract is deployed.
*
* @param binaryArgs - Arguments serialized with Args: dns owner address
*/
export function constructor(_: StaticArray<u8>): StaticArray<u8> {
assert(isDeployingContract());
setOwner(new Args().add(Context.caller().toString()).serialize());
return [];
}
function dns1_ownerKey(address: Address): StaticArray<u8> {
return new Args().add('owned' + address.toString()).serialize();
}
/**
* Adds a new record to the DNS.
* Website name and address are given as argument, owner's address is deduce from caller.
*
* This function will trigger an error event if the name is already reserved.
*
* @param binaryArgs - Website name and its address in a binary format using Args.
*
* @example
* setResolver(new Args().add("my-website").add(myWebSiteAddress)
*/
export function dns1_setResolver(binaryArgs: StaticArray<u8>): void {
const args = new Args(binaryArgs);
const websiteName = args
.nextString()
.expect('website name is missing or invalid');
if (!dns1_isDnsValid(websiteName)) {
triggerError('INVALID_DNS_ENTRY');
}
const websiteAddress = args
.nextString()
.expect('website address is missing or invalid');
const websiteNameBytes = new Args().add(websiteName);
const description = args
.nextString()
.expect('website description is missing or invalid');
if (!dns1_isDescriptionValid(description)) {
triggerError('INVALID_DESCRIPTION_ENTRY');
}
if (Storage.has(websiteNameBytes)) {
triggerError('Try another website name, this one is already taken.');
}
// Check if the website name is blacklisted
const isBlacklistedValue = byteToBool(
dns1_isBlacklisted(new Args().add(websiteName).serialize()),
);
if (isBlacklistedValue) {
triggerError('Try another website name, ' + websiteName + ' is reserved.');
}
Storage.set(
websiteNameBytes,
new Args()
.add(websiteAddress)
.add(Context.caller().toString())
.add(description),
);
dns1_addToOwnerList(Context.caller(), websiteName);
// this event should not be changed without changing the event listener in Thyra
generateEvent(
`Website name ${websiteName} added to DNS at address ${websiteAddress}`,
);
}
/**
* Returns the website address of the given website name.
* If the name is not found, an empty array is returned.
*
* @param binaryArgs - Website name in a binary format using Args.
*
* @returns the website and the owner's address or an empty array.
*
* @example
* resolver(new Args().add("my-website").serialize())
*/
export function dns1_resolver(binaryArgs: StaticArray<u8>): StaticArray<u8> {
if (Storage.has(binaryArgs)) {
return Storage.get(binaryArgs);
}
return [];
}
/**
* Returns the owner's address of the given website name.
* If the website doesn't exist an empty array is returned.
*
* @param binaryArgs - Website name in a binary format using Args.
*
* @returns the owner's address or an empty array.
*
* @example
* owner(new Args().add("my-website").serialize())
*/
export function dns1_owner(binaryArgs: StaticArray<u8>): StaticArray<u8> {
if (Storage.has(binaryArgs)) {
const entry = new Args(Storage.get(binaryArgs));
// skip the website address
entry.nextString().unwrap();
const ownerAddress = entry.nextString().unwrap();
return new Args().add(ownerAddress).serialize();
}
return [];
}
/**
* Checks if the given address is the owner of the website.
* @param binaryArgs - Website name and the address of potential owner in a binary format using Args.
* @returns A serialized boolean indicating if the potential owner is the actual owner of the website.
*/
export function dns1_isOwnerOfWebsite(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const args = new Args(binaryArgs);
const websiteName = args
.nextString()
.expect('website name is missing or invalid');
const potentialOwner = args
.nextString()
.expect('potential owner address is missing or invalid');
const websiteArgs = new Args().add(websiteName).serialize();
const ownerAddressArray = dns1_owner(websiteArgs);
// Convert the owner address array to a string for comparison
const ownerAddress = new Args(ownerAddressArray).nextString().unwrap();
return new Args().add(ownerAddress === potentialOwner).serialize();
}
/**
* Ensures that the caller is the owner of the specific website.
*
* @param websiteName - The name of the website to check ownership.
*/
function dns1_onlyWebsiteOwner(websiteName: string): void {
// Get the address of the caller
const callerAddress = Context.caller().toString();
const argsWebsiteOwner = new Args()
.add(websiteName)
.add(callerAddress)
.serialize();
const isWebsiteOwner = byteToBool(dns1_isOwnerOfWebsite(argsWebsiteOwner));
// Ensure that the caller is the owner of the website
assert(isWebsiteOwner, 'Caller is not the the owner of the website');
}
/**
* Get the owner's list of websites as a string.
*
* @param binaryArgs - The address of the owner in a binary format using Args.
* @returns The owner's list of websites or an empty serialized string.
*/
export function dns1_getOwnerWebsiteList(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const args = new Args(binaryArgs);
const owner = args
.nextString()
.expect('address of the owner is missing or invalid');
const ownerListKey = dns1_ownerKey(new Address(owner));
if (!Storage.has(ownerListKey)) {
return new Args().add('').serialize(); // Return an empty string if the owner's list is not found.
}
return Storage.get(ownerListKey);
}
/**
* Appends a new website name to the list of the given owner.
*
* @param owner -
* @param websiteName -
*/
function dns1_addToOwnerList(owner: Address, websiteName: string): void {
const ownerListKey = dns1_ownerKey(owner);
if (!Storage.has(ownerListKey)) {
Storage.set(ownerListKey, new Args().add(websiteName).serialize());
generateEvent(
`Domain name ${websiteName} added to owner address ${owner.toString()}`,
);
return;
}
const oldList = new Args(Storage.get(ownerListKey)).nextString().unwrap();
if (oldList.split(',').includes(websiteName)) {
triggerError('ALREADY_RESERVED'); // it's not possible with the current implementation.
}
let newList = '';
if (oldList.length == 0) {
// needed if we implement a removeFromOwnerList function
newList = websiteName;
} else {
newList = oldList + ',' + websiteName;
}
Storage.set(ownerListKey, new Args().add(newList).serialize());
generateEvent(
`Domain name ${websiteName} added to owner address ${owner.toString()}`,
);
}
/**
* Deletes a website name from the list of the given owner.
*
* @param websiteName - The name of the website to delete.
*/
function dns1_deleteFromOwnerList(websiteName: string): void {
const ownerbinary = dns1_owner(new Args().add(websiteName).serialize());
const ownerAddr = new Address(new Args(ownerbinary).nextString().unwrap());
const ownerListKey = dns1_ownerKey(ownerAddr);
const oldList = new Args(dns1_getOwnerWebsiteList(ownerbinary))
.nextString()
.unwrap();
const oldListArray = oldList.split(',');
// Find the index of the websiteName in the old list array
const index = oldListArray.indexOf(websiteName);
// Check if the websiteName exists in the owner's list
if (index === -1) {
triggerError('WEBSITE_NOT_FOUND');
return;
}
// Remove the websiteName from the old list array
oldListArray.splice(index, 1);
// Join the updated list array back into a string
const newList = oldListArray.join(',');
// Update the owner's list with the updated list
Storage.set(ownerListKey, new Args().add(newList).serialize());
// Emit an event to indicate the website name has been deleted from the owner's list
generateEvent(
`Domain name ${websiteName} deleted from owner address ${ownerAddr.toString()}`,
);
}
/**
* Retrieves the array of blacklisted keys.
*
* @returns The array of blacklisted keys as a StaticArray<u8>.
*/
export function dns1_getBlacklisted(): StaticArray<u8> {
// Deserialize the blacklisted keys from storage, if it exists
return Storage.has(blackListKey) ? Storage.get(blackListKey) : [0, 0, 0, 0];
}
/**
* Appends multiple website names to the blacklist.
*
* @param binaryArgs - Website names in a binary format using Args.
*/
export function dns1_addWebsitesToBlackList(binaryArgs: StaticArray<u8>): void {
// Ensure that the caller is the contract owner
onlyOwner();
// Extract the website names from binaryArgs and unwrap them into an array
const websiteNames = new Args(binaryArgs).nextStringArray().unwrap();
// Retrieve the current blacklisted keys
const existingBlacklist = new Args(dns1_getBlacklisted())
.nextStringArray()
.unwrap();
// Create a Set to ensure uniqueness of website names
const blacklistSet = new Set<string>();
// Merge the existing blacklist with the new website names
const mergedWebsiteNames = websiteNames.concat(existingBlacklist);
// Add each website name to the blacklist Set
// the set is used to handle the case where a name is already blacklisted
// so we will not add it twice to the blacklist
for (let i = 0; i < mergedWebsiteNames.length; i++) {
blacklistSet.add(mergedWebsiteNames[i]);
}
// Convert the Set back to an array
const updatedBlacklist = blacklistSet.values();
// Serialize the new blacklist array and store it in storage
Storage.set(blackListKey, new Args().add(updatedBlacklist).serialize());
// Generate an event with the website names that were added to the blacklist
generateEvent(`Domain names added to blacklist: ${websiteNames.join(', ')}`);
}
/**
* Checks if a website name is blacklisted.
*
* @param binaryArgs - Website name in a binary format using Args.
* @returns A serialized boolean indicating whether the website name is blacklisted.
*/
export function dns1_isBlacklisted(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const websiteName = new Args(binaryArgs).nextString().unwrap();
const blacklistedKeys = new Args(dns1_getBlacklisted())
.nextStringArray()
.unwrap();
const isBlacklisted = blacklistedKeys.includes(websiteName);
return new Args().add(isBlacklisted).serialize();
}
/**
* Delete a single entry from the DNS based on the given website name.
*
* @param binaryArgs - Website name in a binary format using Args.
*/
export function dns1_deleteEntryFromDNS(binaryArgs: StaticArray<u8>): void {
const websiteName = new Args(binaryArgs).nextString().unwrap();
// Ensure that the caller is the owner of the website
dns1_onlyWebsiteOwner(websiteName);
if (Storage.has(binaryArgs)) {
// Check if the website name exists in the DNS and delete it if found
dns1_deleteFromOwnerList(websiteName);
Storage.del(binaryArgs);
// Generate an event with the website name that was deleted from the DNS
generateEvent(`Domain name deleted from DNS: ${websiteName}`);
} else {
triggerError('WEBSITE_NOT_FOUND');
}
}
/**
* Deletes entries from the DNS based on the given website names.
*
* @param binaryArgs - Website names in a binary format using Args.
*/
export function dns1_deleteEntriesFromDNS(binaryArgs: StaticArray<u8>): void {
// Extract the website names from binaryArgs and unwrap them into an array
const websiteNamesToDelete = new Args(binaryArgs)
.nextStringArray()
.expect('website name list is missing or invalid');
// Loop through the list of website names to delete and remove them from the DNS
for (let i = 0; i < websiteNamesToDelete.length; i++) {
const websiteName = websiteNamesToDelete[i];
dns1_deleteEntryFromDNS(new Args().add(websiteName).serialize());
}
}