@openzeppelin/wizard-stellar 0.2.2 → 0.3.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 (93) hide show
  1. package/dist/add-pausable.d.ts.map +1 -1
  2. package/dist/add-pausable.js +9 -4
  3. package/dist/add-pausable.js.map +1 -1
  4. package/dist/add-upgradeable.d.ts.map +1 -1
  5. package/dist/add-upgradeable.js +6 -2
  6. package/dist/add-upgradeable.js.map +1 -1
  7. package/dist/api.d.ts +5 -2
  8. package/dist/api.d.ts.map +1 -1
  9. package/dist/api.js +9 -1
  10. package/dist/api.js.map +1 -1
  11. package/dist/build-generic.d.ts +5 -1
  12. package/dist/build-generic.d.ts.map +1 -1
  13. package/dist/build-generic.js +3 -0
  14. package/dist/build-generic.js.map +1 -1
  15. package/dist/contract.d.ts +7 -1
  16. package/dist/contract.d.ts.map +1 -1
  17. package/dist/contract.js +17 -1
  18. package/dist/contract.js.map +1 -1
  19. package/dist/fungible.d.ts +5 -2
  20. package/dist/fungible.d.ts.map +1 -1
  21. package/dist/fungible.js +80 -50
  22. package/dist/fungible.js.map +1 -1
  23. package/dist/generate/sources.d.ts.map +1 -1
  24. package/dist/generate/sources.js +6 -0
  25. package/dist/generate/sources.js.map +1 -1
  26. package/dist/generate/stablecoin.d.ts +3 -0
  27. package/dist/generate/stablecoin.d.ts.map +1 -0
  28. package/dist/generate/stablecoin.js +24 -0
  29. package/dist/generate/stablecoin.js.map +1 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/kind.js +1 -0
  35. package/dist/kind.js.map +1 -1
  36. package/dist/non-fungible.d.ts +1 -0
  37. package/dist/non-fungible.d.ts.map +1 -1
  38. package/dist/non-fungible.js +91 -71
  39. package/dist/non-fungible.js.map +1 -1
  40. package/dist/print.d.ts.map +1 -1
  41. package/dist/print.js +13 -5
  42. package/dist/print.js.map +1 -1
  43. package/dist/set-access-control.d.ts +7 -4
  44. package/dist/set-access-control.d.ts.map +1 -1
  45. package/dist/set-access-control.js +57 -15
  46. package/dist/set-access-control.js.map +1 -1
  47. package/dist/set-info.d.ts +2 -0
  48. package/dist/set-info.d.ts.map +1 -1
  49. package/dist/set-info.js +6 -2
  50. package/dist/set-info.js.map +1 -1
  51. package/dist/stablecoin.d.ts +11 -0
  52. package/dist/stablecoin.d.ts.map +1 -0
  53. package/dist/stablecoin.js +103 -0
  54. package/dist/stablecoin.js.map +1 -0
  55. package/dist/utils/define-functions.d.ts +4 -2
  56. package/dist/utils/define-functions.d.ts.map +1 -1
  57. package/dist/utils/define-functions.js +7 -1
  58. package/dist/utils/define-functions.js.map +1 -1
  59. package/dist/utils/version.d.ts +3 -3
  60. package/dist/utils/version.js +2 -2
  61. package/dist/zip-rust.d.ts +5 -0
  62. package/dist/zip-rust.d.ts.map +1 -0
  63. package/dist/zip-rust.js +49 -0
  64. package/dist/zip-rust.js.map +1 -0
  65. package/dist/zip-scaffold.d.ts +0 -3
  66. package/dist/zip-scaffold.d.ts.map +1 -1
  67. package/dist/zip-scaffold.js +9 -85
  68. package/dist/zip-scaffold.js.map +1 -1
  69. package/dist/zip-shared.d.ts +26 -0
  70. package/dist/zip-shared.d.ts.map +1 -0
  71. package/dist/zip-shared.js +79 -0
  72. package/dist/zip-shared.js.map +1 -0
  73. package/package.json +1 -1
  74. package/src/add-pausable.ts +9 -4
  75. package/src/add-upgradeable.ts +6 -2
  76. package/src/api.ts +27 -4
  77. package/src/build-generic.ts +6 -0
  78. package/src/contract.ts +22 -2
  79. package/src/fungible.ts +84 -56
  80. package/src/generate/sources.ts +7 -0
  81. package/src/generate/stablecoin.ts +23 -0
  82. package/src/index.ts +2 -0
  83. package/src/kind.ts +1 -0
  84. package/src/non-fungible.ts +98 -77
  85. package/src/print.ts +15 -5
  86. package/src/set-access-control.ts +72 -19
  87. package/src/set-info.ts +8 -1
  88. package/src/stablecoin.ts +124 -0
  89. package/src/utils/define-functions.ts +11 -4
  90. package/src/utils/version.ts +2 -2
  91. package/src/zip-rust.ts +57 -0
  92. package/src/zip-scaffold.ts +16 -91
  93. package/src/zip-shared.ts +85 -0
