@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.
- package/dist/add-pausable.d.ts.map +1 -1
- package/dist/add-pausable.js +9 -4
- package/dist/add-pausable.js.map +1 -1
- package/dist/add-upgradeable.d.ts.map +1 -1
- package/dist/add-upgradeable.js +6 -2
- package/dist/add-upgradeable.js.map +1 -1
- package/dist/api.d.ts +5 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +9 -1
- package/dist/api.js.map +1 -1
- package/dist/build-generic.d.ts +5 -1
- package/dist/build-generic.d.ts.map +1 -1
- package/dist/build-generic.js +3 -0
- package/dist/build-generic.js.map +1 -1
- package/dist/contract.d.ts +7 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +17 -1
- package/dist/contract.js.map +1 -1
- package/dist/fungible.d.ts +5 -2
- package/dist/fungible.d.ts.map +1 -1
- package/dist/fungible.js +80 -50
- package/dist/fungible.js.map +1 -1
- package/dist/generate/sources.d.ts.map +1 -1
- package/dist/generate/sources.js +6 -0
- package/dist/generate/sources.js.map +1 -1
- package/dist/generate/stablecoin.d.ts +3 -0
- package/dist/generate/stablecoin.d.ts.map +1 -0
- package/dist/generate/stablecoin.js +24 -0
- package/dist/generate/stablecoin.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/kind.js +1 -0
- package/dist/kind.js.map +1 -1
- package/dist/non-fungible.d.ts +1 -0
- package/dist/non-fungible.d.ts.map +1 -1
- package/dist/non-fungible.js +91 -71
- package/dist/non-fungible.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +13 -5
- package/dist/print.js.map +1 -1
- package/dist/set-access-control.d.ts +7 -4
- package/dist/set-access-control.d.ts.map +1 -1
- package/dist/set-access-control.js +57 -15
- package/dist/set-access-control.js.map +1 -1
- package/dist/set-info.d.ts +2 -0
- package/dist/set-info.d.ts.map +1 -1
- package/dist/set-info.js +6 -2
- package/dist/set-info.js.map +1 -1
- package/dist/stablecoin.d.ts +11 -0
- package/dist/stablecoin.d.ts.map +1 -0
- package/dist/stablecoin.js +103 -0
- package/dist/stablecoin.js.map +1 -0
- package/dist/utils/define-functions.d.ts +4 -2
- package/dist/utils/define-functions.d.ts.map +1 -1
- package/dist/utils/define-functions.js +7 -1
- package/dist/utils/define-functions.js.map +1 -1
- package/dist/utils/version.d.ts +3 -3
- package/dist/utils/version.js +2 -2
- package/dist/zip-rust.d.ts +5 -0
- package/dist/zip-rust.d.ts.map +1 -0
- package/dist/zip-rust.js +49 -0
- package/dist/zip-rust.js.map +1 -0
- package/dist/zip-scaffold.d.ts +0 -3
- package/dist/zip-scaffold.d.ts.map +1 -1
- package/dist/zip-scaffold.js +9 -85
- package/dist/zip-scaffold.js.map +1 -1
- package/dist/zip-shared.d.ts +26 -0
- package/dist/zip-shared.d.ts.map +1 -0
- package/dist/zip-shared.js +79 -0
- package/dist/zip-shared.js.map +1 -0
- package/package.json +1 -1
- package/src/add-pausable.ts +9 -4
- package/src/add-upgradeable.ts +6 -2
- package/src/api.ts +27 -4
- package/src/build-generic.ts +6 -0
- package/src/contract.ts +22 -2
- package/src/fungible.ts +84 -56
- package/src/generate/sources.ts +7 -0
- package/src/generate/stablecoin.ts +23 -0
- package/src/index.ts +2 -0
- package/src/kind.ts +1 -0
- package/src/non-fungible.ts +98 -77
- package/src/print.ts +15 -5
- package/src/set-access-control.ts +72 -19
- package/src/set-info.ts +8 -1
- package/src/stablecoin.ts +124 -0
- package/src/utils/define-functions.ts +11 -4
- package/src/utils/version.ts +2 -2
- package/src/zip-rust.ts +57 -0
- package/src/zip-scaffold.ts +16 -91
- 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', '
|
|
19
|
-
c.addUseClause('
|
|
20
|
-
c.
|
|
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
|
|
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
|
-
|
|
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('
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
c.
|
|
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, [
|
|
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
|
|
3
|
+
type OverridableImplicitNameFunction = Omit<BaseFunction, 'name'> & { name?: string };
|
|
4
4
|
|
|
5
|
-
export function defineFunctions<F extends string>(
|
|
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,
|
|
8
|
-
return Object.fromEntries(
|
|
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
|
}
|
package/src/utils/version.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The actual latest version to use in links.
|
|
3
3
|
*/
|
|
4
|
-
export const contractsVersion = '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.
|
|
10
|
+
export const compatibleContractsSemver = '^0.3.0';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The Soroban version for which compilation and testing have passing tests
|
package/src/zip-rust.ts
ADDED
|
@@ -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
|
+
}
|
package/src/zip-scaffold.ts
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 =
|
|
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}" }`,
|
|
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
|
|
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://
|
|
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 =
|
|
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`,
|
|
342
|
-
zip.file(`contracts/${scaffoldContractName}/src/lib.rs`,
|
|
343
|
-
zip.file(`contracts/${scaffoldContractName}/Cargo.toml`,
|
|
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
|
+
`;
|