suidouble 0.0.12 → 0.0.14
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/README.md
CHANGED
|
@@ -155,6 +155,16 @@ const res = await contract.moveCall('chat', 'post', ['0x10cded4f9df05e37b44e3be2
|
|
|
155
155
|
}
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
If you need to transfer some SUI as part of executing contract method, you can use a magic parameter in form of {type: 'SUI', amount: 400000000000n} where 400000000000 is the amount of MIST you want to send. SuiPackageModule will convert this amount to Coin object using Transactions.SplitCoins method.
|
|
159
|
+
|
|
160
|
+
`amount: 400000000000n`, `amount: '400000000000'`, `amount: 400000000000` will work too
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
const moveCallResult = await contract.moveCall('suidouble_chat', 'post_pay', [chatShopObjectId, {type: 'SUI', amount: 400000000000n}, messageText, 'metadata']);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
@todo: sending other Coins
|
|
167
|
+
|
|
158
168
|
##### fetching objects
|
|
159
169
|
|
|
160
170
|
There's instance of SuiMemoryObjectStorage attached to every SuiMaster instance. Every smart contract method call adds created and mutated objects to it. You can also attach any object with it's address (id).
|
|
@@ -297,6 +307,7 @@ suiInBrowser.addEventListener('connected', async()=>{
|
|
|
297
307
|
### Todo
|
|
298
308
|
|
|
299
309
|
- subscribe to events
|
|
310
|
+
- sending other coins as contract methods execution
|
|
300
311
|
- suiobject invalidation/fetching optimization
|
|
301
312
|
- better documentation
|
|
302
|
-
- unit tests
|
|
313
|
+
- unit tests coverage to 90%+
|
package/lib/SuiPackageModule.js
CHANGED
|
@@ -79,7 +79,13 @@ class SuiPackageModule extends SuiCommonMethods {
|
|
|
79
79
|
|
|
80
80
|
const callArgs = [];
|
|
81
81
|
for (let param of params) {
|
|
82
|
-
|
|
82
|
+
if (param && param.type && param.amount && param.type === 'SUI') {
|
|
83
|
+
let amount = BigInt(param.amount);
|
|
84
|
+
const coin = tx.add(sui.Transactions.SplitCoins(tx.gas, [tx.pure(amount)]));
|
|
85
|
+
callArgs.push(coin);
|
|
86
|
+
} else {
|
|
87
|
+
callArgs.push(tx.pure(param));
|
|
88
|
+
}
|
|
83
89
|
}
|
|
84
90
|
tx.moveCall({
|
|
85
91
|
target: `${this._package.address}::${this._moduleName}::${methodName}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suidouble",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
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": {
|
|
@@ -237,7 +237,7 @@ test('testing paginatedResponse', async t => {
|
|
|
237
237
|
const anotherEventsResponse = await contract.fetchEvents('suidouble_chat');
|
|
238
238
|
let loopsInForEach = 0;
|
|
239
239
|
const idsInLoopDict = {};
|
|
240
|
-
await anotherEventsResponse.forEach(async (event)=>{
|
|
240
|
+
await anotherEventsResponse.forEach(async (event)=>{ //
|
|
241
241
|
if (!idsInLoopDict[event.parsedJson.id]) {
|
|
242
242
|
idsInLoopDict[event.parsedJson.id] = true;
|
|
243
243
|
loopsInForEach++;
|
|
@@ -247,6 +247,48 @@ test('testing paginatedResponse', async t => {
|
|
|
247
247
|
t.ok(loopsInForEach >= 60); // it's 60 in move code, but let's keep chat flexible
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
+
test('testing move call with coins', async t => {
|
|
251
|
+
const balanceWas = await suiMaster.getBalance();
|
|
252
|
+
|
|
253
|
+
const longMessageYouCanNotPostForFree = ('message ').padEnd(500, 'test');
|
|
254
|
+
// can't post it for free (as per contract design)
|
|
255
|
+
t.rejects(contract.moveCall('suidouble_chat', 'post', [chatShopObjectId, longMessageYouCanNotPostForFree, 'metadata']));
|
|
256
|
+
|
|
257
|
+
// but can post with with post_pay function sending some sui to it
|
|
258
|
+
const moveCallResult = await contract.moveCall('suidouble_chat', 'post_pay', [chatShopObjectId, {type: 'SUI', amount: 400000000000n}, longMessageYouCanNotPostForFree, 'metadata']);
|
|
259
|
+
|
|
260
|
+
// there're at least some object created
|
|
261
|
+
t.ok(moveCallResult.created.length > 0);
|
|
262
|
+
|
|
263
|
+
// by suidouble_chat contract design, ChatTopMessage is an object representing a thread,
|
|
264
|
+
// it always has at least one ChatResponse (with text of the very first message in thread)
|
|
265
|
+
let foundChatTopMessage = null;
|
|
266
|
+
let foundChatResponse = null;
|
|
267
|
+
let foundText = null;
|
|
268
|
+
moveCallResult.created.forEach((obj)=>{
|
|
269
|
+
if (obj.typeName == 'ChatTopMessage') {
|
|
270
|
+
foundChatTopMessage = true;
|
|
271
|
+
}
|
|
272
|
+
if (obj.typeName == 'ChatResponse') {
|
|
273
|
+
foundChatResponse = true;
|
|
274
|
+
foundText = obj.fields.text;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
t.ok(foundChatTopMessage);
|
|
279
|
+
t.ok(foundChatResponse);
|
|
280
|
+
|
|
281
|
+
// messageTextAsBytes = [].slice.call(new TextEncoder().encode(messageText)); // regular array with utf data
|
|
282
|
+
// suidouble_chat contract store text a bytes (easier to work with unicode things), let's convert it back to js string
|
|
283
|
+
foundText = new TextDecoder().decode(new Uint8Array(foundText));
|
|
284
|
+
|
|
285
|
+
t.equal(foundText, longMessageYouCanNotPostForFree);
|
|
286
|
+
|
|
287
|
+
const balanceNow = await suiMaster.getBalance();
|
|
288
|
+
|
|
289
|
+
t.ok( balanceNow <= (balanceWas - 400000000000n) );
|
|
290
|
+
});
|
|
291
|
+
|
|
250
292
|
test('stops local test node', async t => {
|
|
251
293
|
SuiLocalTestValidator.stop();
|
|
252
294
|
});
|
|
@@ -8,6 +8,7 @@ module suidouble_chat::suidouble_chat {
|
|
|
8
8
|
use sui::dynamic_object_field::{Self};
|
|
9
9
|
|
|
10
10
|
use sui::sui::SUI;
|
|
11
|
+
use sui::coin::{Self, Coin};
|
|
11
12
|
use sui::balance::{Self, Balance};
|
|
12
13
|
|
|
13
14
|
use std::debug;
|
|
@@ -15,11 +16,21 @@ module suidouble_chat::suidouble_chat {
|
|
|
15
16
|
use sui::event::emit;
|
|
16
17
|
|
|
17
18
|
/// Max text length.
|
|
19
|
+
const MAX_TEXT_LENGTH_FREE: u64 = 256;
|
|
18
20
|
const MAX_TEXT_LENGTH: u64 = 512;
|
|
19
21
|
|
|
22
|
+
/// Min price for a long message
|
|
23
|
+
const MIN_PRICE: u64 = 100;
|
|
24
|
+
|
|
20
25
|
/// Text size overflow.
|
|
21
26
|
const ETextOverflow: u64 = 0;
|
|
22
27
|
|
|
28
|
+
/// Not enough funds to pay for the good in question
|
|
29
|
+
const EInsufficientFunds: u64 = 1;
|
|
30
|
+
|
|
31
|
+
/// For when supplied Coin is zero.
|
|
32
|
+
const EZeroAmount: u64 = 2;
|
|
33
|
+
|
|
23
34
|
// ======== Events =========
|
|
24
35
|
|
|
25
36
|
/// Event. When a new chat has been created.
|
|
@@ -86,8 +97,50 @@ module suidouble_chat::suidouble_chat {
|
|
|
86
97
|
text: vector<u8>,
|
|
87
98
|
metadata: vector<u8>,
|
|
88
99
|
ctx: &mut TxContext,
|
|
100
|
+
) {
|
|
101
|
+
assert!(length(&text) <= MAX_TEXT_LENGTH_FREE, ETextOverflow);
|
|
102
|
+
let id = object::new(ctx);
|
|
103
|
+
let chat_response_id = object::new(ctx);
|
|
104
|
+
|
|
105
|
+
emit(ChatTopMessageCreated { id: object::uid_to_inner(&id), top_response_id: object::uid_to_inner(&chat_response_id), });
|
|
106
|
+
emit(ChatResponseCreated { id: object::uid_to_inner(&chat_response_id), top_message_id: object::uid_to_inner(&id), seq_n: 0 });
|
|
107
|
+
|
|
108
|
+
let chat_top_message = ChatTopMessage {
|
|
109
|
+
id: id,
|
|
110
|
+
chat_shop_id: object::id(chat_shop),
|
|
111
|
+
author: tx_context::sender(ctx),
|
|
112
|
+
chat_top_response_id: object::uid_to_inner(&chat_response_id),
|
|
113
|
+
responses_count: 0,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let chat_response = ChatResponse {
|
|
117
|
+
id: chat_response_id,
|
|
118
|
+
chat_top_message_id: object::id(&chat_top_message),
|
|
119
|
+
author: tx_context::sender(ctx),
|
|
120
|
+
text: text,
|
|
121
|
+
metadata,
|
|
122
|
+
seq_n: 0,
|
|
123
|
+
};
|
|
124
|
+
dynamic_object_field::add(&mut chat_top_message.id, b"as_chat_response", chat_response);
|
|
125
|
+
|
|
126
|
+
transfer::share_object(chat_top_message);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Mint (post) a ChatTopMessage object without referencing another object.
|
|
130
|
+
public entry fun post_pay(
|
|
131
|
+
chat_shop: &mut ChatShop,
|
|
132
|
+
sui: Coin<SUI>,
|
|
133
|
+
text: vector<u8>,
|
|
134
|
+
metadata: vector<u8>,
|
|
135
|
+
ctx: &mut TxContext,
|
|
89
136
|
) {
|
|
90
137
|
assert!(length(&text) <= MAX_TEXT_LENGTH, ETextOverflow);
|
|
138
|
+
assert!(coin::value(&sui) > 0, EZeroAmount);
|
|
139
|
+
assert!(coin::value(&sui) >= MIN_PRICE, EInsufficientFunds);
|
|
140
|
+
|
|
141
|
+
let sui_balance = coin::into_balance(sui);
|
|
142
|
+
balance::join(&mut chat_shop.balance, sui_balance);
|
|
143
|
+
|
|
91
144
|
let id = object::new(ctx);
|
|
92
145
|
let chat_response_id = object::new(ctx);
|
|
93
146
|
|
|
@@ -115,6 +168,7 @@ module suidouble_chat::suidouble_chat {
|
|
|
115
168
|
transfer::share_object(chat_top_message);
|
|
116
169
|
}
|
|
117
170
|
|
|
171
|
+
|
|
118
172
|
/// Mint (post) a ChatResponse object
|
|
119
173
|
public entry fun reply(
|
|
120
174
|
chat_top_message: &mut ChatTopMessage,
|
|
@@ -122,7 +176,7 @@ module suidouble_chat::suidouble_chat {
|
|
|
122
176
|
metadata: vector<u8>,
|
|
123
177
|
ctx: &mut TxContext,
|
|
124
178
|
) {
|
|
125
|
-
assert!(length(&text) <=
|
|
179
|
+
assert!(length(&text) <= MAX_TEXT_LENGTH_FREE, ETextOverflow);
|
|
126
180
|
|
|
127
181
|
let dynamic_field_exists = dynamic_object_field::exists_(&chat_top_message.id, b"as_chat_response");
|
|
128
182
|
if (dynamic_field_exists) {
|
|
@@ -148,6 +202,45 @@ module suidouble_chat::suidouble_chat {
|
|
|
148
202
|
transfer::transfer(chat_response, tx_context::sender(ctx));
|
|
149
203
|
}
|
|
150
204
|
|
|
205
|
+
|
|
206
|
+
// /// Mint (post) a ChatResponse object
|
|
207
|
+
// public entry fun reply_pay(
|
|
208
|
+
// chat_top_message: &mut ChatTopMessage,
|
|
209
|
+
// sui: Coin<SUI>,
|
|
210
|
+
// text: vector<u8>,
|
|
211
|
+
// metadata: vector<u8>,
|
|
212
|
+
// ctx: &mut TxContext,
|
|
213
|
+
// ) acquires ChatShop {
|
|
214
|
+
// assert!(length(&text) <= MAX_TEXT_LENGTH, ETextOverflow);
|
|
215
|
+
|
|
216
|
+
// let dynamic_field_exists = dynamic_object_field::exists_(&chat_top_message.id, b"as_chat_response");
|
|
217
|
+
// if (dynamic_field_exists) {
|
|
218
|
+
// let top_level_chat_response = dynamic_object_field::remove<vector<u8>, ChatResponse>(&mut chat_top_message.id, b"as_chat_response");
|
|
219
|
+
// transfer::transfer(top_level_chat_response, chat_top_message.author);
|
|
220
|
+
// };
|
|
221
|
+
|
|
222
|
+
// chat_top_message.responses_count = chat_top_message.responses_count + 1;
|
|
223
|
+
|
|
224
|
+
// let chat_shop = borrow_global_mut<ChatShop>(id_to_address(&chat_top_message.chat_shop_id));
|
|
225
|
+
// let sui_balance = coin::into_balance(sui);
|
|
226
|
+
// balance::join(&mut chat_shop.balance, sui_balance);
|
|
227
|
+
|
|
228
|
+
// let id = object::new(ctx);
|
|
229
|
+
|
|
230
|
+
// 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 });
|
|
231
|
+
|
|
232
|
+
// let chat_response = ChatResponse {
|
|
233
|
+
// id: id,
|
|
234
|
+
// chat_top_message_id: object::id(chat_top_message),
|
|
235
|
+
// author: tx_context::sender(ctx),
|
|
236
|
+
// text: text,
|
|
237
|
+
// metadata,
|
|
238
|
+
// seq_n: chat_top_message.responses_count,
|
|
239
|
+
// };
|
|
240
|
+
|
|
241
|
+
// transfer::transfer(chat_response, tx_context::sender(ctx));
|
|
242
|
+
// }
|
|
243
|
+
|
|
151
244
|
/// Mint a lot of responses (we need this to unit test SuiPaginatedResponse faster)
|
|
152
245
|
public entry fun fill(
|
|
153
246
|
chat_top_message: &mut ChatTopMessage,
|
|
@@ -236,6 +329,22 @@ module suidouble_chat::suidouble_chat {
|
|
|
236
329
|
// test_scenario::return_to_sender(scenario, chat_top_message);
|
|
237
330
|
};
|
|
238
331
|
|
|
332
|
+
test_scenario::next_tx(scenario, anybody);
|
|
333
|
+
{
|
|
334
|
+
let chat_shop = test_scenario::take_shared<ChatShop>(scenario);
|
|
335
|
+
debug::print(&chat_shop);
|
|
336
|
+
|
|
337
|
+
// let chat_shop_ref = &chat_shop;
|
|
338
|
+
let ctx = test_scenario::ctx(scenario);
|
|
339
|
+
post_pay(&mut chat_shop, coin::mint_for_testing<SUI>(100, ctx), b"test", b"metadata", test_scenario::ctx(scenario));
|
|
340
|
+
|
|
341
|
+
// post(chat_shop_ref, b"test", b"metadata", test_scenario::ctx(scenario));
|
|
342
|
+
|
|
343
|
+
debug::print(&chat_shop);
|
|
344
|
+
|
|
345
|
+
test_scenario::return_shared(chat_shop);
|
|
346
|
+
};
|
|
347
|
+
|
|
239
348
|
test_scenario::next_tx(scenario, anybody);
|
|
240
349
|
{
|
|
241
350
|
// let chat_top_message = test_scenario::take_from_sender<ChatTopMessage>(scenario);
|