suidouble 0.0.8 → 0.0.9
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/lib/SuiEvent.js +7 -0
- package/lib/SuiMemoryObjectStorage.js +6 -0
- package/lib/SuiPackage.js +13 -3
- package/lib/SuiPackageModule.js +0 -2
- package/package.json +7 -1
- package/test/sui_master_onlocal.test.js +172 -0
- package/test/test_move_contracts/suidouble_chat/Move.lock +20 -0
- package/test/test_move_contracts/suidouble_chat/Move.toml +10 -0
- package/test/test_move_contracts/suidouble_chat/compiled.json +1 -0
- package/test/test_move_contracts/suidouble_chat/sources/suidouble_chat.move +221 -0
package/lib/SuiEvent.js
CHANGED
|
@@ -12,6 +12,13 @@ class SuiEvent extends SuiCommonMethods {
|
|
|
12
12
|
this._data = params.data || {};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* In module type name, without package and module prefix
|
|
17
|
+
*/
|
|
18
|
+
get typeName() {
|
|
19
|
+
return this._data ? this._data.type.split('::').pop() : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
get data() {
|
|
16
23
|
return this._data;
|
|
17
24
|
}
|
|
@@ -11,6 +11,12 @@ class SuiMemoryObjectStorage extends SuiCommonMethods {
|
|
|
11
11
|
return Object.values(this._objects);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
findMostRecentByTypeName(typeName) {
|
|
15
|
+
return this.findMostRecent((object) => {
|
|
16
|
+
return (object.typeName == typeName);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
find(filterFunction) {
|
|
15
21
|
for (const id in this._objects) {
|
|
16
22
|
if (filterFunction(this._objects[id])) {
|
package/lib/SuiPackage.js
CHANGED
|
@@ -43,6 +43,19 @@ class SuiPackage extends SuiObject {
|
|
|
43
43
|
return this._modules;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
async getModule(moduleName) {
|
|
47
|
+
await this.checkOnChainIfNeeded();
|
|
48
|
+
return this._modules[moduleName];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get isBuilt() {
|
|
52
|
+
return this._isBuilt;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get version() {
|
|
56
|
+
return Number(this._publishedVersion); // return as Number in getter
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
async isOnChain() {
|
|
47
60
|
try {
|
|
48
61
|
await this.checkOnChainIfNeeded();
|
|
@@ -223,9 +236,6 @@ class SuiPackage extends SuiObject {
|
|
|
223
236
|
},
|
|
224
237
|
});
|
|
225
238
|
|
|
226
|
-
console.log('result', result);
|
|
227
|
-
|
|
228
|
-
|
|
229
239
|
if (result?.data?.version) {
|
|
230
240
|
this._publishedVersion = BigInt(result?.data?.version); // not sure, but it's string in response, so let's convert it to bigint, who knows
|
|
231
241
|
this._isPublished = true;
|
package/lib/SuiPackageModule.js
CHANGED
|
@@ -104,8 +104,6 @@ class SuiPackageModule extends SuiCommonMethods {
|
|
|
104
104
|
const listMutated = [];
|
|
105
105
|
const listDeleted = [];
|
|
106
106
|
|
|
107
|
-
console.error('result', result);
|
|
108
|
-
|
|
109
107
|
for (const objectChange of result.objectChanges) {
|
|
110
108
|
if (objectChange.objectId) {
|
|
111
109
|
if (this.objectStorage.byAddress(objectChange.objectId)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suidouble",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Set of provider, package and object classes for javascript representation of Sui Move smart contracts. Use same code for publishing, upgrading, integration testing, interaction with smart contracts and integration in browser web3 dapps",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,5 +26,11 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"tap": "^16.3.4"
|
|
29
|
+
},
|
|
30
|
+
"tap": {
|
|
31
|
+
"branches": 90,
|
|
32
|
+
"lines": 90,
|
|
33
|
+
"functions": 90,
|
|
34
|
+
"statements": 90
|
|
29
35
|
}
|
|
30
36
|
}
|
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const t = require('tap');
|
|
4
4
|
const { test } = t;
|
|
5
|
+
const path = require('path');
|
|
5
6
|
|
|
6
7
|
const { SuiMaster, SuiLocalTestValidator } = require('..');
|
|
7
8
|
|
|
8
9
|
let suiLocalTestValidator = null;
|
|
9
10
|
let suiMaster = null;
|
|
11
|
+
let contract = null;
|
|
12
|
+
|
|
13
|
+
let contractAddressV1 = null;
|
|
14
|
+
let contractAddressV2 = null;
|
|
15
|
+
|
|
16
|
+
let chatShopObjectId = null;
|
|
10
17
|
|
|
11
18
|
test('spawn local test node', async t => {
|
|
12
19
|
suiLocalTestValidator = await SuiLocalTestValidator.launch();
|
|
@@ -34,6 +41,171 @@ test('request sui from faucet', async t => {
|
|
|
34
41
|
t.ok(balanceAfter > balanceBefore);
|
|
35
42
|
});
|
|
36
43
|
|
|
44
|
+
test('attach a local package', async t => {
|
|
45
|
+
contract = suiMaster.addPackage({
|
|
46
|
+
path: path.join(__dirname, './test_move_contracts/suidouble_chat/'),
|
|
47
|
+
});
|
|
48
|
+
// there's nothing in contract yet, it's not built, not published (don't know it's id on chain)
|
|
49
|
+
//
|
|
50
|
+
// we can check that contract's objectStorage is the same instance as on master. Reminder, it's shared between all connections to same chain
|
|
51
|
+
t.equal(contract.objectStorage, suiMaster.objectStorage);
|
|
52
|
+
|
|
53
|
+
// lets try to build it
|
|
54
|
+
await contract.build();
|
|
55
|
+
|
|
56
|
+
t.ok(contract.isBuilt);
|
|
57
|
+
|
|
58
|
+
// and publish
|
|
59
|
+
|
|
60
|
+
await contract.publish();
|
|
61
|
+
|
|
62
|
+
t.ok(contract.address); // there should be some address
|
|
63
|
+
t.ok(contract.id); // same as id
|
|
64
|
+
t.ok(`${contract.address}`.indexOf('0x') === 0); // adress is string starting with '0x'
|
|
65
|
+
|
|
66
|
+
t.equal(contract.version, 1);
|
|
67
|
+
|
|
68
|
+
// there should be module 'suidouble_chat' on the contract we published
|
|
69
|
+
t.ok(contract.modules.suidouble_chat);
|
|
70
|
+
|
|
71
|
+
// we can check that contract's objectStorage is the same instance as on master. Reminder, it's shared between all connections to same chain
|
|
72
|
+
t.equal(contract.modules.suidouble_chat.objectStorage, suiMaster.objectStorage);
|
|
73
|
+
|
|
74
|
+
contractAddressV1 = contract.address;
|
|
75
|
+
|
|
76
|
+
// we'd need to .build() again after changes here. But it lets you upgrade package with very same code
|
|
77
|
+
await contract.upgrade();
|
|
78
|
+
|
|
79
|
+
t.not(contract.address, contractAddressV1);
|
|
80
|
+
t.equal(contract.version, 2);
|
|
81
|
+
|
|
82
|
+
contractAddressV2 = contract.address;
|
|
83
|
+
|
|
84
|
+
// let's quickly check it worked, there should be event ChatShopCreated created and we can fetch it from contract's module
|
|
85
|
+
const eventsResponse = await contract.modules.suidouble_chat.fetchEvents();
|
|
86
|
+
// response is an instance of SuiPaginatedResponse
|
|
87
|
+
let foundChatShopCreatedEvent = false;
|
|
88
|
+
for (const event of eventsResponse.data) {
|
|
89
|
+
if (event.typeName === 'ChatShopCreated') {
|
|
90
|
+
foundChatShopCreatedEvent = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
t.ok(foundChatShopCreatedEvent);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('attach a package by address on the blockchain', async t => {
|
|
98
|
+
suiMaster = new SuiMaster({provider: suiLocalTestValidator, as: 'somebody'});
|
|
99
|
+
await suiMaster.initialize();
|
|
100
|
+
|
|
101
|
+
contract = await suiMaster.addPackage({
|
|
102
|
+
id: contractAddressV2,
|
|
103
|
+
});
|
|
104
|
+
const eventsResponse = await contract.fetchEvents('suidouble_chat');
|
|
105
|
+
|
|
106
|
+
let foundChatShopCreatedEvent = false;
|
|
107
|
+
for (const event of eventsResponse.data) {
|
|
108
|
+
if (event.typeName === 'ChatShopCreated') {
|
|
109
|
+
foundChatShopCreatedEvent = true;
|
|
110
|
+
chatShopObjectId = event.parsedJson.id;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// there should be ChatShopCreated event
|
|
115
|
+
t.ok(foundChatShopCreatedEvent);
|
|
116
|
+
|
|
117
|
+
// it should have id of ChatShop object
|
|
118
|
+
t.ok(chatShopObjectId);
|
|
119
|
+
|
|
120
|
+
// there should be module 'suidouble_chat' on the contract
|
|
121
|
+
t.ok(contract.modules.suidouble_chat);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('can find a package on the blockchain by expected module name (in owned)', async t => {
|
|
125
|
+
suiMaster = new SuiMaster({provider: suiLocalTestValidator, as: 'somebody'});
|
|
126
|
+
await suiMaster.initialize();
|
|
127
|
+
|
|
128
|
+
contract = await suiMaster.addPackage({
|
|
129
|
+
modules: ['suidouble_chat'],
|
|
130
|
+
});
|
|
131
|
+
const eventsResponse = await contract.fetchEvents('suidouble_chat');
|
|
132
|
+
|
|
133
|
+
let foundChatShopCreatedEvent = false;
|
|
134
|
+
for (const event of eventsResponse.data) {
|
|
135
|
+
if (event.typeName === 'ChatShopCreated') {
|
|
136
|
+
foundChatShopCreatedEvent = true;
|
|
137
|
+
chatShopObjectId = event.parsedJson.id;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// there should be ChatShopCreated event
|
|
142
|
+
t.ok(foundChatShopCreatedEvent);
|
|
143
|
+
// it should have id of ChatShop object
|
|
144
|
+
t.ok(chatShopObjectId);
|
|
145
|
+
|
|
146
|
+
// there should be module 'suidouble_chat' on the contract
|
|
147
|
+
t.ok(contract.modules.suidouble_chat);
|
|
148
|
+
|
|
149
|
+
// it should find most recent version of the package
|
|
150
|
+
t.equal(contract.version, 2);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('execute contract methods', async t => {
|
|
154
|
+
const moveCallResult = await contract.moveCall('suidouble_chat', 'post', [chatShopObjectId, 'the message', 'metadata']);
|
|
155
|
+
|
|
156
|
+
// there're at least some object created
|
|
157
|
+
t.ok(moveCallResult.created.length > 0);
|
|
158
|
+
|
|
159
|
+
// by suidouble_chat contract design, ChatTopMessage is an object representing a thread,
|
|
160
|
+
// it always has at least one ChatResponse (with text of the very first message in thread)
|
|
161
|
+
let foundChatTopMessage = null;
|
|
162
|
+
let foundChatResponse = null;
|
|
163
|
+
let foundText = null;
|
|
164
|
+
moveCallResult.created.forEach((obj)=>{
|
|
165
|
+
if (obj.typeName == 'ChatTopMessage') {
|
|
166
|
+
foundChatTopMessage = true;
|
|
167
|
+
}
|
|
168
|
+
if (obj.typeName == 'ChatResponse') {
|
|
169
|
+
foundChatResponse = true;
|
|
170
|
+
foundText = obj.fields.text;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
t.ok(foundChatTopMessage);
|
|
175
|
+
t.ok(foundChatResponse);
|
|
176
|
+
|
|
177
|
+
// messageTextAsBytes = [].slice.call(new TextEncoder().encode(messageText)); // regular array with utf data
|
|
178
|
+
// suidouble_chat contract store text a bytes (easier to work with unicode things), let's convert it back to js string
|
|
179
|
+
foundText = new TextDecoder().decode(new Uint8Array(foundText));
|
|
180
|
+
|
|
181
|
+
t.equal(foundText, 'the message');
|
|
182
|
+
|
|
183
|
+
// now lets post a reply to the thread
|
|
184
|
+
// we need a ChatTopMessage to pass to move's 'reply' function
|
|
185
|
+
// we can find it via ChatTopMessageCreated events or from previous method execution results
|
|
186
|
+
// find from local objectStorage (where previous results are stored)
|
|
187
|
+
const chatTopMessage = contract.objectStorage.findMostRecentByTypeName('ChatTopMessage');
|
|
188
|
+
t.ok(chatTopMessage);
|
|
189
|
+
|
|
190
|
+
const responseTextAsBytes = [].slice.call(new TextEncoder().encode('ขอบคุณครับ, 🇺🇦')); // regular array with utf data
|
|
191
|
+
const moveCallResult2 = await contract.moveCall('suidouble_chat', 'reply', [chatTopMessage.id, responseTextAsBytes, 'metadata']);
|
|
192
|
+
|
|
193
|
+
// there're at least some object created
|
|
194
|
+
t.ok(moveCallResult2.created.length > 0);
|
|
195
|
+
|
|
196
|
+
let responseText = null;
|
|
197
|
+
moveCallResult2.created.forEach((obj)=>{
|
|
198
|
+
if (obj.typeName == 'ChatResponse') {
|
|
199
|
+
responseText = obj.fields.text;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// messageTextAsBytes = [].slice.call(new TextEncoder().encode(messageText)); // regular array with utf data
|
|
203
|
+
// suidouble_chat contract store text a bytes (easier to work with unicode things), let's convert it back to js string
|
|
204
|
+
responseText = new TextDecoder().decode(new Uint8Array(responseText));
|
|
205
|
+
|
|
206
|
+
t.equal(responseText, 'ขอบคุณครับ, 🇺🇦');
|
|
207
|
+
});
|
|
208
|
+
|
|
37
209
|
test('stops local test node', async t => {
|
|
38
210
|
SuiLocalTestValidator.stop();
|
|
39
211
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @generated by Move, please check-in and do not edit manually.
|
|
2
|
+
|
|
3
|
+
[move]
|
|
4
|
+
version = 0
|
|
5
|
+
|
|
6
|
+
dependencies = [
|
|
7
|
+
{ name = "Sui" },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[[move.package]]
|
|
11
|
+
name = "MoveStdlib"
|
|
12
|
+
source = { git = "https://github.com/MystenLabs/sui.git", rev = "testnet", subdir = "crates/sui-framework/packages/move-stdlib" }
|
|
13
|
+
|
|
14
|
+
[[move.package]]
|
|
15
|
+
name = "Sui"
|
|
16
|
+
source = { git = "https://github.com/MystenLabs/sui.git", rev = "testnet", subdir = "crates/sui-framework/packages/sui-framework" }
|
|
17
|
+
|
|
18
|
+
dependencies = [
|
|
19
|
+
{ name = "MoveStdlib" },
|
|
20
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "suidouble_chat"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
|
|
5
|
+
[dependencies]
|
|
6
|
+
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "testnet" }
|
|
7
|
+
|
|
8
|
+
[addresses]
|
|
9
|
+
suidouble_chat = "0x0"
|
|
10
|
+
sui = "0000000000000000000000000000000000000000000000000000000000000002"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"modules":["oRzrCwYAAAALAQAQAhAyA0JQBJIBGgWsAYcBB7MCtQMI6AVABqgGKArQBlEMoQfQAg3xCQYAJAEOARIBFAEaASMBKAEpAAUDAAAHAwAAAwMAAAEIAAAECAAABgwAAAIMAAEABAEAAQQIBwAECwQABQkCAAcKAgAAFwABAAAbAgEAAB4DAQABKwEOAQACDBcBAgcMAhUZGgEHAh0bHAIHDAMTDAEBAwQWFAoBCAQZAAQABCoJCgAGIgwBAQgGKAgBAQgHIAUGAAwHBwsDDQsPBxIHEwgPCBUEFgsVBRgGFgwdAQcICwAEBggECgIKAgcICwQHCAUKAgoCBwgLAQgJAQYICwEFAQgDAgkABQEGCAkBCAgBCAABCQABCAoBCwcBCQABCAQICAkICAUICAgGCAkIBQgJAQIBCAEBCAIBBgkAAQgFAgoCCAYDBwgJCQAJAQEKAgIGCAkJAAEBAgcICQkAAQkBAQgGB0JhbGFuY2UMQ2hhdE93bmVyQ2FwDENoYXRSZXNwb25zZRNDaGF0UmVzcG9uc2VDcmVhdGVkCENoYXRTaG9wD0NoYXRTaG9wQ3JlYXRlZA5DaGF0VG9wTWVzc2FnZRVDaGF0VG9wTWVzc2FnZUNyZWF0ZWQCSUQDU1VJCVR4Q29udGV4dANVSUQDYWRkBmF1dGhvcgdiYWxhbmNlDGNoYXRfc2hvcF9pZBNjaGF0X3RvcF9tZXNzYWdlX2lkFGNoYXRfdG9wX3Jlc3BvbnNlX2lkFGR5bmFtaWNfb2JqZWN0X2ZpZWxkBGVtaXQFZXZlbnQHZXhpc3RzXwJpZARpbml0CG1ldGFkYXRhA25ldwZvYmplY3QEcG9zdAVwcmljZQZyZW1vdmUFcmVwbHkPcmVzcG9uc2VzX2NvdW50BnNlbmRlcgVzZXFfbgxzaGFyZV9vYmplY3QDc3VpDnN1aWRvdWJsZV9jaGF0BHRleHQOdG9wX21lc3NhZ2VfaWQPdG9wX3Jlc3BvbnNlX2lkCHRyYW5zZmVyCnR4X2NvbnRleHQMdWlkX3RvX2lubmVyBHplcm8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwgAAgAAAAAAAAMIAAAAAAAAAAAKAhEQYXNfY2hhdF9yZXNwb25zZQACARYICAECAhYICCcICAICAxYICCYICCEDAwIBFggJBAIDFggJHAMOCwcBCAoFAgUWCAkPCAgRCAgNBR8DBgIGFggJEAgIDQUlCgIYCgIhAwAAAAAEFAoAEQkSAwoALhENOAALABEJDAEOAREKEgA4AQsBBugDAAAAAAAAOAISBDgDAgEBBAAQRQ4BQREHACUEBgUMCwMBCwABBwEnCgMRCQwLCgMRCQwJDgsRCg4JEQoSATgEDgkRCg4LEQoGAAAAAAAAAAASAjgFCwsMBAsAOAYMBQoDLhENDAYOCREKDAcLBAsFCwcLBgYAAAAAAAAAABIFDAoLCQ4KOAcLAy4RDQsBCwIGAAAAAAAAAAASBgwIDQoPAAcCCwg4CAsKOAkCAgEEAARADgFBEQcAJQQGBQwLAwELAAEHAScKABAABwI4CgQZCgAPAAcCOAsKABABFDgMCgAQAhQGAQAAAAAAAAAWCgAPAhUKAxEJDAQOBBEKCgAQABEKCgAQAhQSAjgFCwQKAC44BwoDLhENCwELAgsAEAIUEgYLAy4RDTgMAgUABQMFBAA="],"dependencies":["0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002"],"digest":[160,183,189,147,87,240,81,132,236,190,75,225,74,68,254,14,106,61,173,195,201,119,140,108,230,103,29,238,48,162,45,170]}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
|
|
2
|
+
module suidouble_chat::suidouble_chat {
|
|
3
|
+
use sui::object::{Self, UID, ID};
|
|
4
|
+
use sui::transfer;
|
|
5
|
+
use sui::tx_context::{Self, TxContext};
|
|
6
|
+
use std::vector::length;
|
|
7
|
+
|
|
8
|
+
use sui::dynamic_object_field::{Self};
|
|
9
|
+
|
|
10
|
+
use sui::sui::SUI;
|
|
11
|
+
use sui::balance::{Self, Balance};
|
|
12
|
+
|
|
13
|
+
use std::debug;
|
|
14
|
+
|
|
15
|
+
use sui::event::emit;
|
|
16
|
+
|
|
17
|
+
/// Max text length.
|
|
18
|
+
const MAX_TEXT_LENGTH: u64 = 512;
|
|
19
|
+
|
|
20
|
+
/// Text size overflow.
|
|
21
|
+
const ETextOverflow: u64 = 0;
|
|
22
|
+
|
|
23
|
+
// ======== Events =========
|
|
24
|
+
|
|
25
|
+
/// Event. When a new chat has been created.
|
|
26
|
+
struct ChatShopCreated has copy, drop { id: ID }
|
|
27
|
+
struct ChatTopMessageCreated has copy, drop { id: ID, top_response_id: ID }
|
|
28
|
+
struct ChatResponseCreated has copy, drop { id: ID, top_message_id: ID, seq_n: u64 }
|
|
29
|
+
|
|
30
|
+
/// Capability that grants an owner the right to collect profits.
|
|
31
|
+
struct ChatOwnerCap has key { id: UID }
|
|
32
|
+
|
|
33
|
+
/// A shared object. `key` ability is required.
|
|
34
|
+
struct ChatShop has key {
|
|
35
|
+
id: UID,
|
|
36
|
+
price: u64,
|
|
37
|
+
balance: Balance<SUI>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
struct ChatTopMessage has key, store {
|
|
41
|
+
id: UID,
|
|
42
|
+
chat_shop_id: ID,
|
|
43
|
+
chat_top_response_id: ID,
|
|
44
|
+
author: address,
|
|
45
|
+
responses_count: u64,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
struct ChatResponse has key, store {
|
|
49
|
+
id: UID,
|
|
50
|
+
chat_top_message_id: ID,
|
|
51
|
+
author: address,
|
|
52
|
+
text: vector<u8>,
|
|
53
|
+
// app-specific metadata. We do not enforce a metadata format and delegate this to app layer.
|
|
54
|
+
metadata: vector<u8>,
|
|
55
|
+
seq_n: u64, // n of message in thread
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Init function is often ideal place for initializing
|
|
59
|
+
/// a shared object as it is called only once.
|
|
60
|
+
///
|
|
61
|
+
/// To share an object `transfer::share_object` is used.
|
|
62
|
+
fun init(ctx: &mut TxContext) {
|
|
63
|
+
transfer::transfer(ChatOwnerCap {
|
|
64
|
+
id: object::new(ctx)
|
|
65
|
+
}, tx_context::sender(ctx));
|
|
66
|
+
|
|
67
|
+
let id = object::new(ctx);
|
|
68
|
+
emit(ChatShopCreated { id: object::uid_to_inner(&id) });
|
|
69
|
+
|
|
70
|
+
// Share the object to make it accessible to everyone!
|
|
71
|
+
transfer::share_object(ChatShop {
|
|
72
|
+
id: id,
|
|
73
|
+
price: 1000,
|
|
74
|
+
balance: balance::zero()
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// /// Simple ChatResponse.text getter.
|
|
79
|
+
// public fun text(chat_response: &ChatResponse): String {
|
|
80
|
+
// chat_response.text
|
|
81
|
+
// }
|
|
82
|
+
|
|
83
|
+
/// Mint (post) a chatMessage object without referencing another object.
|
|
84
|
+
public entry fun post(
|
|
85
|
+
chat_shop: &ChatShop,
|
|
86
|
+
text: vector<u8>,
|
|
87
|
+
metadata: vector<u8>,
|
|
88
|
+
ctx: &mut TxContext,
|
|
89
|
+
) {
|
|
90
|
+
assert!(length(&text) <= MAX_TEXT_LENGTH, ETextOverflow);
|
|
91
|
+
let id = object::new(ctx);
|
|
92
|
+
let chat_response_id = object::new(ctx);
|
|
93
|
+
|
|
94
|
+
emit(ChatTopMessageCreated { id: object::uid_to_inner(&id), top_response_id: object::uid_to_inner(&chat_response_id), });
|
|
95
|
+
emit(ChatResponseCreated { id: object::uid_to_inner(&chat_response_id), top_message_id: object::uid_to_inner(&id), seq_n: 0 });
|
|
96
|
+
|
|
97
|
+
let chat_top_message = ChatTopMessage {
|
|
98
|
+
id: id,
|
|
99
|
+
chat_shop_id: object::id(chat_shop),
|
|
100
|
+
author: tx_context::sender(ctx),
|
|
101
|
+
chat_top_response_id: object::uid_to_inner(&chat_response_id),
|
|
102
|
+
responses_count: 0,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
let chat_response = ChatResponse {
|
|
106
|
+
id: chat_response_id,
|
|
107
|
+
chat_top_message_id: object::id(&chat_top_message),
|
|
108
|
+
author: tx_context::sender(ctx),
|
|
109
|
+
text: text,
|
|
110
|
+
metadata,
|
|
111
|
+
seq_n: 0,
|
|
112
|
+
};
|
|
113
|
+
dynamic_object_field::add(&mut chat_top_message.id, b"as_chat_response", chat_response);
|
|
114
|
+
|
|
115
|
+
transfer::share_object(chat_top_message);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public entry fun reply(
|
|
119
|
+
chat_top_message: &mut ChatTopMessage,
|
|
120
|
+
text: vector<u8>,
|
|
121
|
+
metadata: vector<u8>,
|
|
122
|
+
ctx: &mut TxContext,
|
|
123
|
+
) {
|
|
124
|
+
assert!(length(&text) <= MAX_TEXT_LENGTH, ETextOverflow);
|
|
125
|
+
|
|
126
|
+
let dynamic_field_exists = dynamic_object_field::exists_(&chat_top_message.id, b"as_chat_response");
|
|
127
|
+
if (dynamic_field_exists) {
|
|
128
|
+
let top_level_chat_response = dynamic_object_field::remove<vector<u8>, ChatResponse>(&mut chat_top_message.id, b"as_chat_response");
|
|
129
|
+
transfer::transfer(top_level_chat_response, chat_top_message.author);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
chat_top_message.responses_count = chat_top_message.responses_count + 1;
|
|
133
|
+
|
|
134
|
+
let id = object::new(ctx);
|
|
135
|
+
|
|
136
|
+
emit(ChatResponseCreated { id: object::uid_to_inner(&id), top_message_id: object::uid_to_inner(&chat_top_message.id), seq_n: chat_top_message.responses_count });
|
|
137
|
+
|
|
138
|
+
let chat_response = ChatResponse {
|
|
139
|
+
id: id,
|
|
140
|
+
chat_top_message_id: object::id(chat_top_message),
|
|
141
|
+
author: tx_context::sender(ctx),
|
|
142
|
+
text: text,
|
|
143
|
+
metadata,
|
|
144
|
+
seq_n: chat_top_message.responses_count,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
transfer::transfer(chat_response, tx_context::sender(ctx));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
#[test]
|
|
153
|
+
public fun test_module_init() {
|
|
154
|
+
use sui::test_scenario;
|
|
155
|
+
|
|
156
|
+
// Create test address representing game admin
|
|
157
|
+
let admin = @0xBABE;
|
|
158
|
+
let somebody = @0xFAFE;
|
|
159
|
+
let anybody = @0xFAAE;
|
|
160
|
+
// let player = @0x0;
|
|
161
|
+
|
|
162
|
+
// First transaction to emulate module initialization
|
|
163
|
+
let scenario_val = test_scenario::begin(admin);
|
|
164
|
+
let scenario = &mut scenario_val;
|
|
165
|
+
|
|
166
|
+
// Run the module initializers
|
|
167
|
+
test_scenario::next_tx(scenario, admin);
|
|
168
|
+
{
|
|
169
|
+
init(test_scenario::ctx(scenario));
|
|
170
|
+
|
|
171
|
+
};
|
|
172
|
+
// Run the module initializers
|
|
173
|
+
test_scenario::next_tx(scenario, somebody);
|
|
174
|
+
{
|
|
175
|
+
let chat_shop = test_scenario::take_shared<ChatShop>(scenario);
|
|
176
|
+
// let chat_shop_ref = &chat_shop;
|
|
177
|
+
debug::print(&chat_shop);
|
|
178
|
+
|
|
179
|
+
let chat_shop_ref = &chat_shop;
|
|
180
|
+
post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
|
|
181
|
+
|
|
182
|
+
// post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
|
|
183
|
+
|
|
184
|
+
test_scenario::return_shared(chat_shop);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
test_scenario::next_tx(scenario, anybody);
|
|
188
|
+
{
|
|
189
|
+
// let chat_top_message = test_scenario::take_from_sender<ChatTopMessage>(scenario);
|
|
190
|
+
let chat_top_message = test_scenario::take_shared<ChatTopMessage>(scenario);
|
|
191
|
+
// let chat_shop_ref = &chat_shop;
|
|
192
|
+
debug::print(&chat_top_message);
|
|
193
|
+
debug::print(&mut chat_top_message);
|
|
194
|
+
|
|
195
|
+
// let chat_top_message_ref = &chat_top_message;
|
|
196
|
+
reply(&mut chat_top_message, b"response", b"metadata", test_scenario::ctx(scenario));
|
|
197
|
+
|
|
198
|
+
// post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
|
|
199
|
+
|
|
200
|
+
test_scenario::return_shared(chat_top_message);
|
|
201
|
+
// test_scenario::return_to_sender(scenario, chat_top_message);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
test_scenario::next_tx(scenario, anybody);
|
|
205
|
+
{
|
|
206
|
+
// let chat_top_message = test_scenario::take_from_sender<ChatTopMessage>(scenario);
|
|
207
|
+
let chat_top_message = test_scenario::take_shared<ChatTopMessage>(scenario);
|
|
208
|
+
// let chat_shop_ref = &chat_shop;
|
|
209
|
+
debug::print(&mut chat_top_message);
|
|
210
|
+
|
|
211
|
+
// let chat_top_message_ref = &chat_top_message;
|
|
212
|
+
reply(&mut chat_top_message, b"response", b"metadata", test_scenario::ctx(scenario));
|
|
213
|
+
|
|
214
|
+
// post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
|
|
215
|
+
|
|
216
|
+
test_scenario::return_shared(chat_top_message);
|
|
217
|
+
// test_scenario::return_to_sender(scenario, chat_top_message);
|
|
218
|
+
};
|
|
219
|
+
test_scenario::end(scenario_val);
|
|
220
|
+
}
|
|
221
|
+
}
|