@openzeppelin/wizard-stellar 0.6.4 → 0.7.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 (60) hide show
  1. package/dist/account.d.ts.map +1 -1
  2. package/dist/account.js +18 -4
  3. package/dist/account.js.map +1 -1
  4. package/dist/add-votes.d.ts +3 -0
  5. package/dist/add-votes.d.ts.map +1 -0
  6. package/dist/add-votes.js +53 -0
  7. package/dist/add-votes.js.map +1 -0
  8. package/dist/fungible.d.ts +13 -2
  9. package/dist/fungible.d.ts.map +1 -1
  10. package/dist/fungible.js +147 -77
  11. package/dist/fungible.js.map +1 -1
  12. package/dist/generate/fungible.d.ts.map +1 -1
  13. package/dist/generate/fungible.js +2 -0
  14. package/dist/generate/fungible.js.map +1 -1
  15. package/dist/generate/non-fungible.d.ts.map +1 -1
  16. package/dist/generate/non-fungible.js +2 -0
  17. package/dist/generate/non-fungible.js.map +1 -1
  18. package/dist/generate/stablecoin.d.ts.map +1 -1
  19. package/dist/generate/stablecoin.js +2 -0
  20. package/dist/generate/stablecoin.js.map +1 -1
  21. package/dist/governor.d.ts.map +1 -1
  22. package/dist/governor.js +9 -1
  23. package/dist/governor.js.map +1 -1
  24. package/dist/non-fungible.d.ts +2 -0
  25. package/dist/non-fungible.d.ts.map +1 -1
  26. package/dist/non-fungible.js +152 -126
  27. package/dist/non-fungible.js.map +1 -1
  28. package/dist/print.js +16 -1
  29. package/dist/print.js.map +1 -1
  30. package/dist/stablecoin.d.ts.map +1 -1
  31. package/dist/stablecoin.js +6 -31
  32. package/dist/stablecoin.js.map +1 -1
  33. package/dist/utils/compose.d.ts +6 -0
  34. package/dist/utils/compose.d.ts.map +1 -0
  35. package/dist/utils/compose.js +12 -0
  36. package/dist/utils/compose.js.map +1 -0
  37. package/dist/utils/version.d.ts +4 -4
  38. package/dist/utils/version.js +3 -3
  39. package/dist/vault.d.ts +1 -1
  40. package/dist/vault.d.ts.map +1 -1
  41. package/dist/vault.js +27 -7
  42. package/dist/vault.js.map +1 -1
  43. package/dist/zip-shared.d.ts.map +1 -1
  44. package/dist/zip-shared.js +4 -3
  45. package/dist/zip-shared.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/account.ts +18 -4
  48. package/src/add-votes.ts +53 -0
  49. package/src/fungible.ts +171 -86
  50. package/src/generate/fungible.ts +2 -0
  51. package/src/generate/non-fungible.ts +2 -0
  52. package/src/generate/stablecoin.ts +2 -0
  53. package/src/governor.ts +10 -1
  54. package/src/non-fungible.ts +182 -143
  55. package/src/print.ts +14 -1
  56. package/src/stablecoin.ts +9 -37
  57. package/src/utils/compose.ts +8 -0
  58. package/src/utils/version.ts +3 -3
  59. package/src/vault.ts +32 -7
  60. package/src/zip-shared.ts +4 -3
package/src/fungible.ts CHANGED
@@ -3,6 +3,7 @@ import type { Access } from './set-access-control';
3
3
  import { requireAccessControl, setAccessControl } from './set-access-control';
4
4
  import { addPausable } from './add-pausable';
5
5
  import { addUpgradeable } from './add-upgradeable';
6
+ import { addVotes } from './add-votes';
6
7
  import { defineFunctions } from './utils/define-functions';
7
8
  import type { CommonContractOptions } from './common-options';
8
9
  import { withCommonContractDefaults, getSelfArg } from './common-options';
@@ -11,9 +12,11 @@ import { OptionsError } from './error';
11
12
  import { contractDefaults as commonDefaults } from './common-options';
12
13
  import { printContract } from './print';
13
14
  import { toByteArray, toUint } from './utils/convert-strings';
