@btc-vision/btc-runtime 1.10.10 → 1.10.12
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 +190 -0
- package/README.md +258 -137
- package/SECURITY.md +226 -0
- package/docs/README.md +614 -0
- package/docs/advanced/bitcoin-scripts.md +939 -0
- package/docs/advanced/cross-contract-calls.md +579 -0
- package/docs/advanced/plugins.md +1006 -0
- package/docs/advanced/quantum-resistance.md +660 -0
- package/docs/advanced/signature-verification.md +715 -0
- package/docs/api-reference/blockchain.md +729 -0
- package/docs/api-reference/events.md +642 -0
- package/docs/api-reference/op20.md +902 -0
- package/docs/api-reference/op721.md +819 -0
- package/docs/api-reference/safe-math.md +510 -0
- package/docs/api-reference/storage.md +731 -0
- package/docs/contracts/op-net-base.md +786 -0
- package/docs/contracts/op20-token.md +687 -0
- package/docs/contracts/op20s-signatures.md +614 -0
- package/docs/contracts/op721-nft.md +785 -0
- package/docs/contracts/reentrancy-guard.md +787 -0
- package/docs/core-concepts/blockchain-environment.md +724 -0
- package/docs/core-concepts/decorators.md +466 -0
- package/docs/core-concepts/events.md +652 -0
- package/docs/core-concepts/pointers.md +391 -0
- package/docs/core-concepts/security.md +370 -0
- package/docs/core-concepts/storage-system.md +938 -0
- package/docs/examples/basic-token.md +745 -0
- package/docs/examples/nft-with-reservations.md +1210 -0
- package/docs/examples/oracle-integration.md +1212 -0
- package/docs/examples/stablecoin.md +1180 -0
- package/docs/getting-started/first-contract.md +575 -0
- package/docs/getting-started/installation.md +384 -0
- package/docs/getting-started/project-structure.md +630 -0
- package/docs/storage/memory-maps.md +721 -0
- package/docs/storage/stored-arrays.md +714 -0
- package/docs/storage/stored-maps.md +686 -0
- package/docs/storage/stored-primitives.md +608 -0
- package/docs/types/address.md +773 -0
- package/docs/types/bytes-writer-reader.md +938 -0
- package/docs/types/calldata.md +744 -0
- package/docs/types/safe-math.md +403 -0
- package/package.json +51 -26
- package/runtime/memory/MapOfMap.ts +1 -0
- package/runtime/types/SafeMath.ts +121 -1
- package/LICENSE.md +0 -21
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
# Stored Primitives
|
|
2
|
+
|
|
3
|
+
Stored primitives are typed wrappers for single values that persist on-chain. They handle storage reading, writing, and caching automatically.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
StoredU256,
|
|
10
|
+
StoredU64,
|
|
11
|
+
StoredU32,
|
|
12
|
+
StoredBoolean,
|
|
13
|
+
StoredString,
|
|
14
|
+
StoredAddress,
|
|
15
|
+
Blockchain,
|
|
16
|
+
EMPTY_POINTER,
|
|
17
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
18
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
19
|
+
|
|
20
|
+
// Allocate storage pointer
|
|
21
|
+
private counterPointer: u16 = Blockchain.nextPointer;
|
|
22
|
+
|
|
23
|
+
// Create stored value with default
|
|
24
|
+
private counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
|
|
25
|
+
|
|
26
|
+
// Read and write
|
|
27
|
+
const current = this.counter.value; // Read
|
|
28
|
+
this.counter.value = newValue; // Write
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Class Hierarchy
|
|
32
|
+
|
|
33
|
+
The stored primitives are standalone final classes (not inheriting from a common base):
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
classDiagram
|
|
37
|
+
class StoredU256 {
|
|
38
|
+
-pointer: u16
|
|
39
|
+
-subPointer: Uint8Array
|
|
40
|
+
-_value: u256
|
|
41
|
+
+value: u256 getter/setter
|
|
42
|
+
+add(value: u256) this
|
|
43
|
+
+sub(value: u256) this
|
|
44
|
+
+mul(value: u256) this
|
|
45
|
+
+set(value: u256) this
|
|
46
|
+
+toString() string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class StoredU64 {
|
|
50
|
+
-pointer: u16
|
|
51
|
+
-subPointer: Uint8Array
|
|
52
|
+
-_values: u64[4]
|
|
53
|
+
+get(index: u8) u64
|
|
54
|
+
+set(index: u8, value: u64) void
|
|
55
|
+
+save() void
|
|
56
|
+
+getAll() u64[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class StoredU32 {
|
|
60
|
+
-pointer: u16
|
|
61
|
+
-subPointer: Uint8Array
|
|
62
|
+
-_values: u32[8]
|
|
63
|
+
+get(index: u8) u32
|
|
64
|
+
+set(index: u8, value: u32) void
|
|
65
|
+
+save() void
|
|
66
|
+
+getAll() u32[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class StoredBoolean {
|
|
70
|
+
-pointer: u16
|
|
71
|
+
-_value: Uint8Array
|
|
72
|
+
+value: bool getter/setter
|
|
73
|
+
+toUint8Array() Uint8Array
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class StoredString {
|
|
77
|
+
-pointer: u16
|
|
78
|
+
-index: u64
|
|
79
|
+
+value: string getter/setter
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class StoredAddress {
|
|
83
|
+
-pointer: u16
|
|
84
|
+
-_value: Address
|
|
85
|
+
+value: Address getter/setter
|
|
86
|
+
+isDead() bool
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Available Types
|
|
91
|
+
|
|
92
|
+
| Type | Value Type | Size | Description |
|
|
93
|
+
|------|------------|------|-------------|
|
|
94
|
+
| `StoredU256` | `u256` | 32 bytes | 256-bit unsigned integer |
|
|
95
|
+
| `StoredU64` | `u64[4]` | 32 bytes | Stores 4 u64 values in one slot |
|
|
96
|
+
| `StoredU32` | `u32[8]` | 32 bytes | Stores 8 u32 values in one slot |
|
|
97
|
+
| `StoredBoolean` | `bool` | 32 bytes | Boolean value |
|
|
98
|
+
| `StoredString` | `string` | Variable | UTF-8 string (max 65,535 bytes) |
|
|
99
|
+
| `StoredAddress` | `Address` | 32 bytes | Address value |
|
|
100
|
+
|
|
101
|
+
> **Note:** `StoredU64` and `StoredU32` are packed storage types that store multiple values in a single 256-bit storage slot. Use `get(index)` and `set(index, value)` to access individual values, then call `save()` to persist changes.
|
|
102
|
+
|
|
103
|
+
## Storage Key Generation
|
|
104
|
+
|
|
105
|
+
Each stored primitive computes its storage key using `SHA256(pointer || subPointer)`. See [Pointers](../core-concepts/pointers.md#encodepointer-function-flow) for the detailed flow diagram.
|
|
106
|
+
|
|
107
|
+
## Usage
|
|
108
|
+
|
|
109
|
+
### StoredU256
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Declaration
|
|
113
|
+
private balancePointer: u16 = Blockchain.nextPointer;
|
|
114
|
+
private _balance: StoredU256 = new StoredU256(this.balancePointer, EMPTY_POINTER);
|
|
115
|
+
|
|
116
|
+
// Read
|
|
117
|
+
const balance: u256 = this._balance.value;
|
|
118
|
+
|
|
119
|
+
// Write
|
|
120
|
+
this._balance.value = newBalance;
|
|
121
|
+
|
|
122
|
+
// Arithmetic
|
|
123
|
+
this._balance.value = SafeMath.add(this._balance.value, amount);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### StoredBoolean
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Declaration
|
|
130
|
+
private pausedPointer: u16 = Blockchain.nextPointer;
|
|
131
|
+
private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
|
|
132
|
+
|
|
133
|
+
// Read
|
|
134
|
+
if (this._paused.value) {
|
|
135
|
+
throw new Revert('Contract is paused');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Write
|
|
139
|
+
this._paused.value = true;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### StoredString
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Declaration
|
|
146
|
+
private namePointer: u16 = Blockchain.nextPointer;
|
|
147
|
+
private _name: StoredString = new StoredString(this.namePointer, 0);
|
|
148
|
+
|
|
149
|
+
// Write (typically in onDeployment)
|
|
150
|
+
this._name.value = 'My Token';
|
|
151
|
+
|
|
152
|
+
// Read
|
|
153
|
+
const name: string = this._name.value;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### StoredAddress
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Declaration - takes only pointer (default value is Address.zero())
|
|
160
|
+
private ownerPointer: u16 = Blockchain.nextPointer;
|
|
161
|
+
private _owner: StoredAddress = new StoredAddress(this.ownerPointer);
|
|
162
|
+
|
|
163
|
+
// Write
|
|
164
|
+
this._owner.value = Blockchain.tx.origin;
|
|
165
|
+
|
|
166
|
+
// Read
|
|
167
|
+
const owner: Address = this._owner.value;
|
|
168
|
+
|
|
169
|
+
// Check if address is zero (Note: isDead() in StoredAddress actually checks for zero address)
|
|
170
|
+
if (this._owner.isDead()) {
|
|
171
|
+
throw new Revert('Owner not set');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Alternative: use isZero() on the Address instance directly
|
|
175
|
+
if (this._owner.value.isZero()) {
|
|
176
|
+
throw new Revert('Owner not set');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Compare
|
|
180
|
+
if (!Blockchain.tx.sender.equals(this._owner.value)) {
|
|
181
|
+
throw new Revert('Not owner');
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Storage Behavior
|
|
186
|
+
|
|
187
|
+
### Lazy Loading (Value Read Flow)
|
|
188
|
+
|
|
189
|
+
Values are loaded from storage on first access. The read flow follows this pattern:
|
|
190
|
+
|
|
191
|
+
```mermaid
|
|
192
|
+
---
|
|
193
|
+
config:
|
|
194
|
+
theme: dark
|
|
195
|
+
---
|
|
196
|
+
flowchart LR
|
|
197
|
+
A["Access .value"] --> B{"Cached?"}
|
|
198
|
+
B -->|"Yes"| C["Return cached"]
|
|
199
|
+
B -->|"No"| D["ensureValue()"]
|
|
200
|
+
D --> E["encodePointer()"]
|
|
201
|
+
E --> F["getStorageAt()"]
|
|
202
|
+
F --> G["decode()"]
|
|
203
|
+
G --> H["Cache & Return"]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// First access triggers storage read
|
|
208
|
+
const balance = this._balance.value; // Reads from storage
|
|
209
|
+
|
|
210
|
+
// Subsequent accesses use cached value
|
|
211
|
+
const balance2 = this._balance.value; // Uses cache (no storage read)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Automatic Commit (Value Write Flow)
|
|
215
|
+
|
|
216
|
+
Changes are committed to storage automatically following this flow:
|
|
217
|
+
|
|
218
|
+
```mermaid
|
|
219
|
+
---
|
|
220
|
+
config:
|
|
221
|
+
theme: dark
|
|
222
|
+
---
|
|
223
|
+
flowchart LR
|
|
224
|
+
A["Set .value"] --> B["encode()"]
|
|
225
|
+
B --> C["Update cache"]
|
|
226
|
+
C --> D["encodePointer()"]
|
|
227
|
+
D --> E["setStorageAt()"]
|
|
228
|
+
E --> F["Committed"]
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Write value
|
|
233
|
+
this._balance.value = newBalance; // Marks as dirty
|
|
234
|
+
|
|
235
|
+
// Value is committed at transaction end
|
|
236
|
+
// (or immediately in some implementations)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Manual Commit Control
|
|
240
|
+
|
|
241
|
+
For advanced use cases:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Some stored types support NoCommit for read-only access
|
|
245
|
+
const value = this._balance.valueNoCommit; // Read without triggering commit
|
|
246
|
+
|
|
247
|
+
// Useful for view functions that shouldn't modify storage
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Initialization
|
|
251
|
+
|
|
252
|
+
### Default Values
|
|
253
|
+
|
|
254
|
+
Always provide a meaningful default:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// Good: Zero/empty defaults
|
|
258
|
+
private counter: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
|
|
259
|
+
private name: StoredString = new StoredString(ptr, 0);
|
|
260
|
+
private paused: StoredBoolean = new StoredBoolean(ptr, false);
|
|
261
|
+
private owner: StoredAddress = new StoredAddress(ptr); // Default is Address.zero()
|
|
262
|
+
|
|
263
|
+
// The default is returned when storage slot is empty (never written)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Setting Initial Values
|
|
267
|
+
|
|
268
|
+
Set values in `onDeployment`:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
public override onDeployment(calldata: Calldata): void {
|
|
272
|
+
// Set initial values
|
|
273
|
+
this._name.value = calldata.readString();
|
|
274
|
+
this._symbol.value = calldata.readString();
|
|
275
|
+
this._totalSupply.value = calldata.readU256();
|
|
276
|
+
this._owner.value = Blockchain.tx.origin;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Solidity vs OPNet Comparison
|
|
281
|
+
|
|
282
|
+
### Quick Reference Table
|
|
283
|
+
|
|
284
|
+
| Solidity | OPNet | Default Value |
|
|
285
|
+
|----------|-------|---------------|
|
|
286
|
+
| `uint256 public value;` | `StoredU256` | `u256.Zero` |
|
|
287
|
+
| `uint64[4] packed;` | `StoredU64` | `[0, 0, 0, 0]` |
|
|
288
|
+
| `uint32[8] packed;` | `StoredU32` | `[0, 0, 0, 0, 0, 0, 0, 0]` |
|
|
289
|
+
| `string public name;` | `StoredString` | `""` |
|
|
290
|
+
| `bool public paused;` | `StoredBoolean` | `false` |
|
|
291
|
+
| `address public owner;` | `StoredAddress` | `Address.zero()` |
|
|
292
|
+
|
|
293
|
+
> **Note:** `StoredU64` and `StoredU32` pack multiple values into a single storage slot for efficiency. For single-value storage, use `StoredU256` with appropriate conversions.
|
|
294
|
+
|
|
295
|
+
### Operations Comparison
|
|
296
|
+
|
|
297
|
+
| Operation | Solidity | OPNet |
|
|
298
|
+
|-----------|----------|-------|
|
|
299
|
+
| Declare state variable | `uint256 public value;` | `private _value: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);` |
|
|
300
|
+
| Read value | `value` or `this.value` | `this._value.value` |
|
|
301
|
+
| Write value | `value = newValue;` | `this._value.value = newValue;` |
|
|
302
|
+
| Increment | `value++;` | `this._value.value = SafeMath.add(this._value.value, u256.One);` |
|
|
303
|
+
| Decrement | `value--;` | `this._value.value = SafeMath.sub(this._value.value, u256.One);` |
|
|
304
|
+
| Add amount | `value += amount;` | `this._value.value = SafeMath.add(this._value.value, amount);` |
|
|
305
|
+
| Check zero | `value == 0` | `this._value.value.isZero()` |
|
|
306
|
+
| Compare | `value > other` | `this._value.value > other` |
|
|
307
|
+
| Set in constructor | `value = initial;` | Use `onDeployment()` |
|
|
308
|
+
| Public getter | Automatic | Must define manually |
|
|
309
|
+
|
|
310
|
+
### Declaration Patterns
|
|
311
|
+
|
|
312
|
+
| Solidity Pattern | OPNet Equivalent |
|
|
313
|
+
|------------------|------------------|
|
|
314
|
+
| `uint256 public totalSupply;` | `private totalSupplyPtr: u16 = Blockchain.nextPointer;`<br>`private _totalSupply: StoredU256 = new StoredU256(this.totalSupplyPtr, EMPTY_POINTER);` |
|
|
315
|
+
| `string public name = "Token";` | `private namePtr: u16 = Blockchain.nextPointer;`<br>`private _name: StoredString = new StoredString(this.namePtr, 0);`<br>Then in `onDeployment`: `this._name.value = "Token";` |
|
|
316
|
+
| `bool public paused = false;` | `private pausedPtr: u16 = Blockchain.nextPointer;`<br>`private _paused: StoredBoolean = new StoredBoolean(this.pausedPtr, false);` |
|
|
317
|
+
| `address public owner;` | `private ownerPtr: u16 = Blockchain.nextPointer;`<br>`private _owner: StoredAddress = new StoredAddress(this.ownerPtr);` |
|
|
318
|
+
|
|
319
|
+
For complete token examples using stored primitives, see [Basic Token Example](../examples/basic-token.md).
|
|
320
|
+
|
|
321
|
+
## Side-by-Side Code Examples
|
|
322
|
+
|
|
323
|
+
### Counter Contract
|
|
324
|
+
|
|
325
|
+
**Solidity:**
|
|
326
|
+
```solidity
|
|
327
|
+
contract Counter {
|
|
328
|
+
uint256 public count;
|
|
329
|
+
|
|
330
|
+
function increment() external {
|
|
331
|
+
count++;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function decrement() external {
|
|
335
|
+
require(count > 0, "Cannot go below zero");
|
|
336
|
+
count--;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function add(uint256 amount) external {
|
|
340
|
+
count += amount;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function reset() external {
|
|
344
|
+
count = 0;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**OPNet:**
|
|
350
|
+
```typescript
|
|
351
|
+
@final
|
|
352
|
+
export class Counter extends OP_NET {
|
|
353
|
+
private countPointer: u16 = Blockchain.nextPointer;
|
|
354
|
+
private _count: StoredU256 = new StoredU256(this.countPointer, EMPTY_POINTER);
|
|
355
|
+
|
|
356
|
+
public increment(_calldata: Calldata): BytesWriter {
|
|
357
|
+
this._count.value = SafeMath.add(this._count.value, u256.One);
|
|
358
|
+
return new BytesWriter(0);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public decrement(_calldata: Calldata): BytesWriter {
|
|
362
|
+
if (this._count.value.isZero()) {
|
|
363
|
+
throw new Revert('Cannot go below zero');
|
|
364
|
+
}
|
|
365
|
+
this._count.value = SafeMath.sub(this._count.value, u256.One);
|
|
366
|
+
return new BytesWriter(0);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
public add(calldata: Calldata): BytesWriter {
|
|
370
|
+
const amount = calldata.readU256();
|
|
371
|
+
this._count.value = SafeMath.add(this._count.value, amount);
|
|
372
|
+
return new BytesWriter(0);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public reset(_calldata: Calldata): BytesWriter {
|
|
376
|
+
this._count.value = u256.Zero;
|
|
377
|
+
return new BytesWriter(0);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
public count(_calldata: Calldata): BytesWriter {
|
|
381
|
+
const writer = new BytesWriter(32);
|
|
382
|
+
writer.writeU256(this._count.value);
|
|
383
|
+
return writer;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Ownable Contract
|
|
389
|
+
|
|
390
|
+
**Solidity:**
|
|
391
|
+
```solidity
|
|
392
|
+
contract Ownable {
|
|
393
|
+
address public owner;
|
|
394
|
+
bool public paused;
|
|
395
|
+
|
|
396
|
+
modifier onlyOwner() {
|
|
397
|
+
require(msg.sender == owner, "Not owner");
|
|
398
|
+
_;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
constructor() {
|
|
402
|
+
owner = msg.sender;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function transferOwnership(address newOwner) external onlyOwner {
|
|
406
|
+
require(newOwner != address(0), "Invalid address");
|
|
407
|
+
owner = newOwner;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function pause() external onlyOwner {
|
|
411
|
+
paused = true;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function unpause() external onlyOwner {
|
|
415
|
+
paused = false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**OPNet:**
|
|
421
|
+
```typescript
|
|
422
|
+
@final
|
|
423
|
+
export class Ownable extends OP_NET {
|
|
424
|
+
private ownerPointer: u16 = Blockchain.nextPointer;
|
|
425
|
+
private pausedPointer: u16 = Blockchain.nextPointer;
|
|
426
|
+
|
|
427
|
+
private _owner: StoredAddress = new StoredAddress(this.ownerPointer);
|
|
428
|
+
private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
|
|
429
|
+
|
|
430
|
+
public override onDeployment(_calldata: Calldata): void {
|
|
431
|
+
this._owner.value = Blockchain.tx.origin;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private onlyOwner(): void {
|
|
435
|
+
if (!Blockchain.tx.sender.equals(this._owner.value)) {
|
|
436
|
+
throw new Revert('Not owner');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
public transferOwnership(calldata: Calldata): BytesWriter {
|
|
441
|
+
this.onlyOwner();
|
|
442
|
+
const newOwner = calldata.readAddress();
|
|
443
|
+
if (newOwner.equals(Address.zero())) {
|
|
444
|
+
throw new Revert('Invalid address');
|
|
445
|
+
}
|
|
446
|
+
this._owner.value = newOwner;
|
|
447
|
+
return new BytesWriter(0);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
public pause(_calldata: Calldata): BytesWriter {
|
|
451
|
+
this.onlyOwner();
|
|
452
|
+
this._paused.value = true;
|
|
453
|
+
return new BytesWriter(0);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
public unpause(_calldata: Calldata): BytesWriter {
|
|
457
|
+
this.onlyOwner();
|
|
458
|
+
this._paused.value = false;
|
|
459
|
+
return new BytesWriter(0);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public owner(_calldata: Calldata): BytesWriter {
|
|
463
|
+
const writer = new BytesWriter(32);
|
|
464
|
+
writer.writeAddress(this._owner.value);
|
|
465
|
+
return writer;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
public paused(_calldata: Calldata): BytesWriter {
|
|
469
|
+
const writer = new BytesWriter(1);
|
|
470
|
+
writer.writeBoolean(this._paused.value);
|
|
471
|
+
return writer;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Patterns
|
|
477
|
+
|
|
478
|
+
### Read-Modify-Write
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// Increment counter
|
|
482
|
+
public increment(_calldata: Calldata): BytesWriter {
|
|
483
|
+
const current = this._counter.value;
|
|
484
|
+
this._counter.value = SafeMath.add(current, u256.One);
|
|
485
|
+
return new BytesWriter(0);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Toggle boolean
|
|
489
|
+
public togglePause(_calldata: Calldata): BytesWriter {
|
|
490
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
491
|
+
this._paused.value = !this._paused.value;
|
|
492
|
+
return new BytesWriter(0);
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Conditional Updates
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
public setOwner(calldata: Calldata): BytesWriter {
|
|
500
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
501
|
+
|
|
502
|
+
const newOwner = calldata.readAddress();
|
|
503
|
+
|
|
504
|
+
// Validate before writing
|
|
505
|
+
if (newOwner.equals(Address.zero())) {
|
|
506
|
+
throw new Revert('Invalid owner');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Only write if different
|
|
510
|
+
if (!newOwner.equals(this._owner.value)) {
|
|
511
|
+
this._owner.value = newOwner;
|
|
512
|
+
this.emitEvent(new OwnershipTransferred(this._owner.value, newOwner));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return new BytesWriter(0);
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### View Functions
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// Return stored value
|
|
523
|
+
public totalSupply(_calldata: Calldata): BytesWriter {
|
|
524
|
+
const writer = new BytesWriter(32);
|
|
525
|
+
writer.writeU256(this._totalSupply.value);
|
|
526
|
+
return writer;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Return multiple values
|
|
530
|
+
public getInfo(_calldata: Calldata): BytesWriter {
|
|
531
|
+
const writer = new BytesWriter(256);
|
|
532
|
+
writer.writeString(this._name.value);
|
|
533
|
+
writer.writeString(this._symbol.value);
|
|
534
|
+
writer.writeU256(this._totalSupply.value);
|
|
535
|
+
writer.writeU8(this._decimals.value);
|
|
536
|
+
return writer;
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Best Practices
|
|
541
|
+
|
|
542
|
+
### 1. Initialize All Storage
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
// Always set initial values in onDeployment
|
|
546
|
+
public override onDeployment(calldata: Calldata): void {
|
|
547
|
+
this._name.value = 'Token';
|
|
548
|
+
this._symbol.value = 'TKN';
|
|
549
|
+
this._decimals.value = 18;
|
|
550
|
+
this._owner.value = Blockchain.tx.origin;
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### 2. Use Meaningful Defaults
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
// Good: EMPTY_POINTER for uninitialized u256 values
|
|
558
|
+
private counter: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
|
|
559
|
+
|
|
560
|
+
// Note: Set initial values in onDeployment if needed
|
|
561
|
+
// this._counter.value = u256.fromU64(100);
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 3. Validate Before Writing
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
public setLimit(calldata: Calldata): BytesWriter {
|
|
568
|
+
const newLimit = calldata.readU256();
|
|
569
|
+
|
|
570
|
+
// Validate
|
|
571
|
+
if (newLimit.isZero()) {
|
|
572
|
+
throw new Revert('Limit cannot be zero');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (newLimit > u256.fromU64(1000000)) {
|
|
576
|
+
throw new Revert('Limit too high');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Then write
|
|
580
|
+
this._limit.value = newLimit;
|
|
581
|
+
return new BytesWriter(0);
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### 4. Cache Reads in Loops
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
// Bad: Multiple storage reads
|
|
589
|
+
for (let i = 0; i < count; i++) {
|
|
590
|
+
if (amount > this._balance.value) { // Storage read each iteration
|
|
591
|
+
// ...
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Good: Cache the value
|
|
596
|
+
const balance = this._balance.value; // One storage read
|
|
597
|
+
for (let i = 0; i < count; i++) {
|
|
598
|
+
if (amount > balance) {
|
|
599
|
+
// ...
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
**Navigation:**
|
|
607
|
+
- Previous: [BytesWriter/Reader](../types/bytes-writer-reader.md)
|
|
608
|
+
- Next: [Stored Arrays](./stored-arrays.md)
|