@@ -1,9 +1,14 @@
1
1
  import type { BaseFunction, BaseTraitImplBlock, ContractBuilder } from './contract';
2
2
 
3
- export const accessOptions = [false, 'ownable'] as const;
3
+ export const accessOptions = [false, 'ownable', 'roles'] as const;
4
4
  export const DEFAULT_ACCESS_CONTROL = 'ownable';
5
5
 
6
6
  export type Access = (typeof accessOptions)[number];
7
+ export type AccessProps = {
8
+ useMacro: boolean;
9
+ caller?: string;
10
+ role?: string;
11
+ };
7
12
 
8
13
  /**
9
14
  * Sets access control for the contract via constructor args.
@@ -15,14 +20,40 @@ export function setAccessControl(c: ContractBuilder, access: Access): void {
15
20
  case 'ownable': {
16
21
  if (!c.ownable) {
17
22
  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")` });
23
+ c.addUseClause('soroban_sdk', 'Address');
24
+ c.addUseClause('stellar_ownable', 'self', { alias: 'ownable' });
25
+ c.addUseClause('stellar_ownable', 'Ownable');
26
+
27
+ const ownableTrait = {
28
+ traitName: 'Ownable',
29
+ structName: c.name,
30
+ tags: ['default_impl', 'contractimpl'],
31
+ section: 'Utils',
32
+ };
33
+ c.addTraitImplBlock(ownableTrait);
34
+
21
35
  c.addConstructorArgument({ name: 'owner', type: 'Address' });
22
- c.addConstructorCode('e.storage().instance().set(&OWNER, &owner);');
36
+ c.addConstructorCode('ownable::set_owner(e, &owner);');
23
37
  }
24
38
  break;
25
39
  }
40
+ case 'roles': {
41
+ c.addUseClause('soroban_sdk', 'Address');
42
+ c.addUseClause('stellar_access_control', 'self', { alias: 'access_control' });
43
+ c.addUseClause('stellar_access_control', 'AccessControl');
44
+
45
+ const accessControltrait = {
46
+ traitName: 'AccessControl',
47
+ structName: c.name,
48
+ tags: ['default_impl', 'contractimpl'],
49
+ section: 'Utils',
50
+ };
51
+ c.addTraitImplBlock(accessControltrait);
52
+
53
+ c.addConstructorArgument({ name: 'admin', type: 'Address' });
54
+ c.addConstructorCode('access_control::set_admin(e, &admin);');
55
+ break;
56
+ }
26
57
  default: {
27
58
  const _: never = access;
28
59
  throw new Error('Unknown value for `access`');
@@ -32,15 +63,13 @@ export function setAccessControl(c: ContractBuilder, access: Access): void {
32
63
 
33
64
  /**
34
65
  * 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
66
  */
