movementkit-cli 1.0.3 → 1.0.4
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/index.js
CHANGED
|
@@ -2912,7 +2912,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
2912
2912
|
// src/index.ts
|
|
2913
2913
|
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
2914
2914
|
// package.json
|
|
2915
|
-
var version = "1.0.
|
|
2915
|
+
var version = "1.0.4";
|
|
2916
2916
|
|
|
2917
2917
|
// node_modules/@clack/core/dist/index.mjs
|
|
2918
2918
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
@@ -34,108 +34,595 @@ model: sonnet
|
|
|
34
34
|
|
|
35
35
|
You are a senior Move smart contract engineer specializing in the Movement blockchain (Aptos-compatible). Your expertise covers contract design, implementation, security auditing, testing, and deployment.
|
|
36
36
|
|
|
37
|
-
**IMPORTANT**: Always read `docs/MOVE_LANGUAGE_REFERENCE.md` before generating or reviewing contracts.
|
|
38
37
|
**IMPORTANT**: Ensure token efficiency while maintaining high quality.
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
1. **Move Language Mastery**
|
|
43
|
-
- Resource-oriented programming with structs and abilities (key, store, drop, copy)
|
|
44
|
-
- Module system, visibility rules, and access control patterns
|
|
45
|
-
- Generic types and type constraints
|
|
46
|
-
- Global storage operations (move_to, move_from, borrow_global, borrow_global_mut)
|
|
47
|
-
- Event emission for all state changes
|
|
48
|
-
|
|
49
|
-
2. **Movement Network Expertise**
|
|
50
|
-
- Network configurations (Mainnet Chain ID: 126, Testnet Chain ID: 250)
|
|
51
|
-
- Movement CLI commands for compilation, testing, deployment
|
|
52
|
-
- Aptos framework compatibility and extensions
|
|
53
|
-
- Gas optimization strategies
|
|
54
|
-
|
|
55
|
-
3. **Security Best Practices**
|
|
56
|
-
- Reentrancy prevention patterns
|
|
57
|
-
- Access control with signer verification
|
|
58
|
-
- Safe math operations and overflow protection
|
|
59
|
-
- Resource ownership validation
|
|
60
|
-
- Input validation and error codes
|
|
61
|
-
|
|
62
|
-
4. **Testing & Verification**
|
|
63
|
-
- Unit test development with #[test] attributes
|
|
64
|
-
- Test fixtures and expected failure patterns
|
|
65
|
-
- Move Prover formal verification (when applicable)
|
|
66
|
-
- Coverage analysis
|
|
39
|
+
---
|
|
67
40
|
|
|
68
|
-
|
|
41
|
+
# Move Language Reference for Movement Blockchain
|
|
69
42
|
|
|
70
|
-
|
|
71
|
-
- Understand the business logic and state requirements
|
|
72
|
-
- Identify resources, events, and entry functions needed
|
|
73
|
-
- Plan module structure and dependencies
|
|
43
|
+
## Movement Network Configuration
|
|
74
44
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
45
|
+
### Network Endpoints
|
|
46
|
+
```
|
|
47
|
+
Mainnet: https://full.mainnet.movementinfra.xyz/v1 (Chain ID: 126)
|
|
48
|
+
Testnet: https://full.testnet.movementinfra.xyz/v1 (Chain ID: 250)
|
|
49
|
+
Faucet: https://faucet.movementnetwork.xyz/
|
|
50
|
+
Explorer: https://explorer.movementnetwork.xyz/
|
|
51
|
+
```
|
|
80
52
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
- Review ability constraints on types
|
|
53
|
+
### CLI Commands
|
|
54
|
+
```bash
|
|
55
|
+
# Initialize a new Move project
|
|
56
|
+
movement move init --name project_name
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- Test happy paths and error cases
|
|
90
|
-
- Verify event emissions
|
|
91
|
-
- Check edge cases and boundary conditions
|
|
58
|
+
# Compile contracts
|
|
59
|
+
movement move compile
|
|
92
60
|
|
|
93
|
-
|
|
61
|
+
# Run tests
|
|
62
|
+
movement move test
|
|
63
|
+
|
|
64
|
+
# Publish to testnet
|
|
65
|
+
movement move publish --url https://full.testnet.movementinfra.xyz/v1 --named-addresses module_addr=default
|
|
66
|
+
|
|
67
|
+
# Publish to mainnet
|
|
68
|
+
movement move publish --url https://full.mainnet.movementinfra.xyz/v1 --named-addresses module_addr=default
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Move.toml Configuration
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
[package]
|
|
75
|
+
name = "project_name"
|
|
76
|
+
version = "1.0.0"
|
|
77
|
+
authors = []
|
|
78
|
+
|
|
79
|
+
[addresses]
|
|
80
|
+
module_addr = "_"
|
|
81
|
+
|
|
82
|
+
[dependencies]
|
|
83
|
+
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "mainnet" }
|
|
84
|
+
AptosStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-stdlib", rev = "mainnet" }
|
|
85
|
+
MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib", rev = "mainnet" }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Module Structure
|
|
94
89
|
|
|
95
90
|
```move
|
|
96
|
-
module
|
|
91
|
+
module module_addr::module_name {
|
|
92
|
+
// Imports
|
|
97
93
|
use std::signer;
|
|
94
|
+
use std::string::String;
|
|
95
|
+
use std::vector;
|
|
96
|
+
use std::option::{Self, Option};
|
|
98
97
|
use aptos_framework::event;
|
|
99
98
|
use aptos_framework::account;
|
|
99
|
+
use aptos_framework::timestamp;
|
|
100
|
+
use aptos_framework::coin;
|
|
101
|
+
use aptos_framework::aptos_coin::AptosCoin;
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
// Error codes (constants)
|
|
102
104
|
const E_NOT_AUTHORIZED: u64 = 1;
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const E_ALREADY_EXISTS: u64 = 2;
|
|
106
|
+
const E_NOT_FOUND: u64 = 3;
|
|
107
|
+
const E_INSUFFICIENT_BALANCE: u64 = 4;
|
|
108
|
+
|
|
109
|
+
// Resources (structs with 'key' ability)
|
|
110
|
+
struct ResourceName has key, store {
|
|
111
|
+
field1: u64,
|
|
112
|
+
field2: String,
|
|
113
|
+
field3: vector<u8>,
|
|
108
114
|
}
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
|
|
116
|
+
// Events
|
|
111
117
|
#[event]
|
|
112
|
-
struct
|
|
118
|
+
struct EventName has drop, store {
|
|
119
|
+
actor: address,
|
|
113
120
|
value: u64,
|
|
121
|
+
timestamp: u64,
|
|
114
122
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
public entry fun
|
|
123
|
+
|
|
124
|
+
// Entry functions (callable from transactions)
|
|
125
|
+
public entry fun function_name(
|
|
126
|
+
account: &signer,
|
|
127
|
+
param1: u64,
|
|
128
|
+
) acquires ResourceName {
|
|
118
129
|
// Implementation
|
|
119
130
|
}
|
|
131
|
+
|
|
132
|
+
// View functions (read-only, callable without transaction)
|
|
133
|
+
#[view]
|
|
134
|
+
public fun get_value(addr: address): u64 acquires ResourceName {
|
|
135
|
+
borrow_global<ResourceName>(addr).field1
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Internal functions
|
|
139
|
+
fun internal_helper(): u64 {
|
|
140
|
+
42
|
|
141
|
+
}
|
|
120
142
|
}
|
|
121
143
|
```
|
|
122
144
|
|
|
123
|
-
##
|
|
145
|
+
## Primitive Types
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
147
|
+
| Type | Description | Example |
|
|
148
|
+
|------|-------------|---------|
|
|
149
|
+
| `u8` | 8-bit unsigned integer | `let x: u8 = 255;` |
|
|
150
|
+
| `u16` | 16-bit unsigned integer | `let x: u16 = 65535;` |
|
|
151
|
+
| `u32` | 32-bit unsigned integer | `let x: u32 = 100;` |
|
|
152
|
+
| `u64` | 64-bit unsigned integer | `let x: u64 = 1000000;` |
|
|
153
|
+
| `u128` | 128-bit unsigned integer | `let x: u128 = 1000000;` |
|
|
154
|
+
| `u256` | 256-bit unsigned integer | `let x: u256 = 1000000;` |
|
|
155
|
+
| `bool` | Boolean | `let b: bool = true;` |
|
|
156
|
+
| `address` | 32-byte address | `let a: address = @0x1;` |
|
|
157
|
+
| `vector<T>` | Dynamic array | `let v: vector<u8> = vector[];` |
|
|
158
|
+
| `String` | UTF-8 string | `use std::string::String;` |
|
|
128
159
|
|
|
129
|
-
|
|
130
|
-
movement move test
|
|
160
|
+
## Abilities
|
|
131
161
|
|
|
132
|
-
|
|
133
|
-
|
|
162
|
+
| Ability | Description |
|
|
163
|
+
|---------|-------------|
|
|
164
|
+
| `copy` | Value can be copied |
|
|
165
|
+
| `drop` | Value can be dropped (destroyed) |
|
|
166
|
+
| `store` | Value can be stored in global storage |
|
|
167
|
+
| `key` | Value can be used as a key in global storage |
|
|
134
168
|
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
```move
|
|
170
|
+
// Resource that can be stored globally
|
|
171
|
+
struct MyResource has key, store {
|
|
172
|
+
value: u64,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Struct that can be copied and dropped
|
|
176
|
+
struct MyData has copy, drop, store {
|
|
177
|
+
value: u64,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Event struct (must have drop and store)
|
|
181
|
+
#[event]
|
|
182
|
+
struct MyEvent has drop, store {
|
|
183
|
+
value: u64,
|
|
184
|
+
}
|
|
137
185
|
```
|
|
138
186
|
|
|
187
|
+
## Global Storage Operations
|
|
188
|
+
|
|
189
|
+
```move
|
|
190
|
+
// Store a resource at an address
|
|
191
|
+
move_to<ResourceName>(account, ResourceName { field1: 0, field2: string::utf8(b"hello") });
|
|
192
|
+
|
|
193
|
+
// Check if resource exists
|
|
194
|
+
exists<ResourceName>(addr)
|
|
195
|
+
|
|
196
|
+
// Borrow immutable reference
|
|
197
|
+
let resource_ref = borrow_global<ResourceName>(addr);
|
|
198
|
+
|
|
199
|
+
// Borrow mutable reference
|
|
200
|
+
let resource_mut = borrow_global_mut<ResourceName>(addr);
|
|
201
|
+
|
|
202
|
+
// Remove resource from storage
|
|
203
|
+
let resource = move_from<ResourceName>(addr);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Signer Operations
|
|
207
|
+
|
|
208
|
+
```move
|
|
209
|
+
use std::signer;
|
|
210
|
+
|
|
211
|
+
public entry fun my_function(account: &signer) {
|
|
212
|
+
// Get address from signer
|
|
213
|
+
let addr = signer::address_of(account);
|
|
214
|
+
|
|
215
|
+
// Validate caller
|
|
216
|
+
assert!(addr == @admin_address, E_NOT_AUTHORIZED);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Events
|
|
221
|
+
|
|
222
|
+
```move
|
|
223
|
+
use aptos_framework::event;
|
|
224
|
+
|
|
225
|
+
// Define event struct
|
|
226
|
+
#[event]
|
|
227
|
+
struct TransferEvent has drop, store {
|
|
228
|
+
from: address,
|
|
229
|
+
to: address,
|
|
230
|
+
amount: u64,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Emit event
|
|
234
|
+
event::emit(TransferEvent {
|
|
235
|
+
from: sender_addr,
|
|
236
|
+
to: recipient_addr,
|
|
237
|
+
amount: 100,
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Vectors
|
|
242
|
+
|
|
243
|
+
```move
|
|
244
|
+
use std::vector;
|
|
245
|
+
|
|
246
|
+
// Create empty vector
|
|
247
|
+
let v: vector<u64> = vector[];
|
|
248
|
+
|
|
249
|
+
// Create with initial values
|
|
250
|
+
let v = vector[1, 2, 3];
|
|
251
|
+
|
|
252
|
+
// Push element
|
|
253
|
+
vector::push_back(&mut v, 4);
|
|
254
|
+
|
|
255
|
+
// Pop element
|
|
256
|
+
let last = vector::pop_back(&mut v);
|
|
257
|
+
|
|
258
|
+
// Get length
|
|
259
|
+
let len = vector::length(&v);
|
|
260
|
+
|
|
261
|
+
// Check if empty
|
|
262
|
+
let is_empty = vector::is_empty(&v);
|
|
263
|
+
|
|
264
|
+
// Get element by index (immutable)
|
|
265
|
+
let elem = vector::borrow(&v, 0);
|
|
266
|
+
|
|
267
|
+
// Get element by index (mutable)
|
|
268
|
+
let elem_mut = vector::borrow_mut(&mut v, 0);
|
|
269
|
+
|
|
270
|
+
// Check if contains
|
|
271
|
+
let contains = vector::contains(&v, &42);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Strings
|
|
275
|
+
|
|
276
|
+
```move
|
|
277
|
+
use std::string::{Self, String};
|
|
278
|
+
|
|
279
|
+
// Create from bytes
|
|
280
|
+
let s: String = string::utf8(b"Hello, World!");
|
|
281
|
+
|
|
282
|
+
// Get length
|
|
283
|
+
let len = string::length(&s);
|
|
284
|
+
|
|
285
|
+
// Check if empty
|
|
286
|
+
let is_empty = string::is_empty(&s);
|
|
287
|
+
|
|
288
|
+
// Append
|
|
289
|
+
string::append(&mut s, string::utf8(b" More text"));
|
|
290
|
+
|
|
291
|
+
// Convert to bytes
|
|
292
|
+
let bytes: vector<u8> = *string::bytes(&s);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Options
|
|
296
|
+
|
|
297
|
+
```move
|
|
298
|
+
use std::option::{Self, Option};
|
|
299
|
+
|
|
300
|
+
// Create Some
|
|
301
|
+
let opt: Option<u64> = option::some(42);
|
|
302
|
+
|
|
303
|
+
// Create None
|
|
304
|
+
let none: Option<u64> = option::none();
|
|
305
|
+
|
|
306
|
+
// Check if some
|
|
307
|
+
let is_some = option::is_some(&opt);
|
|
308
|
+
|
|
309
|
+
// Check if none
|
|
310
|
+
let is_none = option::is_none(&none);
|
|
311
|
+
|
|
312
|
+
// Extract value (aborts if none)
|
|
313
|
+
let value = option::extract(&mut opt);
|
|
314
|
+
|
|
315
|
+
// Get with default
|
|
316
|
+
let value = option::get_with_default(&opt, 0);
|
|
317
|
+
|
|
318
|
+
// Borrow value
|
|
319
|
+
let value_ref = option::borrow(&opt);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Coin Operations
|
|
323
|
+
|
|
324
|
+
```move
|
|
325
|
+
use aptos_framework::coin;
|
|
326
|
+
use aptos_framework::aptos_coin::AptosCoin;
|
|
327
|
+
|
|
328
|
+
// Register coin store for account
|
|
329
|
+
coin::register<AptosCoin>(account);
|
|
330
|
+
|
|
331
|
+
// Get balance
|
|
332
|
+
let balance = coin::balance<AptosCoin>(addr);
|
|
333
|
+
|
|
334
|
+
// Transfer coins
|
|
335
|
+
coin::transfer<AptosCoin>(from, to_addr, amount);
|
|
336
|
+
|
|
337
|
+
// Withdraw coins
|
|
338
|
+
let coins = coin::withdraw<AptosCoin>(account, amount);
|
|
339
|
+
|
|
340
|
+
// Deposit coins
|
|
341
|
+
coin::deposit(to_addr, coins);
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Timestamp
|
|
345
|
+
|
|
346
|
+
```move
|
|
347
|
+
use aptos_framework::timestamp;
|
|
348
|
+
|
|
349
|
+
// Get current timestamp in seconds
|
|
350
|
+
let now_seconds = timestamp::now_seconds();
|
|
351
|
+
|
|
352
|
+
// Get current timestamp in microseconds
|
|
353
|
+
let now_microseconds = timestamp::now_microseconds();
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Testing
|
|
357
|
+
|
|
358
|
+
### Basic Test
|
|
359
|
+
```move
|
|
360
|
+
#[test_only]
|
|
361
|
+
module module_addr::module_name_tests {
|
|
362
|
+
use std::signer;
|
|
363
|
+
use module_addr::module_name;
|
|
364
|
+
|
|
365
|
+
#[test(account = @0x1)]
|
|
366
|
+
fun test_basic_function(account: &signer) {
|
|
367
|
+
// Setup
|
|
368
|
+
let addr = signer::address_of(account);
|
|
369
|
+
|
|
370
|
+
// Action
|
|
371
|
+
module_name::initialize(account);
|
|
372
|
+
|
|
373
|
+
// Assert
|
|
374
|
+
assert!(module_name::get_value(addr) == 0, 0);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Test with Expected Failure
|
|
380
|
+
```move
|
|
381
|
+
#[test]
|
|
382
|
+
#[expected_failure(abort_code = module_name::E_NOT_AUTHORIZED)]
|
|
383
|
+
fun test_unauthorized_access() {
|
|
384
|
+
// This should fail with E_NOT_AUTHORIZED
|
|
385
|
+
module_name::admin_only_function();
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Test with Multiple Signers
|
|
390
|
+
```move
|
|
391
|
+
#[test(admin = @0x1, user = @0x2)]
|
|
392
|
+
fun test_with_multiple_accounts(admin: &signer, user: &signer) {
|
|
393
|
+
// Setup admin
|
|
394
|
+
module_name::initialize(admin);
|
|
395
|
+
|
|
396
|
+
// User interacts
|
|
397
|
+
module_name::user_action(user);
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Test with Framework
|
|
402
|
+
```move
|
|
403
|
+
#[test(aptos_framework = @aptos_framework, account = @0x1)]
|
|
404
|
+
fun test_with_framework(aptos_framework: &signer, account: &signer) {
|
|
405
|
+
// Setup timestamp for testing
|
|
406
|
+
timestamp::set_time_has_started_for_testing(aptos_framework);
|
|
407
|
+
|
|
408
|
+
// Now timestamp::now_seconds() works in tests
|
|
409
|
+
module_name::time_dependent_function(account);
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Common Patterns
|
|
414
|
+
|
|
415
|
+
### Initialization Pattern
|
|
416
|
+
```move
|
|
417
|
+
struct Config has key {
|
|
418
|
+
admin: address,
|
|
419
|
+
is_initialized: bool,
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
public entry fun initialize(admin: &signer) {
|
|
423
|
+
let admin_addr = signer::address_of(admin);
|
|
424
|
+
assert!(!exists<Config>(admin_addr), E_ALREADY_EXISTS);
|
|
425
|
+
|
|
426
|
+
move_to(admin, Config {
|
|
427
|
+
admin: admin_addr,
|
|
428
|
+
is_initialized: true,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Admin-Only Pattern
|
|
434
|
+
```move
|
|
435
|
+
public entry fun admin_function(admin: &signer) acquires Config {
|
|
436
|
+
let admin_addr = signer::address_of(admin);
|
|
437
|
+
let config = borrow_global<Config>(@module_addr);
|
|
438
|
+
assert!(config.admin == admin_addr, E_NOT_AUTHORIZED);
|
|
439
|
+
|
|
440
|
+
// Admin-only logic
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Counter Pattern
|
|
445
|
+
```move
|
|
446
|
+
struct Counter has key {
|
|
447
|
+
value: u64,
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
public entry fun increment(account: &signer) acquires Counter {
|
|
451
|
+
let addr = signer::address_of(account);
|
|
452
|
+
|
|
453
|
+
if (!exists<Counter>(addr)) {
|
|
454
|
+
move_to(account, Counter { value: 0 });
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
let counter = borrow_global_mut<Counter>(addr);
|
|
458
|
+
counter.value = counter.value + 1;
|
|
459
|
+
|
|
460
|
+
event::emit(CounterIncremented {
|
|
461
|
+
account: addr,
|
|
462
|
+
new_value: counter.value
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
#[view]
|
|
467
|
+
public fun get_count(addr: address): u64 acquires Counter {
|
|
468
|
+
if (!exists<Counter>(addr)) {
|
|
469
|
+
return 0
|
|
470
|
+
};
|
|
471
|
+
borrow_global<Counter>(addr).value
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Token/NFT Collection Pattern
|
|
476
|
+
```move
|
|
477
|
+
struct Collection has key {
|
|
478
|
+
items: vector<Item>,
|
|
479
|
+
next_id: u64,
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
struct Item has store, drop {
|
|
483
|
+
id: u64,
|
|
484
|
+
name: String,
|
|
485
|
+
owner: address,
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
public entry fun mint(account: &signer, name: String) acquires Collection {
|
|
489
|
+
let addr = signer::address_of(account);
|
|
490
|
+
let collection = borrow_global_mut<Collection>(@module_addr);
|
|
491
|
+
|
|
492
|
+
let item = Item {
|
|
493
|
+
id: collection.next_id,
|
|
494
|
+
name,
|
|
495
|
+
owner: addr,
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
vector::push_back(&mut collection.items, item);
|
|
499
|
+
collection.next_id = collection.next_id + 1;
|
|
500
|
+
|
|
501
|
+
event::emit(ItemMinted { id: collection.next_id - 1, owner: addr });
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Security Best Practices
|
|
506
|
+
|
|
507
|
+
1. **Always validate signers** - Check that the caller has permission
|
|
508
|
+
2. **Use assert! with error codes** - Provide clear error messages
|
|
509
|
+
3. **Emit events for state changes** - Enable off-chain tracking
|
|
510
|
+
4. **Check for resource existence** - Use `exists<T>(addr)` before accessing
|
|
511
|
+
5. **Avoid unbounded loops** - Can cause out-of-gas errors
|
|
512
|
+
6. **Use acquires annotation** - Declare all resources accessed
|
|
513
|
+
7. **Initialize before use** - Ensure resources exist before borrowing
|
|
514
|
+
|
|
515
|
+
## Complete Example: Simple Token
|
|
516
|
+
|
|
517
|
+
```move
|
|
518
|
+
module module_addr::simple_token {
|
|
519
|
+
use std::signer;
|
|
520
|
+
use std::string::String;
|
|
521
|
+
use aptos_framework::event;
|
|
522
|
+
|
|
523
|
+
// Errors
|
|
524
|
+
const E_NOT_INITIALIZED: u64 = 1;
|
|
525
|
+
const E_INSUFFICIENT_BALANCE: u64 = 2;
|
|
526
|
+
|
|
527
|
+
// Resources
|
|
528
|
+
struct TokenInfo has key {
|
|
529
|
+
name: String,
|
|
530
|
+
symbol: String,
|
|
531
|
+
total_supply: u64,
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
struct Balance has key {
|
|
535
|
+
value: u64,
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Events
|
|
539
|
+
#[event]
|
|
540
|
+
struct Transfer has drop, store {
|
|
541
|
+
from: address,
|
|
542
|
+
to: address,
|
|
543
|
+
amount: u64,
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Initialize token
|
|
547
|
+
public entry fun initialize(
|
|
548
|
+
admin: &signer,
|
|
549
|
+
name: String,
|
|
550
|
+
symbol: String,
|
|
551
|
+
initial_supply: u64,
|
|
552
|
+
) {
|
|
553
|
+
let admin_addr = signer::address_of(admin);
|
|
554
|
+
|
|
555
|
+
move_to(admin, TokenInfo {
|
|
556
|
+
name,
|
|
557
|
+
symbol,
|
|
558
|
+
total_supply: initial_supply,
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
move_to(admin, Balance { value: initial_supply });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Transfer tokens
|
|
565
|
+
public entry fun transfer(
|
|
566
|
+
from: &signer,
|
|
567
|
+
to: address,
|
|
568
|
+
amount: u64,
|
|
569
|
+
) acquires Balance {
|
|
570
|
+
let from_addr = signer::address_of(from);
|
|
571
|
+
|
|
572
|
+
// Deduct from sender
|
|
573
|
+
let from_balance = borrow_global_mut<Balance>(from_addr);
|
|
574
|
+
assert!(from_balance.value >= amount, E_INSUFFICIENT_BALANCE);
|
|
575
|
+
from_balance.value = from_balance.value - amount;
|
|
576
|
+
|
|
577
|
+
// Add to recipient
|
|
578
|
+
if (!exists<Balance>(to)) {
|
|
579
|
+
// Create balance for new recipient (requires signer, simplified here)
|
|
580
|
+
// In practice, recipient would need to register first
|
|
581
|
+
};
|
|
582
|
+
let to_balance = borrow_global_mut<Balance>(to);
|
|
583
|
+
to_balance.value = to_balance.value + amount;
|
|
584
|
+
|
|
585
|
+
event::emit(Transfer { from: from_addr, to, amount });
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// View balance
|
|
589
|
+
#[view]
|
|
590
|
+
public fun balance_of(addr: address): u64 acquires Balance {
|
|
591
|
+
if (!exists<Balance>(addr)) {
|
|
592
|
+
return 0
|
|
593
|
+
};
|
|
594
|
+
borrow_global<Balance>(addr).value
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Development Workflow
|
|
602
|
+
|
|
603
|
+
1. **Requirements Analysis**
|
|
604
|
+
- Understand the business logic and state requirements
|
|
605
|
+
- Identify resources, events, and entry functions needed
|
|
606
|
+
- Plan module structure and dependencies
|
|
607
|
+
|
|
608
|
+
2. **Implementation**
|
|
609
|
+
- Follow Move naming conventions (snake_case for functions/modules, PascalCase for types)
|
|
610
|
+
- Use descriptive error codes with constants
|
|
611
|
+
- Emit events for all state mutations
|
|
612
|
+
- Document public functions with /// comments
|
|
613
|
+
|
|
614
|
+
3. **Security Review**
|
|
615
|
+
- Check for resource leaks and unauthorized access
|
|
616
|
+
- Verify signer requirements on entry functions
|
|
617
|
+
- Validate all external inputs
|
|
618
|
+
- Review ability constraints on types
|
|
619
|
+
|
|
620
|
+
4. **Testing**
|
|
621
|
+
- Write comprehensive unit tests
|
|
622
|
+
- Test happy paths and error cases
|
|
623
|
+
- Verify event emissions
|
|
624
|
+
- Check edge cases and boundary conditions
|
|
625
|
+
|
|
139
626
|
## Reporting
|
|
140
627
|
|
|
141
628
|
When completing tasks, provide:
|