@openzeppelin/wizard-stellar 0.6.3 → 0.6.4

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/README.md +4 -0
  2. package/dist/account.d.ts +19 -0
  3. package/dist/account.d.ts.map +1 -0
  4. package/dist/account.js +251 -0
  5. package/dist/account.js.map +1 -0
  6. package/dist/add-upgradeable.d.ts +9 -0
  7. package/dist/add-upgradeable.d.ts.map +1 -1
  8. package/dist/add-upgradeable.js +25 -0
  9. package/dist/add-upgradeable.js.map +1 -1
  10. package/dist/api.d.ts +7 -0
  11. package/dist/api.d.ts.map +1 -1
  12. package/dist/api.js +6 -1
  13. package/dist/api.js.map +1 -1
  14. package/dist/build-generic.d.ts +4 -0
  15. package/dist/build-generic.d.ts.map +1 -1
  16. package/dist/build-generic.js +3 -0
  17. package/dist/build-generic.js.map +1 -1
  18. package/dist/contract.d.ts +21 -2
  19. package/dist/contract.d.ts.map +1 -1
  20. package/dist/contract.js +11 -0
  21. package/dist/contract.js.map +1 -1
  22. package/dist/generate/account.d.ts +3 -0
  23. package/dist/generate/account.d.ts.map +1 -0
  24. package/dist/generate/account.js +21 -0
  25. package/dist/generate/account.js.map +1 -0
  26. package/dist/generate/sources.d.ts.map +1 -1
  27. package/dist/generate/sources.js +6 -0
  28. package/dist/generate/sources.js.map +1 -1
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +8 -6
  32. package/dist/index.js.map +1 -1
  33. package/dist/kind.js +1 -0
  34. package/dist/kind.js.map +1 -1
  35. package/dist/print.js +39 -9
  36. package/dist/print.js.map +1 -1
  37. package/dist/utils/compile-test.d.ts.map +1 -1
  38. package/dist/utils/compile-test.js +1 -0
  39. package/dist/utils/compile-test.js.map +1 -1
  40. package/dist/zip-rust.d.ts.map +1 -1
  41. package/dist/zip-rust.js +14 -1
  42. package/dist/zip-rust.js.map +1 -1
  43. package/dist/zip-shared.d.ts +14 -0
  44. package/dist/zip-shared.d.ts.map +1 -1
  45. package/dist/zip-shared.js +212 -1
  46. package/dist/zip-shared.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/account.ts +319 -0
  49. package/src/add-upgradeable.ts +29 -0
  50. package/src/api.ts +12 -0
  51. package/src/build-generic.ts +6 -0
  52. package/src/contract.ts +24 -2
  53. package/src/generate/account.ts +21 -0
  54. package/src/generate/sources.ts +7 -0
  55. package/src/index.ts +2 -0
  56. package/src/kind.ts +1 -0
  57. package/src/print.ts +47 -18
  58. package/src/utils/compile-test.ts +1 -0
  59. package/src/zip-rust.ts +16 -4
  60. package/src/zip-shared.ts +231 -0
package/src/contract.ts CHANGED
@@ -35,7 +35,11 @@ export interface BaseTraitImplBlock {
35
35
  traitName: string;
36
36
  structName: string;
37
37
  tags: string[];
38
- assocType?: string;
38
+ /**
39
+ * Associated type bindings for the impl block. An array declares several of
40
+ * them, one per line (e.g. `CustomAccountInterface`'s `Error` and `Signature`).
41
+ */
42
+ assocType?: string | string[];
39
43
  section?: string;
40
44
  /**
41
45
  * Priority for which trait to print first.
@@ -70,6 +74,12 @@ export interface Variable {
70
74
  export interface Argument {
71
75
  name: string;
72
76
  type?: string;
77
+ /**
78
+ * Comment lines printed above the argument. Forces one argument per line, so
79
+ * only worth using where the caller needs guidance that the type cannot carry,
80
+ * such as where to obtain an address.
81
+ */
82
+ comment?: string[];
73
83
  }
74
84
 
