@openzeppelin/wizard-stellar 0.1.0

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.
Files changed (109) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +8 -0
  3. package/README.md +57 -0
  4. package/dist/add-pausable.d.ts +4 -0
  5. package/dist/add-pausable.d.ts.map +1 -0
  6. package/dist/add-pausable.js +37 -0
  7. package/dist/add-pausable.js.map +1 -0
  8. package/dist/api.d.ts +22 -0
  9. package/dist/api.d.ts.map +1 -0
  10. package/dist/api.js +9 -0
  11. package/dist/api.js.map +1 -0
  12. package/dist/build-generic.d.ts +9 -0
  13. package/dist/build-generic.d.ts.map +1 -0
  14. package/dist/build-generic.js +15 -0
  15. package/dist/build-generic.js.map +1 -0
  16. package/dist/common-options.d.ts +15 -0
  17. package/dist/common-options.d.ts.map +1 -0
  18. package/dist/common-options.js +31 -0
  19. package/dist/common-options.js.map +1 -0
  20. package/dist/contract.d.ts +91 -0
  21. package/dist/contract.d.ts.map +1 -0
  22. package/dist/contract.js +123 -0
  23. package/dist/contract.js.map +1 -0
  24. package/dist/error.d.ts +8 -0
  25. package/dist/error.d.ts.map +1 -0
  26. package/dist/error.js +11 -0
  27. package/dist/error.js.map +1 -0
  28. package/dist/fungible.d.ts +24 -0
  29. package/dist/fungible.d.ts.map +1 -0
  30. package/dist/fungible.js +264 -0
  31. package/dist/fungible.js.map +1 -0
  32. package/dist/generate/alternatives.d.ts +7 -0
  33. package/dist/generate/alternatives.d.ts.map +1 -0
  34. package/dist/generate/alternatives.js +27 -0
  35. package/dist/generate/alternatives.js.map +1 -0
  36. package/dist/generate/fungible.d.ts +3 -0
  37. package/dist/generate/fungible.d.ts.map +1 -0
  38. package/dist/generate/fungible.js +21 -0
  39. package/dist/generate/fungible.js.map +1 -0
  40. package/dist/generate/sources.d.ts +16 -0
  41. package/dist/generate/sources.d.ts.map +1 -0
  42. package/dist/generate/sources.js +62 -0
  43. package/dist/generate/sources.js.map +1 -0
  44. package/dist/index.d.ts +16 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +24 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/kind.d.ts +4 -0
  49. package/dist/kind.d.ts.map +1 -0
  50. package/dist/kind.js +24 -0
  51. package/dist/kind.js.map +1 -0
  52. package/dist/print.d.ts +3 -0
  53. package/dist/print.d.ts.map +1 -0
  54. package/dist/print.js +257 -0
  55. package/dist/print.js.map +1 -0
  56. package/dist/set-access-control.d.ts +15 -0
  57. package/dist/set-access-control.d.ts.map +1 -0
  58. package/dist/set-access-control.js +67 -0
  59. package/dist/set-access-control.js.map +1 -0
  60. package/dist/set-info.d.ts +10 -0
  61. package/dist/set-info.d.ts.map +1 -0
  62. package/dist/set-info.js +13 -0
  63. package/dist/set-info.js.map +1 -0
  64. package/dist/test.d.ts +2 -0
  65. package/dist/test.d.ts.map +1 -0
  66. package/dist/test.js +21 -0
  67. package/dist/test.js.map +1 -0
  68. package/dist/utils/convert-strings.d.ts +27 -0
  69. package/dist/utils/convert-strings.d.ts.map +1 -0
  70. package/dist/utils/convert-strings.js +81 -0
  71. package/dist/utils/convert-strings.js.map +1 -0
  72. package/dist/utils/define-functions.d.ts +5 -0
  73. package/dist/utils/define-functions.d.ts.map +1 -0
  74. package/dist/utils/define-functions.js +7 -0
  75. package/dist/utils/define-functions.js.map +1 -0
  76. package/dist/utils/format-lines.d.ts +6 -0
  77. package/dist/utils/format-lines.d.ts.map +1 -0
  78. package/dist/utils/format-lines.js +28 -0
  79. package/dist/utils/format-lines.js.map +1 -0
  80. package/dist/utils/sorted-by.d.ts +2 -0
  81. package/dist/utils/sorted-by.d.ts.map +1 -0
  82. package/dist/utils/sorted-by.js +7 -0
  83. package/dist/utils/sorted-by.js.map +1 -0
  84. package/dist/utils/version.d.ts +10 -0
  85. package/dist/utils/version.d.ts.map +1 -0
  86. package/dist/utils/version.js +13 -0
  87. package/dist/utils/version.js.map +1 -0
  88. package/package.json +33 -0
  89. package/src/add-pausable.ts +40 -0
  90. package/src/api.ts +30 -0
  91. package/src/build-generic.ts +19 -0
  92. package/src/common-options.ts +38 -0
  93. package/src/contract.ts +201 -0
  94. package/src/error.ts +7 -0
  95. package/src/fungible.ts +300 -0
  96. package/src/generate/alternatives.ts +32 -0
  97. package/src/generate/fungible.ts +21 -0
  98. package/src/generate/sources.ts +75 -0
  99. package/src/index.ts +24 -0
  100. package/src/kind.ts +26 -0
  101. package/src/print.ts +305 -0
  102. package/src/set-access-control.ts +73 -0
  103. package/src/set-info.ts +17 -0
  104. package/src/test.ts +26 -0
  105. package/src/utils/convert-strings.ts +83 -0
  106. package/src/utils/define-functions.ts +9 -0
  107. package/src/utils/format-lines.ts +26 -0
  108. package/src/utils/sorted-by.ts +3 -0
  109. package/src/utils/version.ts +10 -0
