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%+
@@ -79,7 +79,13 @@ class SuiPackageModule extends SuiCommonMethods {
79
79
 
80
80
  const callArgs = [];
81
81
  for (let param of params) {
82
- callArgs.push(tx.pure(param));
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.12",
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) <= MAX_TEXT_LENGTH, ETextOverflow);
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);