75
85
  export class ContractBuilder implements Contract {
@@ -215,7 +225,7 @@ export class ContractBuilder implements Contract {
215
225
  return contractFn;
216
226
  }
217
227
 
218
- overrideAssocType(traitName: string, newAssocType: string): void {
228
+ overrideAssocType(traitName: string, newAssocType: string | string[]): void {
219
229
  const trait = this.implementedTraitsMap.get(traitName);
220
230
  if (trait) {
221
231
  trait.assocType = newAssocType;
@@ -274,6 +284,18 @@ export class ContractBuilder implements Contract {
274
284
  this.constructorCode.push(code);
275
285
  }
276
286
 
287
+ /**
288
+ * Appends a block of constructor code verbatim, keeping duplicate lines.
289
+ *
290
+ * `addConstructorCode` deduplicates, which is right for the idempotent setup
291
+ * statements each generator contributes, but wrong for a multi-line block
292
+ * where lines such as `}` legitimately repeat. Empty entries separate groups
293
+ * and are printed as blank lines.
294
+ */
295
+ addConstructorCodeBlock(lines: string[]): void {
296
+ this.constructorCode.push(...lines);
297
+ }
298
+
277
299
  addDerives(derive: string): void {
278
300
  this.derivesSet.add(derive);
279
301
  }
@@ -0,0 +1,21 @@
1
+ import type { AccountOptions } from '../account';
2
+ import { policyOptions } from '../account';
3
+ import { infoOptions } from '../set-info';
4
+ import { generateAlternatives } from './alternatives';
5
+
6
+ const booleans = [true, false];
7
+
8
+ const blueprint = {
9
+ name: ['MyAccount'],
10
+ delegatedSigners: booleans,
11
+ ed25519Signers: booleans,
12
+ webauthnSigners: booleans,
13
+ policy: policyOptions,
14
+ executionEntryPoint: booleans,
15
+ upgradeable: booleans,
16
+ info: infoOptions,
17
+ };
18
+
19
+ export function* generateAccountOptions(): Generator<Required<AccountOptions>> {
20
+ yield* generateAlternatives(blueprint);
21
+ }
@@ -2,6 +2,7 @@ import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
3
  import crypto from 'crypto';
4
4
 
5
+ import { generateAccountOptions } from './account';
5
6
  import { generateFungibleOptions } from './fungible';
6
7
  import { generateGovernorOptions } from './governor';
7
8
  import { generateNonFungibleOptions } from './non-fungible';
@@ -16,6 +17,12 @@ import type { Contract } from '../contract';
16
17
  type Kind = keyof KindedOptions;
17
18
 
18
19
  export function* generateOptions(kind?: Kind): Generator<GenericOptions> {
20
+ if (!kind || kind === 'Account') {
21
+ for (const kindOpts of generateAccountOptions()) {
22
+ yield { kind: 'Account', ...kindOpts };
23
+ }
24
+ }
25
+
19
26
  if (!kind || kind === 'Fungible') {
20
27
  for (const kindOpts of generateFungibleOptions()) {
21
28
  yield { kind: 'Fungible', ...kindOpts };
package/src/index.ts CHANGED
@@ -21,12 +21,14 @@ export { sanitizeKind } from './kind';
21
21
 
22
22
  export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version';
23
23
 
24
+ export { account } from './api';
24
25
  export { fungible } from './api';
25
26
  export { governor } from './api';
26
27
  export { nonFungible } from './api';
27
28
  export { stablecoin } from './api';
28
29
  export { vault } from './api';
29
30
 
31
+ export type { AccountOptions, Policy } from './account';
30
32
  export type { FungibleOptions } from './fungible';
31
33
  export type { GovernorOptions } from './governor';
32
34
  export type { NonFungibleOptions } from './non-fungible';
package/src/kind.ts CHANGED
@@ -14,6 +14,7 @@ export function sanitizeKind(kind: unknown): Kind {
14
14
 
15
15
  function isKind<T>(value: Kind | T): value is Kind {
16
16
  switch (value) {
17
+ case 'Account':
17
18
  case 'Fungible':
18
19
  case 'Governor':
19
20
  case 'NonFungible':
package/src/print.ts CHANGED
@@ -64,8 +64,17 @@ function printContractErrors(contract: Contract): Lines[] {
64
64
  ];
65
65
  }
66
66
 
67
+ // Lines that open, close or continue a block or expression are not statements,
68
+ // so they must not be terminated. Needed by multi-line code such as a `for` loop
69
+ // or a call whose arguments are spread over several lines.
70
+ const CONTINUATION_LINE_ENDINGS = ['{', '}', ';', ',', '('];
71
+
72
+ function needsSemicolon(line: string): boolean {
73
+ return line.length > 0 && !CONTINUATION_LINE_ENDINGS.includes(line.at(-1)!);
74
+ }
75
+
67
76
  function withSemicolons(lines: string[]): string[] {
68
- return lines.map(line => (line.endsWith(';') ? line : line + ';'));
77
+ return lines.map(line => (needsSemicolon(line) ? line + ';' : line));
69
78
  }
70
79
 
71
80
  function printVariables(contract: Contract): string[] {
@@ -208,8 +217,11 @@ function printImplementedTrait(trait: TraitImplBlock): Lines[] {
208
217
 
209
218
  const head = `impl ${trait.traitName} for ${trait.structName}`;
210
219
 
211
- const assocTypeLines =
212
- trait.assocType !== undefined && trait.assocType.trim().length > 0 ? [TAB + trait.assocType.trim(), ''] : [];
220
+ const assocTypes = (Array.isArray(trait.assocType) ? trait.assocType : [trait.assocType ?? ''])
221
+ .map(assocType => assocType.trim())
222
+ .filter(assocType => assocType.length > 0);
223
+
224
+ const assocTypeLines = assocTypes.length > 0 ? [...assocTypes.map(assocType => TAB + assocType), ''] : [];
213
225
 
214
226
  const functionLines = trait.functions.map(fn => printFunction(fn));
215
227
 
@@ -234,14 +246,14 @@ function printImplementedTrait(trait: TraitImplBlock): Lines[] {
234
246
 
235
247
  function printFunction(fn: ContractFunction): Lines[] {
236
248
  const head = `fn ${fn.name}`;
237
- const args = fn.args.map(a => printArgument(a));
249
+ const args = printArguments(fn.args);
238
250
 
239
251
  const codeLines = fn.codeBefore?.concat(fn.code) ?? fn.code;
240
252
  for (let i = 0; i < codeLines.length; i++) {
241
253
  const line = codeLines[i];
242
254
  const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined;
243
255
  if (line !== undefined && line.length > 0) {
244
- if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) {
256
+ if (shouldEndWithSemicolon && needsSemicolon(line)) {
245
257
  codeLines[i] += ';';
246
258
  } else if (!shouldEndWithSemicolon && line.endsWith(';')) {
247
259
  codeLines[i] = line.slice(0, line.length - 1);
@@ -284,17 +296,23 @@ function printConstructor(contract: Contract): Lines[] {
284
296
  const head = 'fn __constructor';
285
297
  const args = [getSelfArg(), ...contract.constructorArgs];
286
298
 
287
- const body = spaceBetween(withSemicolons(contract.constructorCode));
288
-
289
- const constructor = printFunction2(
290
- true,
291
- head,
292
- args.map(a => printArgument(a)),
293
- [],
294
- undefined,
295
- undefined,
296
- body,
299
+ // Empty entries separate logical groups of statements. Routing them through
300
+ // `spaceBetween` yields blank lines with no trailing indentation.
301
+ const groups = contract.constructorCode.reduce<string[][]>(
302
+ (acc, line) => {
303
+ if (line.length === 0) {
304
+ acc.push([]);
305
+ } else {
306
+ acc[acc.length - 1]!.push(line);
307
+ }
308
+ return acc;
309
+ },
310
+ [[]],
297
311
  );
312
+
313
+ const body = spaceBetween(...groups.map(group => withSemicolons(group)));
314
+
315
+ const constructor = printFunction2(true, head, printArguments(args), [], undefined, undefined, body);
298
316
  return constructor;
299
317
  } else {
300
318
  return [];
@@ -328,11 +346,12 @@ function printFunction2(
328
346
 
329
347
  if (args.length > 0) {
330
348
  const formattedArgs = args.join(', ');
331
- if (formattedArgs.length > 80) {
349
+ // Commented arguments cannot share a line, so they force the expanded form.
350
+ if (formattedArgs.length > 80 || args.some(isComment)) {
332
351
  fn.push(accum);
333
352
  accum = '';
334
353
  // print each arg in a separate line
335
- fn.push(args.map(arg => `${arg},`));
354
+ fn.push(args.map(arg => (isComment(arg) ? arg : `${arg},`)));
336
355
  } else {
337
356
  accum += `${formattedArgs}`;
338
357
  }
@@ -364,13 +383,23 @@ function printArgument(arg: Argument): string {
364
383
  }
365
384
  }
366
385
 
386
+ // Expands each argument into its comment lines followed by the argument itself.
387
+ // `printFunction2` recognizes the comment entries and leaves them untouched.
388
+ function printArguments(args: Argument[]): string[] {
389
+ return args.flatMap(arg => [...(arg.comment ?? []).map(line => `// ${line}`), printArgument(arg)]);
390
+ }
391
+
392
+ function isComment(line: string): boolean {
393
+ return line.startsWith('//');
394
+ }
395
+
367
396
  function printTopLevelDeclarations(contract: Contract): Lines[] {
368
397
  if (contract.topLevelDeclarations.length === 0) return [];
369
398
  return spaceBetween(...contract.topLevelDeclarations);
370
399
  }
371
400
 
372
401
  function printDocumentations(documentations: string[]): string[] {
373
- return documentations.map(documentation => `//! ${documentation}`);
402
+ return documentations.map(documentation => (documentation.length > 0 ? `//! ${documentation}` : '//!'));
374
403
  }
375
404
 
376
405
  function printMetadata(contract: Contract) {
@@ -27,6 +27,7 @@ const toCargoPath = (inputPath: string) => inputPath.replace(/\\/g, '/');
27
27
  const localDependencies = [
28
28
  ['stellar-tokens', 'packages/tokens'],
29
29
  ['stellar-access', 'packages/access'],
30
+ ['stellar-accounts', 'packages/accounts'],
30
31
  ['stellar-contract-utils', 'packages/contract-utils'],
31
32
  ['stellar-governance', 'packages/governance'],
32
33
  ['stellar-macros', 'packages/macros'],
package/src/zip-rust.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import JSZip from 'jszip';
2
+ import { defaults as accountDefaults } from './account';
2
3
  import type { GenericOptions } from './build-generic';
3
4
  import type { Contract } from './contract';
4
5
  import { printContract, removeCreateLevelAttributes } from './print';
@@ -6,6 +7,7 @@ import {
6
7
  contractOptionsToContractName,
7
8
  createRustLibFile,
8
9
  getRequiredLibDependencies,
10
+ printAccountRustTest,
9
11
  printContractCargo,
10
12
  printRustNameTest,
11
13
  printVaultRustTest,
@@ -31,6 +33,19 @@ const gitIgnore = `\
31
33
  target/
32
34
  `;
33
35
 
36
+ // Most contracts get the generic name assertion; kinds whose constructor takes
37
+ // more than plain addresses need a template that can satisfy it.
38
+ const printTest = (c: Contract, opts: GenericOptions) => {
39
+ switch (opts?.kind) {
40
+ case 'Account':
41
+ return printAccountRustTest(c, opts.policy ?? accountDefaults.policy);
42
+ case 'Vault':
43
+ return printVaultRustTest(c);
44
+ default:
45
+ return printRustNameTest(c);
46
+ }
47
+ };
48
+
34
49
  export const createRustZipEnvironment = (c: Contract, opts: GenericOptions) => {
35
50
  const zip = new JSZip();
36
51
 
@@ -38,10 +53,7 @@ export const createRustZipEnvironment = (c: Contract, opts: GenericOptions) => {
38
53
  const requiredLibDeps = getRequiredLibDependencies(c);
39
54
 
40
55
  zip.file(`contracts/${contractName}/src/contract.rs`, removeCreateLevelAttributes(printContract(c)));
41
- zip.file(
42
- `contracts/${contractName}/src/test.rs`,
43
- opts?.kind === 'Vault' ? printVaultRustTest(c) : printRustNameTest(c),
44
- );
56
+ zip.file(`contracts/${contractName}/src/test.rs`, printTest(c, opts));
45
57
  zip.file(`contracts/${contractName}/src/lib.rs`, createRustLibFile);
46
58
  zip.file(`contracts/${contractName}/Cargo.toml`, printContractCargo(contractName, requiredLibDeps));
47
59
  zip.file('Cargo.toml', workspaceCargo(requiredLibDeps));
package/src/zip-shared.ts CHANGED
@@ -101,9 +101,240 @@ fn initial_state() {
101
101
  `;
102
102
  };
103
103
 
104
+ // A smart account contacts its policy and verifier contracts while it is being
105
+ // constructed: `add_context_rule` installs every policy and canonicalizes every
106
+ // external signer key through its verifier. A test that registers the account
107
+ // therefore needs real contracts at those addresses, so the mocks below wrap the
108
+ // library's own logic, the same way the reference examples do.
109
+ const accountPolicies = {
110
+ 'simple-threshold': {
111
+ struct: 'MockSimpleThresholdPolicy',
112
+ module: 'simple_threshold',
113
+ params: 'SimpleThresholdAccountParams',
114
+ },
115
+ 'weighted-threshold': {
116
+ struct: 'MockWeightedThresholdPolicy',
117
+ module: 'weighted_threshold',
118
+ params: 'WeightedThresholdAccountParams',
119
+ },
120
+ } as const;
121
+
122
+ export type AccountPolicy = keyof typeof accountPolicies | false;
123
+
124
+ // How many of each signer type the generated test deploys with. `weights` must
125
+ // have one entry per assembled signer, so the counts drive that too.
126
+ const testSignerCounts = {
127
+ delegated_signers: 2,
128
+ ed25519_keys: 1,
129
+ webauthn_keys: 1,
130
+ } as const;
131
+
132
+ const testConstructorArgValues: Record<string, (signerCount: number) => string> = {
133
+ delegated_signers: () => 'Vec::from_array(&env, [Address::generate(&env), Address::generate(&env)])',
134
+ ed25519_verifier: () => 'ed25519_verifier',
135
+ ed25519_keys: () => 'Vec::from_array(&env, [Bytes::from_slice(&env, &[1u8; 32])])',
136
+ webauthn_verifier: () => 'webauthn_verifier',
137
+ // A WebAuthn key is the 65-byte secp256r1 public key followed by an optional
138
+ // credential id; canonicalization reads the first 65 bytes.
139
+ webauthn_keys: () => 'Vec::from_array(&env, [Bytes::from_slice(&env, &[2u8; 65])])',
140
+ weights: signerCount => `Vec::from_array(&env, [${Array<string>(signerCount).fill('1u32').join(', ')}])`,
141
+ threshold: () => '1u32',
142
+ threshold_policy: () => 'policy',
143
+ };
144
+
145
+ const MOCK_ED25519_VERIFIER = `
146
+ // Minimal Ed25519 verifier contract, for testing purposes only: a real
147
+ // deployment passes the address of a verifier contract it trusts.
148
+ #[contract]
149
+ pub struct MockEd25519Verifier;
150
+
151
+ #[contractimpl]
152
+ impl Verifier for MockEd25519Verifier {
153
+ type KeyData = BytesN<32>;
154
+ type SigData = BytesN<64>;
155
+
156
+ fn verify(
157
+ e: &Env,
158
+ signature_payload: Bytes,
159
+ key_data: BytesN<32>,
160
+ sig_data: BytesN<64>,
161
+ ) -> bool {
162
+ ed25519::verify(e, &signature_payload, &key_data, &sig_data)
163
+ }
164
+
165
+ fn canonicalize_key(e: &Env, key_data: BytesN<32>) -> Bytes {
166
+ ed25519::canonicalize_key(e, &key_data)
167
+ }
168
+
169
+ fn batch_canonicalize_key(e: &Env, keys_data: Vec<BytesN<32>>) -> Vec<Bytes> {
170
+ ed25519::batch_canonicalize_key(e, &keys_data)
171
+ }
172
+ }
173
+ `;
174
+
175
+ const MOCK_WEBAUTHN_VERIFIER = `
176
+ // Minimal WebAuthn (passkey) verifier contract, for testing purposes only: a
177
+ // real deployment passes the address of a verifier contract it trusts.
178
+ #[contract]
179
+ pub struct MockWebauthnVerifier;
180
+
181
+ #[contractimpl]
182
+ impl Verifier for MockWebauthnVerifier {
183
+ type KeyData = Bytes;
184
+ type SigData = Bytes;
185
+
186
+ fn verify(e: &Env, signature_payload: Bytes, key_data: Bytes, sig_data: Bytes) -> bool {
187
+ let sig_struct =
188
+ WebAuthnSigData::from_xdr(e, &sig_data).expect("WebAuthnSigData with correct format");
189
+ let pub_key: BytesN<65> =
190
+ extract_from_bytes(e, &key_data, 0..65).expect("65-byte public key to be extracted");
191
+
192
+ webauthn::verify(e, &signature_payload, &pub_key, &sig_struct)
193
+ }
194
+
195
+ fn canonicalize_key(e: &Env, key_data: Bytes) -> Bytes {
196
+ webauthn::canonicalize_key(e, &key_data)
197
+ }
198
+
199
+ fn batch_canonicalize_key(e: &Env, keys_data: Vec<Bytes>) -> Vec<Bytes> {
200
+ webauthn::batch_canonicalize_key(e, &keys_data)
201
+ }
202
+ }
203
+ `;
204
+
205
+ const printMockPolicyContract = (policy: keyof typeof accountPolicies) => {
206
+ const { struct, module, params } = accountPolicies[policy];
207
+
208
+ return `
209
+ // Minimal policy contract, for testing purposes only: the account installs its
210
+ // policies during construction, so the test needs one deployed at that address.
211
+ // A real deployment passes the address of a policy contract it trusts.
212
+ #[contract]
213
+ pub struct ${struct};
214
+
215
+ #[contractimpl]
216
+ impl Policy for ${struct} {
217
+ type AccountParams = ${module}::${params};
218
+
219
+ fn enforce(
220
+ e: &Env,
221
+ context: Context,
222
+ authenticated_signers: Vec<Signer>,
223
+ context_rule: ContextRule,
224
+ smart_account: Address,
225
+ ) {
226
+ ${module}::enforce(e, &context, &authenticated_signers, &context_rule, &smart_account)
227
+ }
228
+
229
+ fn install(
230
+ e: &Env,
231
+ install_params: Self::AccountParams,
232
+ context_rule: ContextRule,
233
+ smart_account: Address,
234
+ ) {
235
+ ${module}::install(e, &install_params, &context_rule, &smart_account)
236
+ }
237
+
238
+ fn uninstall(e: &Env, context_rule: ContextRule, smart_account: Address) {
239
+ ${module}::uninstall(e, &context_rule, &smart_account)
240
+ }
241
+ }
242
+ `;
243
+ };
244
+
245
+ export const printAccountRustTest = (c: Pick<Contract, 'constructorArgs' | 'name'>, policy: AccountPolicy) => {
246
+ const argNames = (c.constructorArgs || []).map(arg => arg.name);
247
+ const signerCount = Object.entries(testSignerCounts).reduce(
248
+ (total, [argName, count]) => (argNames.includes(argName) ? total + count : total),
249
+ 0,
250
+ );
251
+
252
+ const hasEd25519 = argNames.includes('ed25519_keys');
253
+ const hasWebauthn = argNames.includes('webauthn_keys');
254
+ const hasPolicy = policy !== false;
255
+
256
+ // Every argument is spelled on its own line: the assembled tuple is otherwise
257
+ // far too long to read.
258
+ const registerArgs = argNames
259
+ .map(argName => ` ${testConstructorArgValues[argName]!(signerCount)},`)
260
+ .join('\n');
261
+
262
+ const hasMocks = hasEd25519 || hasWebauthn || hasPolicy;
263
+
264
+ const sorobanImports = [
265
+ ...(hasPolicy ? ['auth::Context'] : []),
266
+ ...(hasMocks ? ['contract', 'contractimpl'] : []),
267
+ 'testutils::Address as _',
268
+ ...(hasWebauthn ? ['xdr::FromXdr'] : []),
269
+ 'Address',
270
+ ...(hasEd25519 || hasWebauthn ? ['Bytes', 'BytesN'] : []),
271
+ 'Env',
272
+ 'Vec',
273
+ ];
274
+
275
+ // Kept on separate lines so that enabling both verifiers does not import
276
+ // `Verifier` twice.
277
+ const libImports = [
278
+ ...(hasPolicy
279
+ ? [
280
+ `use stellar_accounts::policies::{ ${accountPolicies[policy].module}, Policy };`,
281
+ 'use stellar_accounts::smart_account::{ ContextRule, Signer };',
282
+ ]
283
+ : []),
284
+ ...(hasEd25519 || hasWebauthn ? ['use stellar_accounts::verifiers::Verifier;'] : []),
285
+ ...(hasEd25519 ? ['use stellar_accounts::verifiers::ed25519;'] : []),
286
+ ...(hasWebauthn
287
+ ? [
288
+ 'use stellar_accounts::verifiers::utils::extract_from_bytes;',
289
+ 'use stellar_accounts::verifiers::webauthn::{ self, WebAuthnSigData };',
290
+ ]
291
+ : []),
292
+ ];
293
+
294
+ const mockContracts = [
295
+ ...(hasEd25519 ? [MOCK_ED25519_VERIFIER] : []),
296
+ ...(hasWebauthn ? [MOCK_WEBAUTHN_VERIFIER] : []),
297
+ ...(hasPolicy ? [printMockPolicyContract(policy)] : []),
298
+ ];
299
+
300
+ const registrations = [
301
+ ...(hasEd25519 ? [' let ed25519_verifier = env.register(MockEd25519Verifier, ());'] : []),
302
+ ...(hasWebauthn ? [' let webauthn_verifier = env.register(MockWebauthnVerifier, ());'] : []),
303
+ ...(hasPolicy ? [` let policy = env.register(${accountPolicies[policy].struct}, ());`] : []),
304
+ ];
305
+
306
+ return `#![cfg(test)]
307
+
308
+ extern crate std;
309
+
310
+ use soroban_sdk::{ ${sorobanImports.join(', ')} };
311
+ ${libImports.map(libImport => `${libImport}\n`).join('')}
312
+ use crate::contract::{ ${c.name}, ${c.name}Client };
313
+ ${mockContracts.join('')}
314
+ #[test]
315
+ fn initial_state() {
316
+ let env = Env::default();
317
+ ${hasPolicy ? ' env.mock_all_auths();\n' : ''}
318
+ ${registrations.map(registration => `${registration}\n`).join('')}
319
+ let contract_addr = env.register(
320
+ ${c.name},
321
+ (
322
+ ${registerArgs}
323
+ ),
324
+ );
325
+ let client = ${c.name}Client::new(&env, &contract_addr);
326
+
327
+ assert_eq!(client.get_context_rules_count(), 1);
328
+ }
329
+
330
+ // Add more tests below
331
+ `;
332
+ };
333
+
104
334
  const libDependencies = [
105
335
  'stellar-tokens',
106
336
  'stellar-access',
337
+ 'stellar-accounts',
107
338
  'stellar-contract-utils',
108
339
  'stellar-governance',
109
340
  'stellar-macros',