package/src/print.ts ADDED
@@ -0,0 +1,305 @@
1
+ import type { Contract, Argument, ContractFunction, ImplementedTrait, UseClause } from './contract';
2
+
3
+ import type { Lines } from './utils/format-lines';
4
+ import { formatLines, spaceBetween } from './utils/format-lines';
5
+ import { getSelfArg } from './common-options';
6
+ import { compatibleContractsSemver } from './utils/version';
7
+
8
+ const DEFAULT_SECTION = '1. with no section';
9
+ const STANDALONE_IMPORTS_GROUP = 'Standalone Imports';
10
+ const MAX_USE_CLAUSE_LINE_LENGTH = 90;
11
+ const TAB = ' ';
12
+
13
+ export function printContract(contract: Contract): string {
14
+ return formatLines(
15
+ ...spaceBetween(
16
+ [
17
+ `// SPDX-License-Identifier: ${contract.license}`,
18
+ `// Compatible with OpenZeppelin Stellar Soroban Contracts ${compatibleContractsSemver}`,
19
+ ],
20
+ spaceBetween(
21
+ printUseClauses(contract),
22
+ printVariables(contract),
23
+ printContractStruct(contract),
24
+ printContractErrors(contract),
25
+ printContractFunctions(contract),
26
+ printImplementedTraits(contract),
27
+ ),
28
+ ),
29
+ );
30
+ }
31
+
32
+ function printContractStruct(contract: Contract): Lines[] {
33
+ return ['#[contract]', `pub struct ${contract.name};`];
34
+ }
35
+
36
+ function printContractErrors(contract: Contract): Lines[] {
37
+ if (contract.errors.length === 0) {
38
+ return [];
39
+ }
40
+ return [
41
+ '#[contracterror]',
42
+ '#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]',
43
+ '#[repr(u32)]',
44
+ `pub enum ${contract.name}Error {`,
45
+ contract.errors.map(e => `${e.name} = ${e.num},`),
46
+ `}`,
47
+ ];
48
+ }
49
+
50
+ function withSemicolons(lines: string[]): string[] {
51
+ return lines.map(line => (line.endsWith(';') ? line : line + ';'));
52
+ }
53
+
54
+ function printVariables(contract: Contract): string[] {
55
+ return withSemicolons(contract.variables.map(v => `const ${v.name}: ${v.type} = ${v.value}`));
56
+ }
57
+
58
+ function printUseClauses(contract: Contract): Lines[] {
59
+ const useClauses = sortUseClauses(contract);
60
+
61
+ // group by containerPath
62
+ const grouped = useClauses.reduce((result: { [containerPath: string]: UseClause[] }, useClause: UseClause) => {
63
+ if (useClause.groupable) {
64
+ (result[useClause.containerPath] = result[useClause.containerPath] || []).push(useClause);
65
+ } else {
66
+ (result[STANDALONE_IMPORTS_GROUP] = result[STANDALONE_IMPORTS_GROUP] || []).push(useClause);
67
+ }
68
+ return result;
69
+ }, {});
70
+
71
+ const lines = Object.entries(grouped).flatMap(([groupName, group]) => getLinesFromUseClausesGroup(group, groupName));
72
+ return lines.flatMap(line => splitLongUseClauseLine(line.toString()));
73
+ }
74
+
75
+ function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lines[] {
76
+ const lines = [];
77
+ if (groupName === STANDALONE_IMPORTS_GROUP) {
78
+ for (const useClause of group) {
79
+ lines.push(`use ${useClause.containerPath}::${nameWithAlias(useClause)};`);
80
+ }
81
+ } else {
82
+ if (group.length == 1) {
83
+ lines.push(`use ${groupName}::${nameWithAlias(group[0]!)};`);
84
+ } else if (group.length > 1) {
85
+ const names = group.map(useClause => nameWithAlias(useClause)).join(', ');
86
+ lines.push(`use ${groupName}::{${names}};`);
87
+ }
88
+ }
89
+ return lines;
90
+ }
91
+
92
+ function nameWithAlias(useClause: UseClause): string {
93
+ return useClause.alias ? `${useClause.name} as ${useClause.alias}` : useClause.name;
94
+ }
95
+
96
+ // TODO: remove this when we can use a formatting js library
97
+ function splitLongUseClauseLine(line: string): Lines[] {
98
+ const lines = [];
99
+
100
+ const containsBraces = line.indexOf('{') !== -1;
101
+ if (containsBraces && line.length > MAX_USE_CLAUSE_LINE_LENGTH) {
102
+ // split at the first brace
103
+ lines.push(line.slice(0, line.indexOf('{') + 1));
104
+ lines.push(...splitLongLineInner(line.slice(line.indexOf('{') + 1, -2)));
105
+ lines.push('};');
106
+ } else {
107
+ lines.push(line);
108
+ }
109
+ return lines;
110
+ }
111
+
112
+ function splitLongLineInner(line: string): Lines[] {
113
+ const lines = [];
114
+ if (line.length > MAX_USE_CLAUSE_LINE_LENGTH) {
115
+ const max_accessible_string = line.slice(0, MAX_USE_CLAUSE_LINE_LENGTH);
116
+ const lastCommaIndex = max_accessible_string.lastIndexOf(',');
117
+ if (lastCommaIndex !== -1) {
118
+ lines.push(TAB + max_accessible_string.slice(0, lastCommaIndex + 1));
119
+ lines.push(...splitLongLineInner(line.slice(lastCommaIndex + 2)));
120
+ } else {
121
+ lines.push(TAB + max_accessible_string);
122
+ }
123
+ } else {
124
+ lines.push(TAB + line);
125
+ }
126
+ return lines;
127
+ }
128
+
129
+ function sortUseClauses(contract: Contract): UseClause[] {
130
+ return contract.useClauses.sort((a, b) => {
131
+ if (a.containerPath === b.containerPath) {
132
+ if (a.name === 'self' && b.name !== 'self') {
133
+ return -1;
134
+ } else if (a.name !== 'self' && b.name === 'self') {
135
+ return 1;
136
+ }
137
+ return nameWithAlias(a).localeCompare(nameWithAlias(b));
138
+ } else {
139
+ return a.containerPath.localeCompare(b.containerPath);
140
+ }
141
+ });
142
+ }
143
+
144
+ function printImplementedTraits(contract: Contract): Lines[] {
145
+ // sort first by priority, then number of tags, then name
146
+ const sortedTraits = contract.implementedTraits.sort((a, b) => {
147
+ if (a.priority !== b.priority) {
148
+ return (a.priority ?? Infinity) - (b.priority ?? Infinity);
149
+ }
150
+ if (a.tags.length !== b.tags.length) {
151
+ return a.tags.length - b.tags.length;
152
+ }
153
+ return a.name.localeCompare(b.name);
154
+ });
155
+
156
+ // group by section
157
+ const grouped = sortedTraits.reduce(
158
+ (result: { [section: string]: ImplementedTrait[] }, current: ImplementedTrait) => {
159
+ // default to no section
160
+ const section = current.section ?? DEFAULT_SECTION;
161
+ (result[section] = result[section] || []).push(current);
162
+ return result;
163
+ },
164
+ {},
165
+ );
166
+
167
+ const sections = Object.entries(grouped)
168
+ .sort((a, b) => a[0].localeCompare(b[0]))
169
+ .map(([section, impls]) => printImplementedTraitsSection(section, impls as ImplementedTrait[]));
170
+
171
+ return spaceBetween(...sections);
172
+ }
173
+
174
+ function printImplementedTraitsSection(section: string, impls: ImplementedTrait[]): Lines[] {
175
+ const lines = [];
176
+ const isDefaultSection = section === DEFAULT_SECTION;
177
+ if (!isDefaultSection) {
178
+ lines.push('//');
179
+ lines.push(`// ${section}`);
180
+ lines.push('//');
181
+ }
182
+ impls.forEach((trait, index) => {
183
+ if (index > 0 || !isDefaultSection) {
184
+ lines.push('');
185
+ }
186
+ lines.push(...printImplementedTrait(trait));
187
+ });
188
+ return lines;
189
+ }
190
+
191
+ function printImplementedTrait(trait: ImplementedTrait): Lines[] {
192
+ const implLines = [];
193
+ implLines.push(...trait.tags.map(t => `#[${t}]`));
194
+ implLines.push(`impl ${trait.name} for ${trait.for} {`);
195
+ const fns = trait.functions.map(fn => printFunction(fn));
196
+ implLines.push(spaceBetween(...fns));
197
+ implLines.push('}');
198
+ return implLines;
199
+ }
200
+
201
+ function printFunction(fn: ContractFunction): Lines[] {
202
+ const head = `fn ${fn.name}`;
203
+ const args = fn.args.map(a => printArgument(a));
204
+
205
+ const codeLines = fn.codeBefore?.concat(fn.code) ?? fn.code;
206
+ for (let i = 0; i < codeLines.length; i++) {
207
+ const line = codeLines[i];
208
+ const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined;
209
+ if (line !== undefined && line.length > 0) {
210
+ if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) {
211
+ codeLines[i] += ';';
212
+ } else if (!shouldEndWithSemicolon && line.endsWith(';')) {
213
+ codeLines[i] = line.slice(0, line.length - 1);
214
+ }
215
+ }
216
+ }
217
+
218
+ return printFunction2(head, args, fn.tag, fn.returns, undefined, codeLines);
219
+ }
220
+
221
+ function printContractFunctions(contract: Contract): Lines[] {
222
+ const implLines = [];
223
+ implLines.push('#[contractimpl]');
224
+ implLines.push(`impl ${contract.name} {`);
225
+ implLines.push(printConstructor(contract));
226
+ implLines.push('}');
227
+ return implLines;
228
+ }
229
+
230
+ function printConstructor(contract: Contract): Lines[] {
231
+ if (contract.constructorCode.length > 0) {
232
+ const head = 'pub fn __constructor';
233
+ const args = [getSelfArg(), ...contract.constructorArgs];
234
+
235
+ const body = spaceBetween(withSemicolons(contract.constructorCode));
236
+
237
+ const constructor = printFunction2(
238
+ head,
239
+ args.map(a => printArgument(a)),
240
+ undefined,
241
+ undefined,
242
+ undefined,
243
+ body,
244
+ );
245
+ return constructor;
246
+ } else {
247
+ return [];
248
+ }
249
+ }
250
+
251
+ // generic for functions and constructors
252
+ // kindedName = 'fn foo'
253
+ function printFunction2(
254
+ kindedName: string,
255
+ args: string[],
256
+ tag: string | undefined,
257
+ returns: string | undefined,
258
+ returnLine: string | undefined,
259
+ code: Lines[],
260
+ ): Lines[] {
261
+ const fn = [];
262
+
263
+ if (tag !== undefined) {
264
+ fn.push(`#[${tag}]`);
265
+ }
266
+
267
+ let accum = `${kindedName}(`;
268
+
269
+ if (args.length > 0) {
270
+ const formattedArgs = args.join(', ');
271
+ if (formattedArgs.length > 80) {
272
+ fn.push(accum);
273
+ accum = '';
274
+ // print each arg in a separate line
275
+ fn.push(args.map(arg => `${arg},`));
276
+ } else {
277
+ accum += `${formattedArgs}`;
278
+ }
279
+ }
280
+ accum += ')';
281
+
282
+ if (returns === undefined) {
283
+ accum += ' {';
284
+ } else {
285
+ accum += ` -> ${returns} {`;
286
+ }
287
+
288
+ fn.push(accum);
289
+ fn.push(code);
290
+ if (returnLine !== undefined) {
291
+ fn.push([returnLine]);
292
+ }
293
+ fn.push('}');
294
+
295
+ return fn;
296
+ }
297
+
298
+ function printArgument(arg: Argument): string {
299
+ if (arg.type !== undefined) {
300
+ const type = arg.type;
301
+ return `${arg.name}: ${type}`;
302
+ } else {
303
+ return `${arg.name}`;
304
+ }
305
+ }
@@ -0,0 +1,73 @@
1
+ import type { BaseFunction, BaseImplementedTrait, ContractBuilder } from './contract';
2
+
3
+ export const accessOptions = [false, 'ownable'] as const;
4
+ export const DEFAULT_ACCESS_CONTROL = 'ownable';
5
+
6
+ export type Access = (typeof accessOptions)[number];
7
+
8
+ /**
9
+ * Sets access control for the contract via constructor args.
10
+ */
11
+ export function setAccessControl(c: ContractBuilder, access: Access): void {
12
+ switch (access) {
13
+ case false:
14
+ break;
15
+ case 'ownable': {
16
+ if (!c.ownable) {
17
+ c.ownable = true;
18
+ c.addUseClause('soroban_sdk', 'symbol_short');
19
+ c.addUseClause('soroban_sdk', 'Symbol');
20
+ c.addVariable({ name: 'OWNER', type: 'Symbol', value: `symbol_short!("OWNER")` });
21
+ c.addConstructorArgument({ name: 'owner', type: 'Address' });
22
+ c.addConstructorCode('e.storage().instance().set(&OWNER, &owner);');
23
+ }
24
+ break;
25
+ }
26
+ default: {
27
+ const _: never = access;
28
+ throw new Error('Unknown value for `access`');
29
+ }
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Enables access control for the contract and restricts the given function with access control.
35
+ *
36
+ * If `caller` is provided, requires that the caller is the owner. Otherwise, requires that the owner is authorized.
37
+ */
38
+ export function requireAccessControl(
39
+ c: ContractBuilder,
40
+ trait: BaseImplementedTrait,
41
+ fn: BaseFunction,
42
+ access: Access,
43
+ caller?: string,
44
+ ): void {
45
+ if (access === false) {
46
+ access = DEFAULT_ACCESS_CONTROL;
47
+ }
48
+ setAccessControl(c, access);
49
+
50
+ switch (access) {
51
+ case 'ownable': {
52
+ c.addUseClause('soroban_sdk', 'Address');
53
+ const getOwner = 'let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");';
54
+ if (caller) {
55
+ c.addUseClause('soroban_sdk', 'panic_with_error');
56
+ c.addError('Unauthorized', 1); // TODO: Ensure there are no conflicts in error codes
57
+ c.addFunctionCodeBefore(trait, fn, [
58
+ getOwner,
59
+ 'if owner != caller {',
60
+ ` panic_with_error!(e, ${c.name}Error::Unauthorized)`,
61
+ '}',
62
+ ]);
63
+ } else {
64
+ c.addFunctionCodeBefore(trait, fn, [getOwner, 'owner.require_auth();']);
65
+ }
66
+ break;
67
+ }
68
+ default: {
69
+ const _: never = access;
70
+ throw new Error('Unknown value for `access`');
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,17 @@
1
+ import type { ContractBuilder } from './contract';
2
+
3
+ export const infoOptions = [{}, { license: 'WTFPL' }] as const;
4
+
5
+ export const defaults: Info = { license: 'MIT' };
6
+
7
+ export type Info = {
8
+ license?: string;
9
+ };
10
+
11
+ export function setInfo(c: ContractBuilder, info: Info): void {
12
+ const { license } = info;
13
+
14
+ if (license) {
15
+ c.license = license;
16
+ }
17
+ }
package/src/test.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { promises as fs } from 'fs';
2
+ import os from 'os';
3
+ import type { TestFn, ExecutionContext } from 'ava';
4
+ import _test from 'ava';
5
+ import path from 'path';
6
+
7
+ import { writeGeneratedSources } from './generate/sources';
8
+ import type { KindedOptions } from './build-generic';
9
+
10
+ interface Context {
11
+ generatedSourcesPath: string;
12
+ }
13
+
14
+ const test = _test as TestFn<Context>;
15
+
16
+ test.serial('fungible result generated', async t => {
17
+ await testGenerate(t, 'Fungible');
18
+ });
19
+
20
+ async function testGenerate(t: ExecutionContext<Context>, kind: keyof KindedOptions) {
21
+ const generatedSourcesPath = path.join(os.tmpdir(), 'oz-wizard-stellar');
22
+ await fs.rm(generatedSourcesPath, { force: true, recursive: true });
23
+ await writeGeneratedSources(generatedSourcesPath, true, kind);
24
+
25
+ t.pass();
26
+ }
@@ -0,0 +1,83 @@
1
+ import { OptionsError } from '../error';
2
+
3
+ // TODO: Review and convert these to properly handle language restrictions for Stellar
4
+
5
+ /**
6
+ * Converts to an identifier according to the rules in https://docs.cairo-lang.org/language_constructs/identifiers.html
7
+ */
8
+ export function toIdentifier(str: string, capitalize = false): string {
9
+ const result = str
10
+ .normalize('NFD')
11
+ .replace(/[\u0300-\u036f]/g, '') // remove accents
12
+ .replace(/^[^a-zA-Z_]+/, '')
13
+ .replace(/^(.)/, c => (capitalize ? c.toUpperCase() : c))
14
+ .replace(/[^\w]+(.?)/g, (_, c) => c.toUpperCase());
15
+
16
+ if (result.length === 0) {
17
+ throw new OptionsError({
18
+ name: 'Identifier is empty or does not have valid characters',
19
+ });
20
+ } else {
21
+ return result;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Converts to a ByteArray compatible string literal
27
+ */
28
+ export function toByteArray(str: string): string {
29
+ return str
30
+ .normalize('NFD')
31
+ .replace(/[\u0300-\u036f]/g, '') // remove accents
32
+ .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters
33
+ .replace(/(\\|")/g, (_, c) => '\\' + c); // escape backslash or double quotes
34
+ }
35
+
36
+ function maxValueOfUint(bits: number): bigint {
37
+ if (bits <= 0) {
38
+ throw new Error(`Number of bits must be positive (actual '${bits}').`);
39
+ }
40
+ if (bits % 8 !== 0) {
41
+ throw new Error(`The number of bits must be a multiple of 8 (actual '${bits}').`);
42
+ }
43
+ const bytes = bits / 8;
44
+ return BigInt('0x' + 'ff'.repeat(bytes));
45
+ }
46
+
47
+ const UINT_MAX_VALUES = {
48
+ u8: maxValueOfUint(8),
49
+ u16: maxValueOfUint(16),
50
+ u32: maxValueOfUint(32),
51
+ u64: maxValueOfUint(64),
52
+ u128: maxValueOfUint(128),
53
+ u256: maxValueOfUint(256),
54
+ } as const;
55
+
56
+ export type UintType = keyof typeof UINT_MAX_VALUES;
57
+
58
+ /**
59
+ * Checks that a string/number value is a valid `uint` value and converts it to bigint
60
+ */
61
+ export function toUint(value: number | string, field: string, type: UintType): bigint {
62
+ const valueAsStr = value.toString();
63
+ const isValidNumber = /^\d+$/.test(valueAsStr);
64
+ if (!isValidNumber) {
65
+ throw new OptionsError({
66
+ [field]: 'Not a valid number',
67
+ });
68
+ }
69
+ const numValue = BigInt(valueAsStr);
70
+ if (numValue > UINT_MAX_VALUES[type]) {
71
+ throw new OptionsError({
72
+ [field]: `Value is greater than ${type} max value`,
73
+ });
74
+ }
75
+ return numValue;
76
+ }
77
+
78
+ /**
79
+ * Checks that a string/number value is a valid `uint` value
80
+ */
81
+ export function validateUint(value: number | string, field: string, type: UintType): void {
82
+ const _ = toUint(value, field, type);
83
+ }
@@ -0,0 +1,9 @@
1
+ import type { BaseFunction } from '../contract';
2
+
3
+ type ImplicitNameFunction = Omit<BaseFunction, 'name'>;
4
+
5
+ export function defineFunctions<F extends string>(fns: Record<F, ImplicitNameFunction>): Record<F, BaseFunction>;
6
+
7
+ export function defineFunctions(fns: Record<string, ImplicitNameFunction>): Record<string, BaseFunction> {
8
+ return Object.fromEntries(Object.entries(fns).map(([name, fn]) => [name, Object.assign({ name }, fn)]));
9
+ }
@@ -0,0 +1,26 @@
1
+ export type Lines = string | typeof whitespace | Lines[];
2
+
3
+ const whitespace = Symbol('whitespace');
4
+
5
+ export function formatLines(...lines: Lines[]): string {
6
+ return [...indentEach(0, lines)].join('\n') + '\n';
7
+ }
8
+
9
+ function* indentEach(indent: number, lines: Lines[]): Generator<string | typeof whitespace> {
10
+ for (const line of lines) {
11
+ if (line === whitespace) {
12
+ yield '';
13
+ } else if (Array.isArray(line)) {
14
+ yield* indentEach(indent + 1, line);
15
+ } else {
16
+ yield ' '.repeat(indent) + line;
17
+ }
18
+ }
19
+ }
20
+
21
+ export function spaceBetween(...lines: Lines[][]): Lines[] {
22
+ return lines
23
+ .filter(l => l.length > 0)
24
+ .flatMap<Lines>(l => [whitespace, ...l])
25
+ .slice(1);
26
+ }
@@ -0,0 +1,3 @@
1
+ export function sortedBy<T>(xs: Iterable<T>, fn: (x: T) => number): T[] {
2
+ return Array.from(xs).sort((a, b) => fn(a) - fn(b));
3
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The actual latest version to use in links.
3
+ */
4
+ export const contractsVersion = '0.1.0';
5
+ export const contractsVersionTag = `v${contractsVersion}`;
6
+
7
+ /**
8
+ * Semantic version string representing of the minimum compatible version of Contracts to display in output.
9
+ */
10
+ export const compatibleContractsSemver = '^0.1.0';