15
+ import { printComposedContractType } from './utils/compose';
14
16
  import { pickKeys } from '@openzeppelin/wizard-common';
15
17
 
16
18
  const DEFAULT_DECIMALS = 7;
19
+ const I128_MAX = (1n << 127n) - 1n;
17
20
 
18
21
  export const defaults: Required<FungibleOptions> = {
19
22
  name: 'MyToken',
@@ -21,6 +24,8 @@ export const defaults: Required<FungibleOptions> = {
21
24
  decimals: DEFAULT_DECIMALS.toString(),
22
25
  burnable: false,
23
26
  votes: false,
27
+ totalSupply: false,
28
+ cap: '',
24
29
  pausable: false,
25
30
  upgradeable: false,
26
31
  premint: '0',
@@ -40,12 +45,19 @@ export interface FungibleOptions extends CommonContractOptions {
40
45
  decimals?: string;
41
46
  burnable?: boolean;
42
47
  votes?: boolean;
48
+ totalSupply?: boolean;
49
+ cap?: string;
43
50
  pausable?: boolean;
44
51
  upgradeable?: boolean;
45
52
  premint?: string;
46
53
  mintable?: boolean;
47
54
  }
48
55
 
56
+ /**
57
+ * Contract type restricting which accounts can use the token, selected by the Stablecoin limitations.
58
+ */
59
+ export type TransferPolicy = 'AllowList' | 'BlockList';
60
+
49
61
  export function withDefaults(opts: FungibleOptions): Required<FungibleOptions> {
50
62
  return {
51
63
  ...opts,
@@ -53,6 +65,8 @@ export function withDefaults(opts: FungibleOptions): Required<FungibleOptions> {
53
65
  decimals: opts.decimals ?? defaults.decimals,
54
66
  burnable: opts.burnable ?? defaults.burnable,
55
67
  votes: opts.votes ?? defaults.votes,
68
+ totalSupply: opts.totalSupply ?? defaults.totalSupply,
69
+ cap: opts.cap ?? defaults.cap,
56
70
  pausable: opts.pausable ?? defaults.pausable,
57
71
  upgradeable: opts.upgradeable ?? defaults.upgradeable,
58
72
  premint: opts.premint || defaults.premint,
@@ -65,24 +79,71 @@ export function isAccessControlRequired(opts: Partial<FungibleOptions>): boolean
65
79
  }
66
80
 
67
81
  export function buildFungible(opts: FungibleOptions): ContractBuilder {
82
+ return buildFungibleWithTransferPolicy(opts);
83
+ }
84
+
85
+ /**
86
+ * Builds a Fungible contract whose contract type also includes the given transfer policy.
87
+ */
88
+ export function buildFungibleWithTransferPolicy(
89
+ opts: FungibleOptions,
90
+ transferPolicy?: TransferPolicy,
91
+ ): ContractBuilder {
68
92
  const c = new ContractBuilder(opts.name);
69
93
 
70
94
  const allOpts = withDefaults(opts);
71
95
 
72
96
  const decimals = toUint(allOpts.decimals, 'decimals', 'u32');
73
97
 
98
+ const capped = allOpts.cap !== '';
99
+ // The cap is checked against the total supply, so it requires the supply to be tracked.
100
+ const totalSupply = allOpts.totalSupply || capped;
101
+
102
+ if (allOpts.votes && capped) {
103
+ throw new OptionsError({
104
+ votes: 'Votes extension cannot be used with a cap',
105
+ cap: 'Cap cannot be used with Votes extension',
106
+ });
107
+ }
108
+
109
+ if (allOpts.votes && totalSupply) {
110
+ throw new OptionsError({
111
+ votes: 'Votes extension cannot be used with Total Supply extension',
112
+ totalSupply: 'Total Supply extension cannot be used with Votes extension',
113
+ });
114
+ }
115
+
116
+ // Order follows the library's documentation, e.g. `Compose<(AllowList, Capped, TotalSupply)>`.
117
+ const contractTypes = [
118
+ ...(transferPolicy ? [transferPolicy] : []),
119
+ ...(allOpts.votes ? ['FungibleVotes'] : []),
120
+ ...(capped ? ['Capped'] : []),
121
+ ...(totalSupply ? ['TotalSupply'] : []),
122
+ ];
123
+
74
124
  addBase(
75
125
  c,
76
126
  toByteArray(allOpts.name),
77
127
  toByteArray(allOpts.symbol),
78
128
  decimals,
79
- allOpts.votes,
129
+ contractTypes,
80
130
  allOpts.pausable,
81
131
  allOpts.explicitImplementations,
82
132
  );
83
133
 
134
+ if (allOpts.votes) {
135
+ addVotes(c, allOpts.explicitImplementations);
136
+ }
137
+
138
+ if (totalSupply) {
139
+ addTotalSupply(c, allOpts.explicitImplementations);
140
+ }
141
+
142
+ // The cap has to be set before the premint, which is checked against it.
143
+ const capAbsolute = capped ? addCap(c, allOpts.cap, decimals, allOpts.explicitImplementations) : undefined;
144
+
84
145
  if (allOpts.premint) {
85
- addPremint(c, allOpts.premint, decimals, allOpts.votes);
146
+ addPremint(c, allOpts.premint, decimals, capAbsolute);
86
147
  }
87
148
 
88
149
  if (allOpts.pausable) {
@@ -94,11 +155,11 @@ export function buildFungible(opts: FungibleOptions): ContractBuilder {
94
155
  }
95
156
 
96
157
  if (allOpts.burnable) {
97
- addBurnable(c, allOpts.votes, allOpts.pausable, allOpts.explicitImplementations);
158
+ addBurnable(c, allOpts.pausable, allOpts.explicitImplementations);
98
159
  }
99
160
 
100
161
  if (allOpts.mintable) {
101
- addMintable(c, allOpts.access, allOpts.votes, allOpts.pausable, allOpts.explicitImplementations);
162
+ addMintable(c, allOpts.access, allOpts.pausable, allOpts.explicitImplementations);
102
163
  }
103
164
 
104
165
  setAccessControl(c, allOpts.access, allOpts.explicitImplementations);
@@ -112,7 +173,7 @@ function addBase(
112
173
  name: string,
113
174
  symbol: string,
114
175
  decimals: bigint,
115
- votes: boolean,
176
+ contractTypes: string[],
116
177
  pausable: boolean,
117
178
  explicitImplementations: boolean,
118
179
  ) {
@@ -123,10 +184,9 @@ function addBase(
123
184
 
124
185
  // Set token functions
125
186
  c.addUseClause('stellar_tokens::fungible', 'Base');
187
+ c.addUseClause('stellar_tokens::fungible', 'Compose');
126
188
  c.addUseClause('stellar_tokens::fungible', 'FungibleToken');
127
- if (votes) {
128
- c.addUseClause('stellar_tokens::fungible', 'ContractOverrides');
129
- c.addUseClause('stellar_governance::votes', 'Votes');
189
+ if (contractTypes.includes('FungibleVotes')) {
130
190
  c.addUseClause('stellar_tokens::fungible', 'votes::FungibleVotes');
131
191
  }
132
192
  c.addUseClause('soroban_sdk', 'contract');
@@ -142,7 +202,7 @@ function addBase(
142
202
  traitName: 'FungibleToken',
143
203
  structName: c.name,
144
204
  tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
145
- assocType: `type ContractType = ${votes ? 'FungibleVotes' : 'Base'};`,
205
+ assocType: printComposedContractType(contractTypes.length > 0 ? contractTypes : ['Base']),
146
206
  };
147
207
 
148
208
  c.addTraitImplBlock(fungibleTokenTrait);
@@ -159,46 +219,82 @@ function addBase(
159
219
  c.addFunctionTag(functions.transfer_from, 'when_not_paused', fungibleTokenTrait);
160
220
  }
161
221
 
162
- if (votes) {
163
- c.addTraitImplBlock({
164
- traitName: 'Votes',
165
- structName: c.name,
166
- tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
167
- section: 'Extensions',
168
- });
222
+ // `Base` implements every `FungibleToken` method inherently, while the other contract types provide
223
+ // some of them only through the `ContractOverrides` trait, which then has to be in scope. The
224
+ // supply-tracking contract types provide transfers through it as well.
225
+ const tracksSupply = contractTypes.includes('TotalSupply');
226
+ if ((explicitImplementations && contractTypes.length > 0) || (pausable && tracksSupply)) {
227
+ c.addUseClause('stellar_tokens::fungible', 'ContractOverrides');
169
228
  }
170
229
  }
171
230
 
172
- function addMintable(
173
- c: ContractBuilder,
174
- access: Access,
175
- votes: boolean,
176
- pausable: boolean,
177
- explicitImplementations: boolean,
178
- ) {
179
- const mintFn = votes ? votesFunctions.mint : functions.mint;
180
- const mintWithCallerFn = votes ? votesFunctions.mint_with_caller : functions.mint_with_caller;
231
+ function addTotalSupply(c: ContractBuilder, explicitImplementations: boolean) {
232
+ c.addUseClause('stellar_tokens::fungible', 'total_supply::{FungibleTotalSupply, TotalSupply}');
233
+
234
+ const fungibleTotalSupplyTrait = {
235
+ traitName: 'FungibleTotalSupply',
236
+ structName: c.name,
237
+ tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
238
+ section: 'Extensions',
239
+ };
240
+
241
+ if (explicitImplementations) c.addTraitFunction(fungibleTotalSupplyTrait, functions.total_supply);
242
+ else c.addTraitImplBlock(fungibleTotalSupplyTrait);
243
+ }
244
+
245
+ function addCap(c: ContractBuilder, cap: string, decimals: bigint, explicitImplementations: boolean): bigint {
246
+ if (!premintPattern.test(cap)) {
247
+ throw new OptionsError({
248
+ cap: 'Not a valid number',
249
+ });
250
+ }
251
+
252
+ const capAbsolute = toUint(getInitialSupply(cap, Number(decimals), 'cap'), 'cap', 'u128');
253
+ // `Capped::set_cap` takes an `i128`.
254
+ validateI128(capAbsolute, 'cap');
255
+ if (capAbsolute === 0n) {
256
+ throw new OptionsError({
257
+ cap: 'Cap must be greater than 0',
258
+ });
259
+ }
260
+
261
+ c.addUseClause('stellar_tokens::fungible', 'capped::{Capped, FungibleCapped}');
262
+ c.addConstructorCode(`Capped::set_cap(e, ${capAbsolute});`);
181
263
 
264
+ const fungibleCappedTrait = {
265
+ traitName: 'FungibleCapped',
266
+ structName: c.name,
267
+ tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
268
+ section: 'Extensions',
269
+ };
270
+
271
+ if (explicitImplementations) c.addTraitFunction(fungibleCappedTrait, functions.cap);
272
+ else c.addTraitImplBlock(fungibleCappedTrait);
273
+
274
+ return capAbsolute;
275
+ }
276
+
277
+ function addMintable(c: ContractBuilder, access: Access, pausable: boolean, explicitImplementations: boolean) {
182
278
  switch (access) {
183
279
  case false:
184
280
  break;
185
281
  case 'ownable': {
186
- c.addFreeFunction(mintFn);
282
+ c.addFreeFunction(functions.mint);
187
283
 
188
- requireAccessControl(c, undefined, mintFn, access, undefined, explicitImplementations);
284
+ requireAccessControl(c, undefined, functions.mint, access, undefined, explicitImplementations);
189
285
 
190
286
  if (pausable) {
191
- c.addFunctionTag(mintFn, 'when_not_paused');
287
+ c.addFunctionTag(functions.mint, 'when_not_paused');
192
288
  }
193
289
  break;
194
290
  }
195
291
  case 'roles': {
196
- c.addFreeFunction(mintWithCallerFn);
292
+ c.addFreeFunction(functions.mint_with_caller);
197
293
 
198
294
  requireAccessControl(
199
295
  c,
200
296
  undefined,
201
- mintWithCallerFn,
297
+ functions.mint_with_caller,
202
298
  access,
203
299
  {
204
300
  useMacro: true,
@@ -209,7 +305,7 @@ function addMintable(
209
305
  );
210
306
 
211
307
  if (pausable) {
212
- c.addFunctionTag(mintWithCallerFn, 'when_not_paused');
308
+ c.addFunctionTag(functions.mint_with_caller, 'when_not_paused');
213
309
  }
214
310
  break;
215
311
  }
@@ -220,7 +316,7 @@ function addMintable(
220
316
  }
221
317
  }
222
318
 
223
- function addBurnable(c: ContractBuilder, votes: boolean, pausable: boolean, explicitImplementations: boolean) {
319
+ function addBurnable(c: ContractBuilder, pausable: boolean, explicitImplementations: boolean) {
224
320
  c.addUseClause('stellar_tokens::fungible', 'burnable::FungibleBurnable');
225
321
 
226
322
  const fungibleBurnableTrait = {
@@ -230,29 +326,34 @@ function addBurnable(c: ContractBuilder, votes: boolean, pausable: boolean, expl
230
326
  section: 'Extensions',
231
327
  };
232
328
 
233
- const burnFn = votes ? votesFunctions.burn : functions.burn;
234
- const burnFromFn = votes ? votesFunctions.burn_from : functions.burn_from;
235
-
236
- if (pausable || votes || explicitImplementations) {
329
+ if (pausable || explicitImplementations) {
237
330
  if (pausable) {
238
331
  c.addUseClause('stellar_macros', 'when_not_paused');
239
332
  }
240
333
 
241
- c.addTraitFunction(fungibleBurnableTrait, burnFn);
242
- c.addTraitFunction(fungibleBurnableTrait, burnFromFn);
334
+ c.addTraitFunction(fungibleBurnableTrait, functions.burn);
335
+ c.addTraitFunction(fungibleBurnableTrait, functions.burn_from);
243
336
 
244
337
  if (pausable) {
245
- c.addFunctionTag(burnFn, 'when_not_paused', fungibleBurnableTrait);
246
- c.addFunctionTag(burnFromFn, 'when_not_paused', fungibleBurnableTrait);
338
+ c.addFunctionTag(functions.burn, 'when_not_paused', fungibleBurnableTrait);
339
+ c.addFunctionTag(functions.burn_from, 'when_not_paused', fungibleBurnableTrait);
247
340
  }
248
341
  } else {
249
342
  c.addTraitImplBlock(fungibleBurnableTrait);
250
343
  }
251
344
  }
252
345
 
253
- export const premintPattern = /^(\d*\.?\d*)$/;
346
+ function validateI128(value: bigint, field: string) {
347
+ if (value > I128_MAX) {
348
+ throw new OptionsError({
349
+ [field]: 'Value is greater than i128 max value',
350
+ });
351
+ }
352
+ }
353
+
354
+ export const premintPattern = /^\d*(?:\.\d*)?$/;
254
355
 
255
- function addPremint(c: ContractBuilder, amount: string, decimals: bigint, votes: boolean) {
356
+ function addPremint(c: ContractBuilder, amount: string, decimals: bigint, capAbsolute?: bigint) {
256
357
  if (amount !== undefined && amount !== '0') {
257
358
  if (!premintPattern.test(amount)) {
258
359
  throw new OptionsError({
@@ -260,11 +361,19 @@ function addPremint(c: ContractBuilder, amount: string, decimals: bigint, votes:
260
361
  });
261
362
  }
262
363
 
263
- // TODO: handle signed int?
264
364
  const premintAbsolute = toUint(getInitialSupply(amount, Number(decimals)), 'premint', 'u128');
365
+ // `mint` takes an `i128`.
366
+ validateI128(premintAbsolute, 'premint');
367
+
368
+ if (capAbsolute !== undefined && premintAbsolute > capAbsolute) {
369
+ throw new OptionsError({
370
+ premint: 'Premint exceeds the cap',
371
+ cap: 'Cap is lower than the premint',
372
+ });
373
+ }
265
374
 
266
375
  c.addConstructorArgument({ name: 'recipient', type: 'Address' });
267
- c.addConstructorCode(`${votes ? 'FungibleVotes' : 'Base'}::mint(e, &recipient, ${premintAbsolute});`);
376
+ c.addConstructorCode(`<Self as FungibleToken>::ContractType::mint(e, &recipient, ${premintAbsolute});`);
268
377
  }
269
378
  }
270
379
 
@@ -273,15 +382,16 @@ function addPremint(c: ContractBuilder, amount: string, decimals: bigint, votes:
273
382
  *
274
383
  * @param premint Premint amount in token units, may be fractional
275
384
  * @param decimals The number of decimals in the token
385
+ * @param field The option reported in errors
276
386
  * @returns `premint` with zeros padded or removed based on `decimals`.
277
387
  * @throws OptionsError if `premint` has more than one decimal character or is more precise than allowed by the `decimals` argument.
278
388
  */
279
- export function getInitialSupply(premint: string, decimals: number): string {
389
+ export function getInitialSupply(premint: string, decimals: number, field = 'premint'): string {
280
390
  let result;
281
391
  const premintSegments = premint.split('.');
282
392
  if (premintSegments.length > 2) {
283
393
  throw new OptionsError({
284
- premint: 'Not a valid number',
394
+ [field]: 'Not a valid number',
285
395
  });
286
396
  } else {
287
397
  const firstSegment = premintSegments[0] ?? '';
@@ -292,12 +402,12 @@ export function getInitialSupply(premint: string, decimals: number): string {
292
402
  } catch {
293
403
  // .repeat gives an error if decimals number is too large
294
404
  throw new OptionsError({
295
- premint: 'Decimals number too large',
405
+ [field]: 'Decimals number too large',
296
406
  });
297
407
  }
298
408
  } else if (decimals < lastSegment.length) {
299
409
  throw new OptionsError({
300
- premint: 'Too many decimals',
410
+ [field]: 'Too many decimals',
301
411
  });
302
412
  }
303
413
  // concat segments without leading zeros
@@ -311,11 +421,6 @@ export function getInitialSupply(premint: string, decimals: number): string {
311
421
 
312
422
  export const functions = defineFunctions({
313
423
  // Token Functions
314
- total_supply: {
315
- args: [getSelfArg()],
316
- returns: 'i128',
317
- code: ['Self::ContractType::total_supply(e)'],
318
- },
319
424
  balance: {
320
425
  args: [getSelfArg(), { name: 'account', type: 'Address' }],
321
426
  returns: 'i128',
@@ -372,39 +477,19 @@ export const functions = defineFunctions({
372
477
  },
373
478
 
374
479
  // Extensions
375
- burn: {
376
- args: [getSelfArg(), { name: 'from', type: 'Address' }, { name: 'amount', type: 'i128' }],
377
- code: ['Base::burn(e, &from, amount)'],
378
- },
379
- burn_from: {
380
- args: [
381
- getSelfArg(),
382
- { name: 'spender', type: 'Address' },
383
- { name: 'from', type: 'Address' },
384
- { name: 'amount', type: 'i128' },
385
- ],
386
- code: ['Base::burn_from(e, &spender, &from, amount)'],
387
- },
388
- mint: {
389
- args: [getSelfArg(), { name: 'account', type: 'Address' }, { name: 'amount', type: 'i128' }],
390
- code: ['Base::mint(e, &account, amount);'],
480
+ total_supply: {
481
+ args: [getSelfArg()],
482
+ returns: 'i128',
483
+ code: ['Self::ContractType::total_supply(e)'],
391
484
  },
392
- mint_with_caller: {
393
- name: 'mint',
394
- args: [
395
- getSelfArg(),
396
- { name: 'account', type: 'Address' },
397
- { name: 'amount', type: 'i128' },
398
- { name: 'caller', type: 'Address' },
399
- ],
400
- code: ['Base::mint(e, &account, amount);'],
485
+ cap: {
486
+ args: [getSelfArg()],
487
+ returns: 'i128',
488
+ code: ['Capped::cap(e)'],
401
489
  },
402
- });
403
-
404
- const votesFunctions = defineFunctions({
405
490
  burn: {
406
491
  args: [getSelfArg(), { name: 'from', type: 'Address' }, { name: 'amount', type: 'i128' }],
407
- code: ['FungibleVotes::burn(e, &from, amount)'],
492
+ code: ['Self::ContractType::burn(e, &from, amount)'],
408
493
  },
409
494
  burn_from: {
410
495
  args: [
@@ -413,11 +498,12 @@ const votesFunctions = defineFunctions({
413
498
  { name: 'from', type: 'Address' },
414
499
  { name: 'amount', type: 'i128' },
415
500
  ],
416
- code: ['FungibleVotes::burn_from(e, &spender, &from, amount)'],
501
+ code: ['Self::ContractType::burn_from(e, &spender, &from, amount)'],
417
502
  },
503
+ // Minting goes through the contract type, which keeps the total supply, the cap and the voting units up to date.
418
504
  mint: {
419
505
  args: [getSelfArg(), { name: 'account', type: 'Address' }, { name: 'amount', type: 'i128' }],
420
- code: ['FungibleVotes::mint(e, &account, amount);'],
506
+ code: ['<Self as FungibleToken>::ContractType::mint(e, &account, amount);'],
421
507
  },
422
508
  mint_with_caller: {
423
509
  name: 'mint',
@@ -427,12 +513,11 @@ const votesFunctions = defineFunctions({
427
513
  { name: 'amount', type: 'i128' },
428
514
  { name: 'caller', type: 'Address' },
429
515
  ],
430
- code: ['FungibleVotes::mint(e, &account, amount);'],
516
+ code: ['<Self as FungibleToken>::ContractType::mint(e, &account, amount);'],
431
517
  },
432
518
  });
433
519
 
434
520
  const fungibleTokenTraitFunctions = pickKeys(functions, [
435
- 'total_supply',
436
521
  'balance',
437
522
  'allowance',
438
523
  'transfer',
@@ -11,6 +11,8 @@ const blueprint = {
11
11
  decimals: ['7', '18'],
12
12
  burnable: booleans,
13
13
  votes: booleans,
14
+ totalSupply: booleans,
15
+ cap: ['', '1000'],
14
16
  pausable: booleans,
15
17
  upgradeable: booleans,
16
18
  mintable: booleans,
@@ -15,6 +15,8 @@ const blueprint = {
15
15
  upgradeable: booleans,
16
16
  enumerable: booleans,
17
17
  consecutive: booleans,
18
+ royalties: booleans,
19
+ defaultRoyaltyBasisPoints: ['500'],
18
20
  sequential: booleans,
19
21
  mintable: booleans,
20
22
  access: accessOptions,
@@ -11,6 +11,8 @@ const blueprint = {
11
11
  decimals: ['7', '18'],
12
12
  burnable: booleans,
13
13
  votes: booleans,
14
+ totalSupply: booleans,
15
+ cap: ['', '1000'],
14
16
  pausable: booleans,
15
17
  upgradeable: booleans,
16
18
  mintable: booleans,
package/src/governor.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { CommonContractOptions } from './common-options';
2
2
  import { contractDefaults as commonDefaults, getSelfArg, withCommonContractDefaults } from './common-options';
3
3
  import { ContractBuilder } from './contract';
4
+ import { OptionsError } from './error';
4
5
  import { printContract } from './print';
5
6
  import { setAccessControl } from './set-access-control';
6
7
  import { addUpgradeable } from './add-upgradeable';
@@ -59,12 +60,20 @@ export function buildGovernor(opts: GovernorOptions): ContractBuilder {
59
60
  const allOpts = withDefaults(opts);
60
61
  const c = new ContractBuilder(allOpts.name);
61
62
 
63
+ // The library rejects a zero voting period, which would make the constructor panic on deploy.
64
+ const votingPeriod = toUint(allOpts.votingPeriod, 'votingPeriod', 'u32');
65
+ if (votingPeriod === 0n) {
66
+ throw new OptionsError({
67
+ votingPeriod: 'Voting period must be greater than 0',
68
+ });
69
+ }
70
+
62
71
  addBase(
63
72
  c,
64
73
  toByteArray(allOpts.name),
65
74
  toByteArray(allOpts.version),
66
75
  toUint(allOpts.votingDelay, 'votingDelay', 'u32'),
67
- toUint(allOpts.votingPeriod, 'votingPeriod', 'u32'),
76
+ votingPeriod,
68
77
  toUint(allOpts.proposalThreshold, 'proposalThreshold', 'u128'),
69
78
  toUint(allOpts.quorum, 'quorum', 'u128'),
70
79
  allOpts.timelock,