@0xobelisk/sui-common 1.2.0-pre.98 ā 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +92 -0
- package/README.md +670 -1
- package/dist/index.d.ts +61 -27
- package/dist/index.js +1078 -461
- package/dist/index.js.map +1 -1
- package/package.json +15 -17
- package/src/codegen/debug.ts +0 -4
- package/src/codegen/types/index.ts +46 -10
- package/src/codegen/utils/config.ts +1 -1
- package/src/codegen/utils/format.ts +0 -6
- package/src/codegen/utils/formatAndWrite.ts +10 -31
- package/src/codegen/utils/generateLock.ts +122 -0
- package/src/codegen/utils/index.ts +4 -2
- package/src/codegen/utils/renderMove/{schemaGen.ts ā codegen.ts} +40 -28
- package/src/codegen/utils/renderMove/common.ts +0 -65
- package/src/codegen/utils/renderMove/dapp.ts +2 -14
- package/src/codegen/utils/renderMove/generateDappKey.ts +33 -18
- package/src/codegen/utils/renderMove/generateError.ts +32 -15
- package/src/codegen/utils/renderMove/generateGenesis.ts +55 -22
- package/src/codegen/utils/renderMove/generateInitTest.ts +26 -14
- package/src/codegen/utils/renderMove/generateObjects.ts +377 -0
- package/src/codegen/utils/renderMove/generatePermits.ts +151 -0
- package/src/codegen/utils/renderMove/generateResources.ts +894 -242
- package/src/codegen/utils/renderMove/generateScenes.ts +467 -0
- package/src/codegen/utils/renderMove/generateScript.ts +18 -13
- package/src/codegen/utils/renderMove/generateSystem.ts +0 -2
- package/src/codegen/utils/renderMove/generateUserStorageInit.ts +37 -0
- package/src/codegen/utils/validateConfig.ts +237 -0
- package/src/index.ts +0 -1
- package/src/modules.d.ts +0 -10
- package/src/codegen/modules.d.ts +0 -1
- package/src/codegen/utils/posixPath.ts +0 -8
- package/src/codegen/utils/renderMove/generateComponents.ts +0 -802
- package/src/codegen/utils/renderMove/generateDefaultSchema.ts +0 -216
- package/src/codegen/utils/renderMove/generateEvent.ts +0 -99
- package/src/codegen/utils/renderMove/generateSchema.ts +0 -287
- package/src/codegen/utils/renderMove/generateSchemaHub.ts +0 -60
- package/src/parseData/index.ts +0 -1
- package/src/parseData/parser/index.ts +0 -47
|
@@ -1,24 +1,41 @@
|
|
|
1
|
-
import { ErrorData } from '../../types';
|
|
1
|
+
import { ErrorData, ErrorEntry } from '../../types';
|
|
2
2
|
import { formatAndWriteMove } from '../formatAndWrite';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
function toErrorConstName(key: string): string {
|
|
5
|
+
return (
|
|
6
|
+
'E' +
|
|
7
|
+
key
|
|
8
|
+
.split('_')
|
|
9
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
10
|
+
.join('')
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getMessage(entry: ErrorEntry): string {
|
|
15
|
+
return typeof entry === 'string' ? entry : entry.message;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function generateError(projectName: string, errors: ErrorData, path: string) {
|
|
19
|
+
console.log('\nš¦ Starting Error Generation...');
|
|
20
|
+
|
|
21
|
+
const entries = Object.entries(errors)
|
|
22
|
+
.map(([name, entry]) => {
|
|
23
|
+
const constName = toErrorConstName(name);
|
|
24
|
+
const message = getMessage(entry as ErrorEntry);
|
|
25
|
+
console.log(` āā ${name}: ${message}`);
|
|
26
|
+
return [
|
|
27
|
+
` #[error]`,
|
|
28
|
+
` const ${constName}: vector<u8> = b"${message}";`,
|
|
29
|
+
` public fun ${name}(condition: bool) { assert!(condition, ${constName}) }`
|
|
30
|
+
].join('\n');
|
|
31
|
+
})
|
|
32
|
+
.join('\n\n');
|
|
6
33
|
|
|
7
|
-
|
|
8
|
-
${Object.entries(errors)
|
|
9
|
-
.map(([name, message]) => {
|
|
10
|
-
console.log(` āā ${name}: ${message}`);
|
|
11
|
-
return `#[error]
|
|
12
|
-
const ${name.toUpperCase()}: vector<u8> = b"${message}";
|
|
13
|
-
public fun ${name}_error(condition: bool) { assert!(condition, ${name.toUpperCase()}) }
|
|
14
|
-
`;
|
|
15
|
-
})
|
|
16
|
-
.join('\n')}
|
|
17
|
-
}`;
|
|
34
|
+
const code = `module ${projectName}::error {\n${entries}\n}\n`;
|
|
18
35
|
|
|
19
36
|
await formatAndWriteMove(
|
|
20
37
|
code,
|
|
21
|
-
`${path}/src/${projectName}/sources/codegen/
|
|
38
|
+
`${path}/src/${projectName}/sources/codegen/error.move`,
|
|
22
39
|
'formatAndWriteMove'
|
|
23
40
|
);
|
|
24
41
|
}
|
|
@@ -1,30 +1,63 @@
|
|
|
1
1
|
import { DubheConfig } from '../../types';
|
|
2
2
|
import { formatAndWriteMove } from '../formatAndWrite';
|
|
3
|
-
// import { existsSync } from 'fs'; // Unused
|
|
4
|
-
// import { capitalizeAndRemoveUnderscores } from './generateSchema'; // Unused
|
|
5
|
-
// import path from 'node:path'; // Unused
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
export async function generateGenesis(config: DubheConfig, path: string, initialMode: 0 | 1 = 1) {
|
|
5
|
+
// The dubhe framework itself is not a DApp ā it provides the infrastructure.
|
|
6
|
+
// Its genesis only needs to call deploy_hook::run to initialise framework state
|
|
7
|
+
// (fee config, admin, etc.) without creating a DappStorage for itself.
|
|
8
|
+
const isDubheFramework = config.name === 'dubhe';
|
|
9
|
+
|
|
10
|
+
let genesis_code = isDubheFramework
|
|
11
|
+
? `module ${config.name}::genesis {
|
|
12
|
+
use dubhe::dapp_service::DappHub;
|
|
13
|
+
|
|
14
|
+
// The framework genesis initialises the DappHub state via deploy_hook.
|
|
15
|
+
// No DappStorage is created for the framework itself ā the framework is
|
|
16
|
+
// infrastructure, not a DApp.
|
|
17
|
+
public fun run(dapp_hub: &mut DappHub, ctx: &mut TxContext) {
|
|
18
|
+
${config.name}::deploy_hook::run(dapp_hub, ctx);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Called during framework upgrades to run any custom migration logic.
|
|
22
|
+
// \`dubhe upgrade\` rewrites the region between the separator comments.
|
|
23
|
+
public(package) fun migrate(_dapp_hub: &mut DappHub, _ctx: &mut TxContext) {
|
|
24
|
+
// ==========================================
|
|
25
|
+
// Add custom migration logic here.
|
|
26
|
+
// ==========================================
|
|
27
|
+
}
|
|
10
28
|
}
|
|
29
|
+
`
|
|
30
|
+
: `module ${config.name}::genesis {
|
|
31
|
+
use sui::clock::Clock;
|
|
32
|
+
use dubhe::dapp_service::{DappHub, DappStorage};
|
|
33
|
+
use ${config.name}::dapp_key;
|
|
34
|
+
use dubhe::dapp_system;
|
|
35
|
+
use std::ascii::string;
|
|
36
|
+
|
|
37
|
+
// The one-shot guard is enforced inside dapp_system::create_dapp, which
|
|
38
|
+
// records the DappKey type in DappHub before returning DappStorage.
|
|
39
|
+
// genesis.move does not need to carry its own guard.
|
|
40
|
+
public fun run(dapp_hub: &mut DappHub, clock: &Clock, ctx: &mut TxContext) {
|
|
41
|
+
// create_dapp aborts with dapp_already_initialized_error on repeated calls.
|
|
42
|
+
let dapp_key = dapp_key::new();
|
|
43
|
+
let mut ds = dapp_system::create_dapp(dapp_key, dapp_hub, string(b"${config.name}"), string(b"${config.description}"), ${initialMode}, clock, ctx);
|
|
44
|
+
|
|
45
|
+
// Set up initial DApp state (e.g. default resource values).
|
|
46
|
+
${config.name}::deploy_hook::run(&mut ds, ctx);
|
|
47
|
+
|
|
48
|
+
// Share DappStorage so every transaction can access it.
|
|
49
|
+
transfer::public_share_object(ds);
|
|
50
|
+
}
|
|
11
51
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Create Dapp
|
|
22
|
-
let dapp_key = dapp_key::new();
|
|
23
|
-
dapp_system::create_dapp(dapp_hub, dapp_key, string(b"${config.name}"), string(b"${config.description}"), clock, ctx);
|
|
24
|
-
|
|
25
|
-
// Logic that needs to be automated once the contract is deployed
|
|
26
|
-
${config.name}::deploy_hook::run(dapp_hub, ctx);
|
|
27
|
-
}
|
|
52
|
+
// Called during contract upgrades to register newly added resource tables
|
|
53
|
+
// and run any custom migration logic. \`dubhe upgrade\` rewrites the region
|
|
54
|
+
// between the separator comments; do not edit that block manually.
|
|
55
|
+
public(package) fun migrate(_dapp_hub: &mut DappHub, _dapp_storage: &mut DappStorage, _ctx: &mut TxContext) {
|
|
56
|
+
// ==========================================
|
|
57
|
+
// Add custom migration logic here (e.g. initialise new resource defaults).
|
|
58
|
+
// migrate_to_vN in migrate.move calls this function automatically.
|
|
59
|
+
// ==========================================
|
|
60
|
+
}
|
|
28
61
|
}
|
|
29
62
|
`;
|
|
30
63
|
await formatAndWriteMove(genesis_code, path, 'formatAndWriteMove');
|
|
@@ -2,23 +2,35 @@ import { DubheConfig } from '../../types';
|
|
|
2
2
|
import { formatAndWriteMove } from '../formatAndWrite';
|
|
3
3
|
|
|
4
4
|
export async function generateInitTest(config: DubheConfig, path: string) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
// For the dubhe package itself, we only need DappHub helpers (no UserStorage/DappStorage per-DApp).
|
|
6
|
+
// For DApp packages, expose all three test helpers.
|
|
7
|
+
const isDubhe = config.name === 'dubhe';
|
|
8
|
+
|
|
9
|
+
const dappSpecificHelpers = !isDubhe
|
|
10
|
+
? `
|
|
11
|
+
/// Create a DappStorage for this DApp without sharing it.
|
|
12
|
+
/// Suitable for unit tests that exercise global-resource functions.
|
|
13
|
+
public fun create_dapp_storage_for_testing(ctx: &mut TxContext): dubhe::dapp_service::DappStorage {
|
|
14
|
+
dubhe::dapp_system::create_dapp_storage_for_testing<${config.name}::dapp_key::DappKey>(ctx)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Create a UserStorage for \`owner\` without sharing it.
|
|
18
|
+
/// Suitable for unit tests that exercise user-level resource functions.
|
|
19
|
+
public fun create_user_storage_for_testing(owner: address, ctx: &mut TxContext): dubhe::dapp_service::UserStorage {
|
|
20
|
+
dubhe::dapp_system::create_user_storage_for_testing<${config.name}::dapp_key::DappKey>(owner, ctx)
|
|
21
|
+
}`
|
|
22
|
+
: '';
|
|
23
|
+
|
|
24
|
+
let init_test_code = `
|
|
25
|
+
module ${config.name}::init_test {
|
|
10
26
|
use dubhe::dapp_system;
|
|
11
27
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
${config.name != 'dubhe' ? `dubhe::genesis::run(&mut dapp_hub, &clock, ctx);` : ''}
|
|
17
|
-
${config.name}::genesis::run(&mut dapp_hub, &clock, ctx);
|
|
18
|
-
clock::destroy_for_testing(clock);
|
|
19
|
-
test_scenario::next_tx(scenario, ctx.sender());
|
|
20
|
-
dapp_hub
|
|
28
|
+
/// Create a DappHub for testing without sharing it.
|
|
29
|
+
/// Suitable for unit tests that need a DappHub context.
|
|
30
|
+
public fun create_dapp_hub_for_testing(ctx: &mut TxContext): dubhe::dapp_service::DappHub {
|
|
31
|
+
dapp_system::create_dapp_hub_for_testing(ctx)
|
|
21
32
|
}
|
|
33
|
+
${dappSpecificHelpers}
|
|
22
34
|
}
|
|
23
35
|
`;
|
|
24
36
|
await formatAndWriteMove(init_test_code, path, 'formatAndWriteMove');
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { DubheConfig, ObjectConfig, Component } from '../../types';
|
|
2
|
+
import { formatAndWriteMove } from '../formatAndWrite';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function toPascalCase(str: string): string {
|
|
6
|
+
return str
|
|
7
|
+
.split('_')
|
|
8
|
+
.map((word) => {
|
|
9
|
+
if (/^\d+$/.test(word)) return word;
|
|
10
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
11
|
+
})
|
|
12
|
+
.join('');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getMoveType(t: string): string {
|
|
16
|
+
return t === 'string' || t === 'String' ? 'String' : t;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate typed field accessors (get/set) for an ObjectStorage's own fields.
|
|
21
|
+
* Calls the framework's set_object_field / get_object_field which handle
|
|
22
|
+
* native-type storage and event emission automatically.
|
|
23
|
+
*/
|
|
24
|
+
// Type token for substitution into generated code
|
|
25
|
+
function objStorageType(markerName: string): string {
|
|
26
|
+
return `dubhe::dapp_service::ObjectStorage<${markerName}>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function generateFieldAccessors(objKey: string, cfg: ObjectConfig): string {
|
|
30
|
+
const markerName = toPascalCase(objKey);
|
|
31
|
+
const storageType = objStorageType(markerName);
|
|
32
|
+
const lines: string[] = [];
|
|
33
|
+
|
|
34
|
+
for (const [fieldName, fieldType] of Object.entries(cfg.fields)) {
|
|
35
|
+
const moveType = getMoveType(fieldType as string);
|
|
36
|
+
|
|
37
|
+
lines.push(`
|
|
38
|
+
public fun get_${fieldName}(storage: &${storageType}): ${moveType} {
|
|
39
|
+
dubhe::dapp_system::get_object_field<${markerName}, ${moveType}>(storage, b"${fieldName}")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public(package) fun set_${fieldName}(storage: &mut ${storageType}, value: ${moveType}) {
|
|
43
|
+
dubhe::dapp_system::set_object_field<DappKey, ${markerName}, ${moveType}>(
|
|
44
|
+
dapp_key::new(), storage, b"${fieldName}", value
|
|
45
|
+
);
|
|
46
|
+
}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return lines.join('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate bag accessor functions for a fungible resource accepted by this object.
|
|
54
|
+
* Reads use has_object_field to return a default of 0; writes do a read-modify-write
|
|
55
|
+
* through the framework CRUD API so events are emitted.
|
|
56
|
+
*/
|
|
57
|
+
function generateFungibleBagAccessors(objKey: string, resourceName: string): string {
|
|
58
|
+
const markerName = toPascalCase(objKey);
|
|
59
|
+
const storageType = objStorageType(markerName);
|
|
60
|
+
|
|
61
|
+
return `
|
|
62
|
+
public fun get_${resourceName}(storage: &${storageType}): u64 {
|
|
63
|
+
if (dubhe::dapp_system::has_object_field<${markerName}, u64>(storage, b"${resourceName}")) {
|
|
64
|
+
dubhe::dapp_system::get_object_field<${markerName}, u64>(storage, b"${resourceName}")
|
|
65
|
+
} else { 0 }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public(package) fun add_${resourceName}(storage: &mut ${storageType}, amount: u64) {
|
|
69
|
+
let current = get_${resourceName}(storage);
|
|
70
|
+
dubhe::dapp_system::set_object_field<DappKey, ${markerName}, u64>(
|
|
71
|
+
dapp_key::new(), storage, b"${resourceName}", current + amount
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public(package) fun sub_${resourceName}(storage: &mut ${storageType}, amount: u64) {
|
|
76
|
+
let current = get_${resourceName}(storage);
|
|
77
|
+
assert!(current >= amount, EInsufficientAmount);
|
|
78
|
+
dubhe::dapp_system::set_object_field<DappKey, ${markerName}, u64>(
|
|
79
|
+
dapp_key::new(), storage, b"${resourceName}", current - amount
|
|
80
|
+
);
|
|
81
|
+
}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generate bag accessor functions for a keyed resource accepted by this object.
|
|
86
|
+
* item_id is BCS-encoded as the Bag key; value is vector<u8> (raw bytes).
|
|
87
|
+
*/
|
|
88
|
+
function generateKeyedBagAccessors(objKey: string, resourceName: string, idField: string): string {
|
|
89
|
+
const markerName = toPascalCase(objKey);
|
|
90
|
+
const storageType = objStorageType(markerName);
|
|
91
|
+
|
|
92
|
+
return `
|
|
93
|
+
public fun has_${resourceName}(storage: &${storageType}, ${idField}: u64): bool {
|
|
94
|
+
let key = sui::bcs::to_bytes(&${idField});
|
|
95
|
+
dubhe::dapp_system::has_object_field<${markerName}, vector<u8>>(storage, key)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public fun get_${resourceName}_data(storage: &${storageType}, ${idField}: u64): vector<u8> {
|
|
99
|
+
let key = sui::bcs::to_bytes(&${idField});
|
|
100
|
+
dubhe::dapp_system::get_object_field<${markerName}, vector<u8>>(storage, key)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public(package) fun set_${resourceName}_data(storage: &mut ${storageType}, ${idField}: u64, data: vector<u8>) {
|
|
104
|
+
let key = sui::bcs::to_bytes(&${idField});
|
|
105
|
+
assert!(!dubhe::dapp_system::has_object_field<${markerName}, vector<u8>>(storage, key), EDuplicateItemId);
|
|
106
|
+
dubhe::dapp_system::set_object_field<DappKey, ${markerName}, vector<u8>>(
|
|
107
|
+
dapp_key::new(), storage, key, data
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public(package) fun remove_${resourceName}_data(storage: &mut ${storageType}, ${idField}: u64): vector<u8> {
|
|
112
|
+
let key = sui::bcs::to_bytes(&${idField});
|
|
113
|
+
assert!(dubhe::dapp_system::has_object_field<${markerName}, vector<u8>>(storage, key), EFieldNotFound);
|
|
114
|
+
dubhe::dapp_system::remove_object_field<DappKey, ${markerName}, vector<u8>>(dapp_key::new(), storage, key)
|
|
115
|
+
}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate cross-storage transfer functions for acceptsFrom sources.
|
|
120
|
+
* These live in the DESTINATION module and atomically move resources between
|
|
121
|
+
* two ObjectStorage (or SceneStorage) objects.
|
|
122
|
+
*/
|
|
123
|
+
function generateAcceptsFromTransfers(
|
|
124
|
+
projectName: string,
|
|
125
|
+
destKey: string,
|
|
126
|
+
destAccepts: string[],
|
|
127
|
+
acceptsFrom: string[],
|
|
128
|
+
config: DubheConfig
|
|
129
|
+
): { imports: string[]; functions: string[] } {
|
|
130
|
+
const resources = config.resources ?? {};
|
|
131
|
+
const allObjects = config.objects ?? {};
|
|
132
|
+
const allScenes = config.scenes ?? {};
|
|
133
|
+
|
|
134
|
+
const destMarker = toPascalCase(destKey);
|
|
135
|
+
const destStorageType = objStorageType(destMarker);
|
|
136
|
+
const imports: string[] = [];
|
|
137
|
+
const functions: string[] = [];
|
|
138
|
+
|
|
139
|
+
for (const sourceName of acceptsFrom) {
|
|
140
|
+
const sourceCfg = allObjects[sourceName] ?? allScenes[sourceName];
|
|
141
|
+
if (!sourceCfg) continue;
|
|
142
|
+
|
|
143
|
+
const sourceAccepts = sourceCfg.accepts ?? [];
|
|
144
|
+
const sourceMarker = toPascalCase(sourceName);
|
|
145
|
+
// Source may be ObjectStorage or SceneStorage ā import just the module
|
|
146
|
+
imports.push(` use ${projectName}::${sourceName};`);
|
|
147
|
+
|
|
148
|
+
const commonResources = sourceAccepts.filter((r) => destAccepts.includes(r));
|
|
149
|
+
|
|
150
|
+
for (const resourceName of commonResources) {
|
|
151
|
+
const resCfg = resources[resourceName];
|
|
152
|
+
if (!resCfg || typeof resCfg === 'string') continue;
|
|
153
|
+
const comp = resCfg as Component;
|
|
154
|
+
|
|
155
|
+
// Use fully-qualified phantom type to avoid import issues
|
|
156
|
+
const isSourceScene = !!allScenes[sourceName];
|
|
157
|
+
const qualifiedSourceMarker = `${projectName}::${sourceName}::${sourceMarker}`;
|
|
158
|
+
const sourceStorageType = isSourceScene
|
|
159
|
+
? `dubhe::dapp_service::SceneStorage<${qualifiedSourceMarker}>`
|
|
160
|
+
: `dubhe::dapp_service::ObjectStorage<${qualifiedSourceMarker}>`;
|
|
161
|
+
const sourceSceneCfg = isSourceScene ? allScenes[sourceName] : undefined;
|
|
162
|
+
const sourcePermitType =
|
|
163
|
+
sourceSceneCfg?.authorization.kind === 'permit'
|
|
164
|
+
? `dubhe::dapp_service::ScenePermit<${projectName}::${
|
|
165
|
+
sourceSceneCfg.authorization.permit
|
|
166
|
+
}::${toPascalCase(sourceSceneCfg.authorization.permit)}>`
|
|
167
|
+
: '';
|
|
168
|
+
const sourcePermitParam =
|
|
169
|
+
sourceSceneCfg?.authorization.kind === 'permit'
|
|
170
|
+
? ` source_permit: &${sourcePermitType},\n`
|
|
171
|
+
: '';
|
|
172
|
+
const sourceIsPermitScene = sourceSceneCfg?.authorization.kind === 'permit';
|
|
173
|
+
const sourceCallPrefix = sourceIsPermitScene ? 'source_permit, ' : '';
|
|
174
|
+
// Permit-authorized scene writes resolve the caller identity from user_storage.
|
|
175
|
+
const sourceUserStorageParam = sourceIsPermitScene
|
|
176
|
+
? ` user_storage: &dubhe::dapp_service::UserStorage,\n`
|
|
177
|
+
: '';
|
|
178
|
+
const sourceUserStorageCall = sourceIsPermitScene ? 'user_storage, ' : '';
|
|
179
|
+
const sourceCtxParam = sourceIsPermitScene ? ' ctx: &TxContext,\n' : '';
|
|
180
|
+
const sourceCtxCall = sourceIsPermitScene ? ', ctx' : '';
|
|
181
|
+
|
|
182
|
+
if (!comp.fungible && comp.keys?.length) {
|
|
183
|
+
const idField = comp.keys[0];
|
|
184
|
+
functions.push(`
|
|
185
|
+
/// Transfer ${resourceName} (keyed item) from ${sourceName} into this ${destKey}.
|
|
186
|
+
public(package) fun transfer_${sourceName}_to_${destKey}_${resourceName}(
|
|
187
|
+
${sourcePermitParam} from: &mut ${sourceStorageType},
|
|
188
|
+
to: &mut ${destStorageType},
|
|
189
|
+
${sourceUserStorageParam} ${idField}: u64,
|
|
190
|
+
${sourceCtxParam}
|
|
191
|
+
) {
|
|
192
|
+
let data = ${sourceName}::remove_${resourceName}_data(${sourceCallPrefix}from, ${sourceUserStorageCall}${idField}${sourceCtxCall});
|
|
193
|
+
set_${resourceName}_data(to, ${idField}, data);
|
|
194
|
+
}`);
|
|
195
|
+
} else {
|
|
196
|
+
functions.push(`
|
|
197
|
+
/// Transfer ${resourceName} (fungible) from ${sourceName} into this ${destKey}.
|
|
198
|
+
public(package) fun transfer_${sourceName}_to_${destKey}_${resourceName}(
|
|
199
|
+
${sourcePermitParam} from: &mut ${sourceStorageType},
|
|
200
|
+
to: &mut ${destStorageType},
|
|
201
|
+
${sourceUserStorageParam} amount: u64,
|
|
202
|
+
${sourceCtxParam}
|
|
203
|
+
) {
|
|
204
|
+
${sourceName}::sub_${resourceName}(${sourceCallPrefix}from, ${sourceUserStorageCall}amount${sourceCtxCall});
|
|
205
|
+
add_${resourceName}(to, amount);
|
|
206
|
+
}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { imports, functions };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function generateObjects(config: DubheConfig, outputDir: string) {
|
|
215
|
+
if (!config.objects || Object.keys(config.objects).length === 0) return;
|
|
216
|
+
console.log('\nš¦ Starting Object Storage Generation...');
|
|
217
|
+
|
|
218
|
+
const projectName = config.name;
|
|
219
|
+
const resources = config.resources ?? {};
|
|
220
|
+
|
|
221
|
+
for (const [objKey, objCfg] of Object.entries(config.objects)) {
|
|
222
|
+
console.log(` āā ${objKey}`);
|
|
223
|
+
const markerName = toPascalCase(objKey);
|
|
224
|
+
const storageAlias = `${markerName}Storage`;
|
|
225
|
+
const typeTag = `b"${objKey}"`;
|
|
226
|
+
|
|
227
|
+
// Own field accessors (framework CRUD calls)
|
|
228
|
+
const fieldAccessors = generateFieldAccessors(objKey, objCfg);
|
|
229
|
+
|
|
230
|
+
// Bag accessors for accepted resources
|
|
231
|
+
const acceptedResources = objCfg.accepts ?? [];
|
|
232
|
+
const bagAccessorParts: string[] = [];
|
|
233
|
+
for (const resourceName of acceptedResources) {
|
|
234
|
+
const resCfg = resources[resourceName];
|
|
235
|
+
if (!resCfg || typeof resCfg === 'string') continue;
|
|
236
|
+
const comp = resCfg as Component;
|
|
237
|
+
if (!comp.fungible && comp.keys?.length) {
|
|
238
|
+
bagAccessorParts.push(generateKeyedBagAccessors(objKey, resourceName, comp.keys[0]));
|
|
239
|
+
} else {
|
|
240
|
+
bagAccessorParts.push(generateFungibleBagAccessors(objKey, resourceName));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// acceptsFrom: cross-storage transfer functions
|
|
245
|
+
const { imports: afImports, functions: afFunctions } = generateAcceptsFromTransfers(
|
|
246
|
+
projectName,
|
|
247
|
+
objKey,
|
|
248
|
+
acceptedResources,
|
|
249
|
+
objCfg.acceptsFrom ?? [],
|
|
250
|
+
config
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// adminOnly check for create
|
|
254
|
+
const adminCheck = objCfg.adminOnly
|
|
255
|
+
? ` assert!(ctx.sender() == dubhe::dapp_service::dapp_admin(dapp_storage), ENoPermission);`
|
|
256
|
+
: '';
|
|
257
|
+
|
|
258
|
+
const fullStorageTypeLocal = `dubhe::dapp_service::ObjectStorage<${markerName}>`;
|
|
259
|
+
|
|
260
|
+
// assert_<key>_id helper (reads entity_id via framework accessor)
|
|
261
|
+
const assertIdFn = `
|
|
262
|
+
public fun assert_${objKey}_id(storage: &${fullStorageTypeLocal}, expected: vector<u8>) {
|
|
263
|
+
assert!(*dubhe::dapp_service::object_storage_entity_id(storage) == expected, EWrongEntityId);
|
|
264
|
+
}`;
|
|
265
|
+
|
|
266
|
+
// entity_id accessor
|
|
267
|
+
const entityIdFn = `
|
|
268
|
+
public fun entity_id(storage: &${fullStorageTypeLocal}): vector<u8> {
|
|
269
|
+
*dubhe::dapp_service::object_storage_entity_id(storage)
|
|
270
|
+
}`;
|
|
271
|
+
|
|
272
|
+
// Conditional String import: needed when own fields or String-typed accepts are used
|
|
273
|
+
const ownFieldTypes = Object.values(objCfg.fields) as string[];
|
|
274
|
+
const needsStringImport = ownFieldTypes.some(
|
|
275
|
+
(t) => t === 'string' || t === 'String' || t === 'vector<String>'
|
|
276
|
+
);
|
|
277
|
+
const stringImport = needsStringImport ? `\n use std::ascii::String;` : '';
|
|
278
|
+
|
|
279
|
+
// Conditional error constants
|
|
280
|
+
const hasKeyedAccepts = acceptedResources.some((r) => {
|
|
281
|
+
const rc = resources[r];
|
|
282
|
+
if (!rc || typeof rc === 'string') return false;
|
|
283
|
+
return !!(rc as Component).keys?.length && !(rc as Component).fungible;
|
|
284
|
+
});
|
|
285
|
+
const hasFungibleAccepts = acceptedResources.some((r) => {
|
|
286
|
+
const rc = resources[r];
|
|
287
|
+
if (!rc || typeof rc === 'string') return false;
|
|
288
|
+
return !!(rc as Component).fungible;
|
|
289
|
+
});
|
|
290
|
+
// EFieldNotFound is only used in remove_*_data for keyed bag accessors
|
|
291
|
+
const objNeedsFieldNotFound = hasKeyedAccepts;
|
|
292
|
+
const objNeedsInsufficient = hasFungibleAccepts;
|
|
293
|
+
const objNeedsDuplicate = hasKeyedAccepts;
|
|
294
|
+
const objNeedsNoPermission = !!objCfg.adminOnly;
|
|
295
|
+
|
|
296
|
+
const errorConstants = [
|
|
297
|
+
objNeedsFieldNotFound
|
|
298
|
+
? ` #[error]\n const EFieldNotFound: vector<u8> = b"Field not found";`
|
|
299
|
+
: '',
|
|
300
|
+
objNeedsInsufficient
|
|
301
|
+
? ` #[error]\n const EInsufficientAmount: vector<u8> = b"Insufficient amount";`
|
|
302
|
+
: '',
|
|
303
|
+
objNeedsDuplicate
|
|
304
|
+
? ` #[error]\n const EDuplicateItemId: vector<u8> = b"Duplicate item id";`
|
|
305
|
+
: '',
|
|
306
|
+
` #[error]\n const EWrongEntityId: vector<u8> = b"Wrong entity id";`,
|
|
307
|
+
objNeedsNoPermission
|
|
308
|
+
? ` #[error]\n const ENoPermission: vector<u8> = b"Caller does not have permission";`
|
|
309
|
+
: ''
|
|
310
|
+
]
|
|
311
|
+
.filter(Boolean)
|
|
312
|
+
.join('\n');
|
|
313
|
+
|
|
314
|
+
const afImportBlock = afImports.length > 0 ? '\n' + afImports.join('\n') : '';
|
|
315
|
+
|
|
316
|
+
// Use the full framework type name for all function signatures.
|
|
317
|
+
// Move type aliases (public type) are not yet supported in this Sui version,
|
|
318
|
+
// so we use ObjectStorage<Marker> directly.
|
|
319
|
+
const fullStorageType = `dubhe::dapp_service::ObjectStorage<${markerName}>`;
|
|
320
|
+
|
|
321
|
+
const code = `module ${projectName}::${objKey} {
|
|
322
|
+
use dubhe::dapp_service::DappStorage;
|
|
323
|
+
use ${projectName}::dapp_key;
|
|
324
|
+
use ${projectName}::dapp_key::DappKey;${stringImport}${afImportBlock}
|
|
325
|
+
|
|
326
|
+
// āāā Error constants āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
327
|
+
${errorConstants}
|
|
328
|
+
|
|
329
|
+
const TYPE_TAG: vector<u8> = ${typeTag};
|
|
330
|
+
|
|
331
|
+
// āāā Phantom marker type āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
332
|
+
/// Phantom type that distinguishes ${storageAlias} from other ObjectStorage types
|
|
333
|
+
/// at the Move compiler level, preserving compile-time type safety.
|
|
334
|
+
/// All functions use ObjectStorage<${markerName}> directly in their signatures.
|
|
335
|
+
public struct ${markerName} has copy, drop {}
|
|
336
|
+
|
|
337
|
+
// āāā ID / entity accessors āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
338
|
+
${entityIdFn}
|
|
339
|
+
|
|
340
|
+
${assertIdFn}
|
|
341
|
+
|
|
342
|
+
// āāā Field accessors (own fields) āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
343
|
+
${fieldAccessors}
|
|
344
|
+
|
|
345
|
+
// āāā Bag accessors for accepted resources āāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
346
|
+
${bagAccessorParts.join('\n')}
|
|
347
|
+
|
|
348
|
+
// āāā acceptsFrom: cross-storage transfer functions āāāāāāāāāāāāāāāāāāāāā
|
|
349
|
+
${afFunctions.join('\n')}
|
|
350
|
+
|
|
351
|
+
// āāā Lifecycle entry functions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
352
|
+
public(package) fun create_${objKey}(
|
|
353
|
+
dapp_storage: &mut DappStorage,
|
|
354
|
+
entity_id: vector<u8>,
|
|
355
|
+
ctx: &mut TxContext,
|
|
356
|
+
) {
|
|
357
|
+
${adminCheck}
|
|
358
|
+
dubhe::dapp_system::create_and_share_typed_object<DappKey, ${markerName}>(
|
|
359
|
+
dapp_key::new(), dapp_storage, TYPE_TAG, entity_id, ctx
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
public(package) fun destroy_${objKey}(
|
|
364
|
+
dapp_storage: &mut DappStorage,
|
|
365
|
+
storage: ${fullStorageType},
|
|
366
|
+
_ctx: &TxContext,
|
|
367
|
+
) {
|
|
368
|
+
dubhe::dapp_system::destroy_typed_object<DappKey, ${markerName}>(
|
|
369
|
+
dapp_key::new(), dapp_storage, storage
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
`;
|
|
374
|
+
|
|
375
|
+
await formatAndWriteMove(code, path.join(outputDir, `${objKey}.move`), 'formatAndWriteMove');
|
|
376
|
+
}
|
|
377
|
+
}
|