@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.
- package/LICENSE +661 -0
- package/NOTICE +8 -0
- package/README.md +57 -0
- package/dist/add-pausable.d.ts +4 -0
- package/dist/add-pausable.d.ts.map +1 -0
- package/dist/add-pausable.js +37 -0
- package/dist/add-pausable.js.map +1 -0
- package/dist/api.d.ts +22 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +9 -0
- package/dist/api.js.map +1 -0
- package/dist/build-generic.d.ts +9 -0
- package/dist/build-generic.d.ts.map +1 -0
- package/dist/build-generic.js +15 -0
- package/dist/build-generic.js.map +1 -0
- package/dist/common-options.d.ts +15 -0
- package/dist/common-options.d.ts.map +1 -0
- package/dist/common-options.js +31 -0
- package/dist/common-options.js.map +1 -0
- package/dist/contract.d.ts +91 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +123 -0
- package/dist/contract.js.map +1 -0
- package/dist/error.d.ts +8 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +11 -0
- package/dist/error.js.map +1 -0
- package/dist/fungible.d.ts +24 -0
- package/dist/fungible.d.ts.map +1 -0
- package/dist/fungible.js +264 -0
- package/dist/fungible.js.map +1 -0
- package/dist/generate/alternatives.d.ts +7 -0
- package/dist/generate/alternatives.d.ts.map +1 -0
- package/dist/generate/alternatives.js +27 -0
- package/dist/generate/alternatives.js.map +1 -0
- package/dist/generate/fungible.d.ts +3 -0
- package/dist/generate/fungible.d.ts.map +1 -0
- package/dist/generate/fungible.js +21 -0
- package/dist/generate/fungible.js.map +1 -0
- package/dist/generate/sources.d.ts +16 -0
- package/dist/generate/sources.d.ts.map +1 -0
- package/dist/generate/sources.js +62 -0
- package/dist/generate/sources.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/kind.d.ts +4 -0
- package/dist/kind.d.ts.map +1 -0
- package/dist/kind.js +24 -0
- package/dist/kind.js.map +1 -0
- package/dist/print.d.ts +3 -0
- package/dist/print.d.ts.map +1 -0
- package/dist/print.js +257 -0
- package/dist/print.js.map +1 -0
- package/dist/set-access-control.d.ts +15 -0
- package/dist/set-access-control.d.ts.map +1 -0
- package/dist/set-access-control.js +67 -0
- package/dist/set-access-control.js.map +1 -0
- package/dist/set-info.d.ts +10 -0
- package/dist/set-info.d.ts.map +1 -0
- package/dist/set-info.js +13 -0
- package/dist/set-info.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +21 -0
- package/dist/test.js.map +1 -0
- package/dist/utils/convert-strings.d.ts +27 -0
- package/dist/utils/convert-strings.d.ts.map +1 -0
- package/dist/utils/convert-strings.js +81 -0
- package/dist/utils/convert-strings.js.map +1 -0
- package/dist/utils/define-functions.d.ts +5 -0
- package/dist/utils/define-functions.d.ts.map +1 -0
- package/dist/utils/define-functions.js +7 -0
- package/dist/utils/define-functions.js.map +1 -0
- package/dist/utils/format-lines.d.ts +6 -0
- package/dist/utils/format-lines.d.ts.map +1 -0
- package/dist/utils/format-lines.js +28 -0
- package/dist/utils/format-lines.js.map +1 -0
- package/dist/utils/sorted-by.d.ts +2 -0
- package/dist/utils/sorted-by.d.ts.map +1 -0
- package/dist/utils/sorted-by.js +7 -0
- package/dist/utils/sorted-by.js.map +1 -0
- package/dist/utils/version.d.ts +10 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +13 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +33 -0
- package/src/add-pausable.ts +40 -0
- package/src/api.ts +30 -0
- package/src/build-generic.ts +19 -0
- package/src/common-options.ts +38 -0
- package/src/contract.ts +201 -0
- package/src/error.ts +7 -0
- package/src/fungible.ts +300 -0
- package/src/generate/alternatives.ts +32 -0
- package/src/generate/fungible.ts +21 -0
- package/src/generate/sources.ts +75 -0
- package/src/index.ts +24 -0
- package/src/kind.ts +26 -0
- package/src/print.ts +305 -0
- package/src/set-access-control.ts +73 -0
- package/src/set-info.ts +17 -0
- package/src/test.ts +26 -0
- package/src/utils/convert-strings.ts +83 -0
- package/src/utils/define-functions.ts +9 -0
- package/src/utils/format-lines.ts +26 -0
- package/src/utils/sorted-by.ts +3 -0
- 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
|
+
}
|
package/src/set-info.ts
ADDED
|
@@ -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,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';
|