38
67
  export function requireAccessControl(
39
68
  c: ContractBuilder,
40
69
  trait: BaseTraitImplBlock | undefined,
41
70
  fn: BaseFunction,
42
71
  access: Access,
43
- caller?: string,
72
+ accessProps: AccessProps = { useMacro: true },
44
73
  ): void {
45
74
  if (access === false) {
46
75
  access = DEFAULT_ACCESS_CONTROL;
@@ -49,19 +78,43 @@ export function requireAccessControl(
49
78
 
50
79
  switch (access) {
51
80
  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(
58
- fn,
59
- [getOwner, `if owner != ${caller} {`, ` panic_with_error!(e, ${c.name}Error::Unauthorized)`, '}'],
60
- trait,
61
- );
81
+ c.addUseClause('stellar_ownable_macro', 'only_owner');
82
+
83
+ if (accessProps.useMacro) {
84
+ c.addFunctionTag(fn, 'only_owner', trait);
62
85
  } else {
63
- c.addFunctionCodeBefore(fn, [getOwner, 'owner.require_auth();'], trait);
86
+ c.addFunctionCodeBefore(fn, [`ownable::enforce_owner_auth(e);`], trait);
64
87
  }
88
+
89
+ break;
90
+ }
91
+ case 'roles': {
92
+ const { useMacro, caller, role } = accessProps;
93
+
94
+ if (caller && role) {
95
+ c.addUseClause('soroban_sdk', 'Symbol');
96
+ c.addConstructorArgument({ name: role, type: 'Address' });
97
+ c.addConstructorCode(`access_control::grant_role_no_auth(e, &admin, &${role}, &Symbol::new(e, "${role}"));`);
98
+
99
+ if (useMacro) {
100
+ c.addUseClause('stellar_access_control_macros', 'only_role');
101
+ c.addFunctionTag(fn, `only_role(${caller}, "${role}")`, trait);
102
+ } else {
103
+ c.addFunctionCodeBefore(
104
+ fn,
105
+ [`access_control::ensure_role(e, ${caller}, &Symbol::new(e, "${role}"));`],
106
+ trait,
107
+ );
108
+ }
109
+ } else {
110
+ if (useMacro) {
111
+ c.addUseClause('stellar_access_control_macros', 'only_admin');
112
+ c.addFunctionTag(fn, 'only_admin', trait);
113
+ } else {
114
+ c.addFunctionCodeBefore(fn, ['access_control::enforce_admin_auth(e);'], trait);
115
+ }
116
+ }
117
+
65
118
  break;
66
119
  }
67
120
  default: {
package/src/set-info.ts CHANGED
@@ -1,15 +1,22 @@
1
1
  import type { ContractBuilder } from './contract';
2
2
 
3
+ export const TAG_SECURITY_CONTACT = `@custom:security-contact`;
4
+
3
5
  export const infoOptions = [{}, { license: 'WTFPL' }] as const;
4
6
 
5
7
  export const defaults: Info = { license: 'MIT' };
6
8
 
7
9
  export type Info = {
8
10
  license?: string;
11
+ securityContact?: string;
9
12
  };
10
13
 
11
14
  export function setInfo(c: ContractBuilder, info: Info): void {
12
- const { license } = info;
15
+ const { securityContact, license } = info;
16
+
17
+ if (securityContact) {
18
+ c.addSecurityTag(securityContact);
19
+ }
13
20
 
14
21
  if (license) {
15
22
  c.license = license;
@@ -0,0 +1,124 @@
1
+ import type { Contract, ContractBuilder } from './contract';
2
+ import { defineFunctions } from './utils/define-functions';
3
+ import { getSelfArg } from './common-options';
4
+ import { printContract } from './print';
5
+ import type { FungibleOptions } from './fungible';
6
+ import {
7
+ buildFungible,
8
+ defaults as fungibleDefaults,
9
+ withDefaults as withFungibleDefaults,
10
+ functions as fungibleFunctions,
11
+ isAccessControlRequired as fungibleIsAccessControlRequired,
12
+ } from './fungible';
13
+ import { requireAccessControl, type Access } from './set-access-control';
14
+
15
+ export const defaults: Required<StablecoinOptions> = {
16
+ ...fungibleDefaults,
17
+ name: 'MyStablecoin',
18
+ symbol: 'MST',
19
+ limitations: false,
20
+ } as const;
21
+
22
+ export const limitationsOptions = [false, 'allowlist', 'blocklist'] as const;
23
+
24
+ export function printStablecoin(opts: StablecoinOptions = defaults): string {
25
+ return printContract(buildStablecoin(opts));
26
+ }
27
+
28
+ export interface StablecoinOptions extends FungibleOptions {
29
+ limitations?: false | 'allowlist' | 'blocklist';
30
+ }
31
+
32
+ function withDefaults(opts: StablecoinOptions): Required<StablecoinOptions> {
33
+ return {
34
+ ...withFungibleDefaults(opts),
35
+ name: opts.name ?? defaults.name,
36
+ symbol: opts.symbol ?? defaults.symbol,
37
+ limitations: opts.limitations ?? defaults.limitations,
38
+ };
39
+ }
40
+
41
+ export function isAccessControlRequired(opts: Partial<StablecoinOptions>): boolean {
42
+ return fungibleIsAccessControlRequired(opts) || opts.limitations !== false;
43
+ }
44
+
45
+ export function buildStablecoin(opts: StablecoinOptions): Contract {
46
+ const allOpts = withDefaults(opts);
47
+
48
+ const c = buildFungible(allOpts);
49
+
50
+ if (allOpts.limitations) {
51
+ addLimitations(c, allOpts.access, allOpts.limitations);
52
+ }
53
+
54
+ return c;
55
+ }
56
+
57
+ function addLimitations(c: ContractBuilder, access: Access, mode: 'allowlist' | 'blocklist') {
58
+ const type = mode === 'allowlist';
59
+
60
+ const limitationsTrait = {
61
+ traitName: type ? 'FungibleAllowList' : 'FungibleBlockList',
62
+ structName: c.name,
63
+ tags: ['contractimpl'],
64
+ section: 'Extensions',
65
+ };
66
+
67
+ if (type) {
68
+ c.addUseClause('stellar_fungible', 'allowlist::{AllowList, FungibleAllowList}');
69
+ c.overrideAssocType('FungibleToken', 'type ContractType = AllowList;');
70
+ } else {
71
+ c.addUseClause('stellar_fungible', 'blocklist::{BlockList, FungibleBlockList}');
72
+ c.overrideAssocType('FungibleToken', 'type ContractType = BlockList;');
73
+ }
74
+
75
+ const [getterFn, addFn, removeFn] = type
76
+ ? [functions.allowed, functions.allow_user, functions.disallow_user]
77
+ : [functions.blocked, functions.block_user, functions.unblock_user];
78
+
79
+ c.addTraitFunction(limitationsTrait, getterFn);
80
+
81
+ const accessProps = {
82
+ useMacro: true,
83
+ role: 'manager',
84
+ caller: 'operator',
85
+ };
86
+
87
+ c.addTraitFunction(limitationsTrait, addFn);
88
+ requireAccessControl(c, limitationsTrait, addFn, access, accessProps);
89
+
90
+ c.addTraitFunction(limitationsTrait, removeFn);
91
+ requireAccessControl(c, limitationsTrait, removeFn, access, accessProps);
92
+ }
93
+
94
+ const functions = {
95
+ ...fungibleFunctions,
96
+ ...defineFunctions({
97
+ allowed: {
98
+ args: [getSelfArg(), { name: 'account', type: 'Address' }],
99
+ returns: 'bool',
100
+ code: ['AllowList::allowed(e, &account)'],
101
+ },
102
+ allow_user: {
103
+ args: [getSelfArg(), { name: 'user', type: 'Address' }, { name: 'operator', type: 'Address' }],
104
+ code: ['AllowList::allow_user(e, &user)'],
105
+ },
106
+ disallow_user: {
107
+ args: [getSelfArg(), { name: 'user', type: 'Address' }, { name: 'operator', type: 'Address' }],
108
+ code: ['AllowList::disallow_user(e, &user)'],
109
+ },
110
+ blocked: {
111
+ args: [getSelfArg(), { name: 'account', type: 'Address' }],
112
+ returns: 'bool',
113
+ code: ['BlockList::blocked(e, &account)'],
114
+ },
115
+ block_user: {
116
+ args: [getSelfArg(), { name: 'user', type: 'Address' }, { name: 'operator', type: 'Address' }],
117
+ code: ['BlockList::block_user(e, &user)'],
118
+ },
119
+ unblock_user: {
120
+ args: [getSelfArg(), { name: 'user', type: 'Address' }, { name: 'operator', type: 'Address' }],
121
+ code: ['BlockList::unblock_user(e, &user)'],
122
+ },
123
+ }),
124
+ };
@@ -1,9 +1,16 @@
1
1
  import type { BaseFunction } from '../contract';
2
2
 
3
- type ImplicitNameFunction = Omit<BaseFunction, 'name'>;
3
+ type OverridableImplicitNameFunction = Omit<BaseFunction, 'name'> & { name?: string };
4
4
 
5
- export function defineFunctions<F extends string>(fns: Record<F, ImplicitNameFunction>): Record<F, BaseFunction>;
5
+ export function defineFunctions<F extends string>(
6
+ fns: Record<F, OverridableImplicitNameFunction>,
7
+ ): Record<F, BaseFunction>;
6
8
 
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
+ export function defineFunctions(fns: Record<string, OverridableImplicitNameFunction>): Record<string, BaseFunction> {
10
+ return Object.fromEntries(
11
+ Object.entries(fns).map(([implicitName, fn]) => [
12
+ implicitName,
13
+ Object.assign({ name: fn.name ?? implicitName }, fn),
14
+ ]),
15
+ );
9
16
  }
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * The actual latest version to use in links.
3
3
  */
4
- export const contractsVersion = '0.2.0';
4
+ export const contractsVersion = '0.3.0';
5
5
  export const contractsVersionTag = `v${contractsVersion}`;
6
6
 
7
7
  /**
8
8
  * Semantic version string representing of the minimum compatible version of Contracts to display in output.
9
9
  */
10
- export const compatibleContractsSemver = '^0.2.0';
10
+ export const compatibleContractsSemver = '^0.3.0';
11
11
 
12
12
  /**
13
13
  * The Soroban version for which compilation and testing have passing tests
@@ -0,0 +1,57 @@
1
+ import JSZip from 'jszip';
2
+ import type { GenericOptions } from './build-generic';
3
+ import type { Contract } from './contract';
4
+ import { printContract, removeCreateLevelAttributes } from './print';
5
+ import {
6
+ addDependenciesWith,
7
+ allStellarDependencies,
8
+ contractOptionsToContractName,
9
+ createRustLibFile,
10
+ printContractCargo,
11
+ printRustNameTest,
12
+ } from './zip-shared';
13
+ import { contractsVersionTag, compatibleSorobanVersion } from './utils/version';
14
+
15
+ const workspaceCargo = `[workspace]
16
+ resolver = "2"
17
+ members = ["contracts/*"]
18
+
19
+ [workspace.package]
20
+ authors = []
21
+ edition = "2021"
22
+ license = "Apache-2.0"
23
+ version = "0.0.1"
24
+
25
+ [workspace.dependencies]
26
+ ${addDependenciesWith(`{ git = "https://github.com/OpenZeppelin/stellar-contracts", tag = "${contractsVersionTag}" }`, allStellarDependencies)}${addDependenciesWith(`{ version = "${compatibleSorobanVersion}" }`, ['soroban'])}
27
+ `;
28
+
29
+ const readme = `\
30
+ # Sample Rust Contract Environment
31
+
32
+ This project demonstrates a basic Rust contract environment use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/) and a test for that contract. Make sure you have the required dependencies and keep building!
33
+
34
+ ## Go further
35
+
36
+ Continue your development journey with [Stellar CLI](https://github.com/stellar/stellar-cli).
37
+
38
+ ## Installing dependencies
39
+
40
+ - See [Rust and Stellar installation guide](https://developers.stellar.org/docs/build/smart-contracts/getting-started/setup).
41
+ - See [Git installation guide](https://github.com/git-guides/install-git).
42
+ `;
43
+
44
+ export async function zipRust(c: Contract, opts: GenericOptions) {
45
+ const zip = new JSZip();
46
+
47
+ const contractName = contractOptionsToContractName(opts?.kind || 'contract');
48
+
49
+ zip.file(`contracts/${contractName}/src/contract.rs`, removeCreateLevelAttributes(printContract(c)));
50
+ zip.file(`contracts/${contractName}/src/test.rs`, printRustNameTest(c));
51
+ zip.file(`contracts/${contractName}/src/lib.rs`, createRustLibFile);
52
+ zip.file(`contracts/${contractName}/Cargo.toml`, printContractCargo(contractName));
53
+ zip.file('Cargo.toml', workspaceCargo);
54
+ zip.file('README.md', readme);
55
+
56
+ return zip;
57
+ }
@@ -3,82 +3,14 @@ import type { GenericOptions } from './build-generic';
3
3
  import type { Contract } from './contract';
4
4
  import { printContract, removeCreateLevelAttributes } from './print';
5
5
  import { compatibleSorobanVersion, contractsVersionTag } from './utils/version';
6
-
7
- const dependencies = {
8
- base: ['stellar-default-impl-macro'],
9
- fungible: ['stellar-fungible'],
10
- nonFungible: ['stellar-non-fungible'],
11
- pausable: ['stellar-pausable', 'stellar-pausable-macros'],
12
- upgradable: ['stellar-upgradeable', 'stellar-upgradeable-macros'],
13
- soroban: ['soroban-sdk'],
14
- } as const;
15
-
16
- const addDependenciesWith = (dependencyValue: string, dependenciesToAdd: (keyof typeof dependencies)[]) =>
17
- dependenciesToAdd.reduce((addedDependency, dependencyName) => {
18
- return `${addedDependency}${dependencies[dependencyName].map(cargoDependencies => `${cargoDependencies} = ${dependencyValue}\n`).join('')}`;
19
- }, '');
20
-
21
- const getDependenciesToAdd = (opts: GenericOptions): (keyof typeof dependencies)[] => {
22
- const dependenciesToAdd: (keyof typeof dependencies)[] = ['base'];
23
-
24
- if (opts.kind === 'Fungible') dependenciesToAdd.push('fungible');
25
- if (opts.kind === 'NonFungible') dependenciesToAdd.push('nonFungible');
26
- if (opts.pausable) dependenciesToAdd.push('pausable');
27
- if (opts.upgradeable) dependenciesToAdd.push('upgradable');
28
-
29
- return dependenciesToAdd;
30
- };
31
-
32
- const test = (c: Contract) => `#![cfg(test)]
33
-
34
- extern crate std;
35
-
36
- use soroban_sdk::{ ${getAddressArgs(c).length ? 'testutils::Address as _, Address, ' : ''}Env, String };
37
-
38
- use crate::contract::{ ${c.name}, ${c.name}Client };
39
-
40
- #[test]
41
- fn initial_state() {
42
- let env = Env::default();
43
-
44
- let contract_addr = env.register(${c.name}, (${getAddressArgs(c)
45
- .map(() => 'Address::generate(&env)')
46
- .join(',')}${getAddressArgs(c).length === 1 ? ',' : ''}));
47
- let client = ${c.name}Client::new(&env, &contract_addr);
48
-
49
- assert_eq!(client.name(), String::from_str(&env, "${c.name}"));
50
- }
51
-
52
- // Add more tests bellow
53
- `;
54
-
55
- function contractCargo(opts: GenericOptions, scaffoldContractName: string) {
56
- return `[package]
57
- name = "${scaffoldContractName.replace(/_/, '-')}-contract"
58
- edition.workspace = true
59
- license.workspace = true
60
- publish = false
61
- version.workspace = true
62
-
63
- [lib]
64
- crate-type = ["cdylib"]
65
- doctest = false
66
-
67
- [dependencies]
68
- ${addDependenciesWith('{ workspace = true }', [...getDependenciesToAdd(opts), 'soroban'])}
69
- [dev-dependencies]
70
- ${addDependenciesWith('{ workspace = true, features = ["testutils"] }', ['soroban'])}
71
- `;
72
- }
73
-
74
- function pascalToSnakeCase(string: string) {
75
- return string
76
- .replace(/([A-Z])/g, '_$1')
77
- .replace(/^_/, '')
78
- .toLowerCase();
79
- }
80
-
81
- export const contractOptionsToScaffoldContractName = pascalToSnakeCase;
6
+ import {
7
+ addDependenciesWith,
8
+ allStellarDependencies,
9
+ contractOptionsToContractName,
10
+ createRustLibFile,
11
+ printContractCargo,
12
+ printRustNameTest,
13
+ } from './zip-shared';
82
14
 
83
15
  function getAddressArgs(c: Contract): string[] {
84
16
  return c.constructorArgs
@@ -161,11 +93,11 @@ setup_environment() {
161
93
  }
162
94
  `;
163
95
 
164
- const updateWorkspaceCargo = (opts: GenericOptions) => `update_cargo() {
96
+ const updateWorkspaceCargo = `update_cargo() {
165
97
  cp Cargo.toml Cargo.toml.bak
166
98
 
167
99
  cat <<EOF > deps.tmp
168
- ${addDependenciesWith(`{ git = "https://github.com/OpenZeppelin/stellar-contracts", tag = "${contractsVersionTag}" }`, getDependenciesToAdd(opts))}${addDependenciesWith(`{ version = "${compatibleSorobanVersion}" }`, ['soroban'])}
100
+ ${addDependenciesWith(`{ git = "https://github.com/OpenZeppelin/stellar-contracts", tag = "${contractsVersionTag}" }`, allStellarDependencies)}${addDependenciesWith(`{ version = "${compatibleSorobanVersion}" }`, ['soroban'])}
169
101
  EOF
170
102
 
171
103
  awk '
@@ -233,7 +165,7 @@ init_git(){
233
165
 
234
166
  ${environmentsFileUpdate(c, scaffoldContractName)}
235
167
 
236
- ${updateWorkspaceCargo(opts)}
168
+ ${updateWorkspaceCargo}
237
169
 
238
170
  build_contracts() {
239
171
  cargo build
@@ -293,7 +225,7 @@ This project demonstrates a basic Scaffold use case. It comes with a contract ge
293
225
  ## Installing dependencies
294
226
 
295
227
  - See [Git installation guide](https://github.com/git-guides/install-git).
296
- - See [Rust installation guide](https://www.rust-lang.org/tools/install).
228
+ - See [Rust and Stellar installation guide](https://developers.stellar.org/docs/build/smart-contracts/getting-started/setup).
297
229
  - See [Scaffold CLI installation guide](https://github.com/AhaLabs/scaffold-stellar?tab=readme-ov-file#quick-start).
298
230
  - See [Docker installation guide](https://docs.docker.com/engine/install/).
299
231
  - See [Node installation guide](https://nodejs.org/en/download).
@@ -325,22 +257,15 @@ npm run dev
325
257
  `;
326
258
  };
327
259
 
328
- const createLib = `#![no_std]
329
- #![allow(dead_code)]
330
-
331
- mod contract;
332
- mod test;
333
- `;
334
-
335
260
  export async function zipScaffold(c: Contract, opts: GenericOptions) {
336
261
  const zip = new JSZip();
337
262
 
338
- const scaffoldContractName = contractOptionsToScaffoldContractName(opts?.kind || 'contract');
263
+ const scaffoldContractName = contractOptionsToContractName(opts?.kind || 'contract');
339
264
 
340
265
  zip.file(`contracts/${scaffoldContractName}/src/contract.rs`, removeCreateLevelAttributes(printContract(c)));
341
- zip.file(`contracts/${scaffoldContractName}/src/test.rs`, test(c));
342
- zip.file(`contracts/${scaffoldContractName}/src/lib.rs`, createLib);
343
- zip.file(`contracts/${scaffoldContractName}/Cargo.toml`, contractCargo(opts, scaffoldContractName));
266
+ zip.file(`contracts/${scaffoldContractName}/src/test.rs`, printRustNameTest(c));
267
+ zip.file(`contracts/${scaffoldContractName}/src/lib.rs`, createRustLibFile);
268
+ zip.file(`contracts/${scaffoldContractName}/Cargo.toml`, printContractCargo(scaffoldContractName));
344
269
  zip.file('setup.sh', setupSh(c, opts, scaffoldContractName));
345
270
  zip.file('README-WIZARD.md', readme(c));
346
271
 
@@ -0,0 +1,85 @@
1
+ import type { Contract } from './contract';
2
+
3
+ function pascalToSnakeCase(string: string) {
4
+ return string
5
+ .replace(/([A-Z])/g, '_$1')
6
+ .replace(/^_/, '')
7
+ .toLowerCase();
8
+ }
9
+
10
+ export const contractOptionsToContractName = pascalToSnakeCase;
11
+
12
+ export function getAddressArgs(c: Pick<Contract, 'constructorArgs'>): string[] {
13
+ return (c.constructorArgs || [])
14
+ .filter(constructorArg => constructorArg.type?.trim() === 'Address')
15
+ .map(constructorArg => constructorArg.name);
16
+ }
17
+
18
+ export const printRustNameTest = (c: Pick<Contract, 'constructorArgs' | 'name'>) => `#![cfg(test)]
19
+
20
+ extern crate std;
21
+
22
+ use soroban_sdk::{ ${getAddressArgs(c).length ? 'testutils::Address as _, Address, ' : ''}Env, String };
23
+
24
+ use crate::contract::{ ${c.name}, ${c.name}Client };
25
+
26
+ #[test]
27
+ fn initial_state() {
28
+ let env = Env::default();
29
+
30
+ let contract_addr = env.register(${c.name}, (${getAddressArgs(c)
31
+ .map(() => 'Address::generate(&env)')
32
+ .join(',')}${getAddressArgs(c).length === 1 ? ',' : ''}));
33
+ let client = ${c.name}Client::new(&env, &contract_addr);
34
+
35
+ assert_eq!(client.name(), String::from_str(&env, "${c.name}"));
36
+ }
37
+
38
+ // Add more tests bellow
39
+ `;
40
+
41
+ const getKeysOf = <TObject extends Record<string, unknown>>(objectToGetKeysOf: TObject) =>
42
+ Object.keys(objectToGetKeysOf) as (keyof TObject)[];
43
+
44
+ export const stellarDependencies = {
45
+ base: ['stellar-default-impl-macro'],
46
+ fungible: ['stellar-fungible'],
47
+ nonFungible: ['stellar-non-fungible'],
48
+ pausable: ['stellar-pausable', 'stellar-pausable-macros'],
49
+ upgradable: ['stellar-upgradeable', 'stellar-upgradeable-macros'],
50
+ } as const;
51
+
52
+ export const allStellarDependencies = getKeysOf(stellarDependencies);
53
+
54
+ export const allDependencies = {
55
+ ...stellarDependencies,
56
+ soroban: ['soroban-sdk'],
57
+ } as const;
58
+
59
+ export const addDependenciesWith = (dependencyValue: string, dependenciesToAdd: (keyof typeof allDependencies)[]) =>
60
+ dependenciesToAdd.reduce((addedDependency, dependencyName) => {
61
+ return `${addedDependency}${allDependencies[dependencyName].map(cargoDependencies => `${cargoDependencies} = ${dependencyValue}\n`).join('')}`;
62
+ }, '');
63
+
64
+ export const printContractCargo = (scaffoldContractName: string) => `[package]
65
+ name = "${scaffoldContractName.replace(/_/, '-')}-contract"
66
+ edition.workspace = true
67
+ license.workspace = true
68
+ publish = false
69
+ version.workspace = true
70
+
71
+ [lib]
72
+ crate-type = ["cdylib"]
73
+ doctest = false
74
+
75
+ [dependencies]
76
+ ${addDependenciesWith('{ workspace = true }', [...allStellarDependencies, 'soroban'])}
77
+ [dev-dependencies]
78
+ ${addDependenciesWith('{ workspace = true, features = ["testutils"] }', ['soroban'])}`;
79
+
80
+ export const createRustLibFile = `#![no_std]
81
+ #![allow(dead_code)]
82
+
83
+ mod contract;
84
+ mod test;
85
+ `;