@btc-vision/btc-runtime 1.10.10 → 1.10.11
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 +840 -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 +473 -0
- package/docs/core-concepts/storage-system.md +969 -0
- package/docs/examples/basic-token.md +745 -0
- package/docs/examples/nft-with-reservations.md +1440 -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 +764 -0
- package/docs/storage/stored-arrays.md +778 -0
- package/docs/storage/stored-maps.md +758 -0
- package/docs/storage/stored-primitives.md +655 -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 +446 -0
- package/package.json +51 -26
- package/runtime/memory/MapOfMap.ts +1 -0
- package/LICENSE.md +0 -21
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
# Storage System
|
|
2
|
+
|
|
3
|
+
OPNet uses a pointer-based storage system that provides deterministic, secure, and efficient data persistence on Bitcoin L1. This guide explains how storage works and how to use it effectively.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Unlike Solidity where storage is implicitly managed, OPNet requires explicit pointer allocation for all persistent data. This design provides:
|
|
8
|
+
|
|
9
|
+
- **Deterministic storage locations** via SHA256 hashing
|
|
10
|
+
- **Collision-free addressing** through unique pointer combinations
|
|
11
|
+
- **Efficient access** with optimized read/write patterns
|
|
12
|
+
- **Verifiable state proofs** for cross-chain validation
|
|
13
|
+
|
|
14
|
+
## How Storage Works
|
|
15
|
+
|
|
16
|
+
### Storage Keys
|
|
17
|
+
|
|
18
|
+
Every storage location is identified by a unique key generated from:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
StorageKey = SHA256(pointer || subPointer)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Where:
|
|
25
|
+
- `pointer` is a `u16` (0-65535) identifying the storage slot type
|
|
26
|
+
- `subPointer` is a `u256` for sub-indexing (e.g., addresses in a mapping)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Example: Balance storage for address 0xABC...
|
|
30
|
+
pointer = 3 // balances mapping pointer
|
|
31
|
+
subPointer = 0xABC... // the address
|
|
32
|
+
storageKey = SHA256(3 || 0xABC...)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Storage Key Derivation Flow
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
---
|
|
39
|
+
config:
|
|
40
|
+
theme: dark
|
|
41
|
+
---
|
|
42
|
+
flowchart LR
|
|
43
|
+
subgraph Input["Developer Input"]
|
|
44
|
+
DEV["Contract Code"]
|
|
45
|
+
DEV -->|"Declares"| VAR1["totalSupplyPointer: u16"]
|
|
46
|
+
DEV -->|"Declares"| VAR2["balancesPointer: u16"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
subgraph Allocation["Runtime Allocation"]
|
|
50
|
+
BC["Blockchain.nextPointer"]
|
|
51
|
+
VAR1 -->|"Calls"| BC
|
|
52
|
+
VAR2 -->|"Calls"| BC
|
|
53
|
+
BC -->|"Returns 0"| P0["Pointer 0"]
|
|
54
|
+
BC -->|"Returns 1"| P1["Pointer 1"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
subgraph SimpleKey["Simple Value Key"]
|
|
58
|
+
P0 --> EMPTY["EMPTY_POINTER<br/>(u256.Zero)"]
|
|
59
|
+
EMPTY --> HASH1["SHA256(0 || 0x00...00)"]
|
|
60
|
+
HASH1 --> KEY1[("Storage Key<br/>for totalSupply")]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
subgraph MappingKey["Mapping Key (balances)"]
|
|
64
|
+
P1 --> ADDR["User Address<br/>0xABC..."]
|
|
65
|
+
ADDR --> HASH2["SHA256(1 || 0xABC...)"]
|
|
66
|
+
HASH2 --> KEY2[("Storage Key<br/>for balances[0xABC]")]
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Storage Layout
|
|
71
|
+
|
|
72
|
+
```mermaid
|
|
73
|
+
---
|
|
74
|
+
config:
|
|
75
|
+
theme: dark
|
|
76
|
+
---
|
|
77
|
+
flowchart LR
|
|
78
|
+
CS[Contract Storage] --> P0["Pointer 0: totalSupply"]
|
|
79
|
+
CS --> P1["Pointer 1: name"]
|
|
80
|
+
CS --> P2["Pointer 2: symbol"]
|
|
81
|
+
CS --> P3["Pointer 3: balances mapping"]
|
|
82
|
+
CS --> P4["Pointer 4: allowances mapping"]
|
|
83
|
+
|
|
84
|
+
P3 --> S1["subPointer 0xAAA -> balance"]
|
|
85
|
+
P3 --> S2["subPointer 0xBBB -> balance"]
|
|
86
|
+
P3 --> S3["..."]
|
|
87
|
+
|
|
88
|
+
P4 --> N1["owner+spender hash -> allowance"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Solidity vs OPNet Storage Model
|
|
92
|
+
|
|
93
|
+
In Solidity, storage slots are assigned implicitly by the compiler. In OPNet, you explicitly allocate pointers at runtime.
|
|
94
|
+
|
|
95
|
+
### Quick Reference Table
|
|
96
|
+
|
|
97
|
+
| Feature | Solidity | OPNet |
|
|
98
|
+
|---------|----------|-------|
|
|
99
|
+
| Storage slot assignment | Implicit (compiler) | Explicit (`Blockchain.nextPointer`) |
|
|
100
|
+
| Hash function | keccak256 | SHA256 |
|
|
101
|
+
| Mapping type | `mapping(K => V)` | `StoredMapU256`, `AddressMemoryMap`, `MapOfMap<T>` |
|
|
102
|
+
| Array type | `T[]` | `StoredU256Array`, `StoredAddressArray`, etc. |
|
|
103
|
+
| Simple value | `uint256 public x;` | `StoredU256` |
|
|
104
|
+
| String storage | `string public s;` | `StoredString` |
|
|
105
|
+
| Boolean storage | `bool public b;` | `StoredBoolean` |
|
|
106
|
+
| Address storage | `address public a;` | `StoredAddress` |
|
|
107
|
+
| Nested mapping | `mapping(a => mapping(b => c))` | `MapOfMap<T>` |
|
|
108
|
+
| Default uint value | `0` | `u256.Zero` |
|
|
109
|
+
| Maximum slots/pointers | ~2^256 | 65,535 (`u16`) |
|
|
110
|
+
|
|
111
|
+
### Type Mapping Reference
|
|
112
|
+
|
|
113
|
+
| Solidity Type | OPNet Equivalent | Notes |
|
|
114
|
+
|---------------|------------------|-------|
|
|
115
|
+
| `uint256` | `StoredU256` | 32 bytes |
|
|
116
|
+
| `uint64` (packed) | `StoredU64` | Stores up to 4 u64 values in one slot |
|
|
117
|
+
| `uint32` (packed) | `StoredU32` | Stores up to 8 u32 values in one slot |
|
|
118
|
+
| `bool` | `StoredBoolean` | 1 byte |
|
|
119
|
+
| `string` | `StoredString` | Variable length |
|
|
120
|
+
| `address` | `StoredAddress` | 32 bytes |
|
|
121
|
+
| `uint256[]` | `StoredU256Array` | Dynamic array |
|
|
122
|
+
| `address[]` | `StoredAddressArray` | Dynamic array |
|
|
123
|
+
| `mapping(address => uint256)` | `AddressMemoryMap` | Address-keyed |
|
|
124
|
+
| `mapping(uint256 => uint256)` | `StoredMapU256` | u256-keyed |
|
|
125
|
+
| `mapping(address => mapping(address => uint256))` | `MapOfMap<u256>` | Two-level nesting |
|
|
126
|
+
|
|
127
|
+
### Side-by-Side Code Comparison
|
|
128
|
+
|
|
129
|
+
```solidity
|
|
130
|
+
// Solidity - Implicit slot assignment
|
|
131
|
+
contract Token {
|
|
132
|
+
uint256 public totalSupply; // slot 0 (assigned by compiler)
|
|
133
|
+
string public name; // slot 1 (assigned by compiler)
|
|
134
|
+
mapping(address => uint256) balances; // slot 2 (assigned by compiler)
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// OPNet - Explicit pointer allocation
|
|
140
|
+
export class Token extends OP_NET {
|
|
141
|
+
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer; // ~0 (allocated at runtime)
|
|
142
|
+
private readonly namePointer: u16 = Blockchain.nextPointer; // ~1 (allocated at runtime)
|
|
143
|
+
private readonly balancesPointer: u16 = Blockchain.nextPointer; // ~2 (allocated at runtime)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```mermaid
|
|
148
|
+
---
|
|
149
|
+
config:
|
|
150
|
+
theme: dark
|
|
151
|
+
---
|
|
152
|
+
flowchart LR
|
|
153
|
+
subgraph SolidityFlow["Solidity (Ethereum)"]
|
|
154
|
+
S_USER[("👤 User")] -->|"Sends ETH + calldata"| S_TX["Ethereum Transaction"]
|
|
155
|
+
S_TX -->|"EVM executes"| S_CONTRACT["Smart Contract"]
|
|
156
|
+
|
|
157
|
+
subgraph S_Storage["Storage (Implicit)"]
|
|
158
|
+
S_COMPILER["Compiler assigns slots<br/>at compile time"]
|
|
159
|
+
S_SLOT0["Slot 0: totalSupply"]
|
|
160
|
+
S_SLOT1["Slot 1: balances"]
|
|
161
|
+
S_SLOT2["Slot 2: allowances"]
|
|
162
|
+
S_COMPILER -.->|"Hidden from dev"| S_SLOT0
|
|
163
|
+
S_COMPILER -.->|"Hidden from dev"| S_SLOT1
|
|
164
|
+
S_COMPILER -.->|"Hidden from dev"| S_SLOT2
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
S_CONTRACT -->|"keccak256(slot.key)"| S_Storage
|
|
168
|
+
S_CONTRACT -->|"CAN hold ETH"| S_CUSTODY["Contract Custody<br/>address(this).balance"]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
subgraph OPNetFlow["OPNet (Bitcoin L1)"]
|
|
172
|
+
O_USER[("👤 User")] -->|"Signs Bitcoin TX"| O_TX["Bitcoin Transaction"]
|
|
173
|
+
O_TX -->|"WASM executes"| O_CONTRACT["Smart Contract"]
|
|
174
|
+
|
|
175
|
+
subgraph O_Storage["Storage (Explicit)"]
|
|
176
|
+
O_RUNTIME["Runtime allocates ptrs<br/>at execution time"]
|
|
177
|
+
O_PTR0["Pointer 0: totalSupplyPointer"]
|
|
178
|
+
O_PTR1["Pointer 1: balancesPointer"]
|
|
179
|
+
O_PTR2["Pointer 2: allowancesPointer"]
|
|
180
|
+
O_RUNTIME -->|"Dev controls"| O_PTR0
|
|
181
|
+
O_RUNTIME -->|"Dev controls"| O_PTR1
|
|
182
|
+
O_RUNTIME -->|"Dev controls"| O_PTR2
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
O_CONTRACT -->|"SHA256(ptr || subPtr)"| O_Storage
|
|
186
|
+
O_CONTRACT -->|"CANNOT hold BTC"| O_VERIFY["Verify-Only Pattern<br/>blockchain.tx.outputs"]
|
|
187
|
+
O_VERIFY -->|"Validates"| O_TX
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
subgraph KeyDiff["Critical Differences"]
|
|
191
|
+
DIFF1["Custody: Solidity holds funds,<br/>OPNet verifies outputs"]
|
|
192
|
+
DIFF2["Storage: Solidity implicit slots,<br/>OPNet explicit pointers"]
|
|
193
|
+
DIFF3["Hash: Solidity keccak256,<br/>OPNet SHA256"]
|
|
194
|
+
DIFF4["Execution: Solidity EVM,<br/>OPNet WASM"]
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## CRITICAL: Map Implementation Warning
|
|
199
|
+
|
|
200
|
+
> **DO NOT USE AssemblyScript's Built-in Map**
|
|
201
|
+
>
|
|
202
|
+
> When creating custom map implementations or extending map functionality, you **MUST** use the Map class from `@btc-vision/btc-runtime/runtime`, NOT the built-in AssemblyScript Map.
|
|
203
|
+
>
|
|
204
|
+
> **Why the AssemblyScript Map is broken for blockchain:**
|
|
205
|
+
> - NOT optimized for blockchain storage patterns
|
|
206
|
+
> - Does NOT handle Uint8Array buffers as keys correctly
|
|
207
|
+
> - Does NOT work properly with Address key comparisons
|
|
208
|
+
> - Will cause silent data corruption or key collisions
|
|
209
|
+
>
|
|
210
|
+
> **CORRECT:**
|
|
211
|
+
> ```typescript
|
|
212
|
+
> import { Map } from '@btc-vision/btc-runtime/runtime';
|
|
213
|
+
>
|
|
214
|
+
> export class MyCustomMap<V> extends Map<Address, V> {
|
|
215
|
+
> // Your implementation
|
|
216
|
+
> }
|
|
217
|
+
> ```
|
|
218
|
+
>
|
|
219
|
+
> **WRONG:**
|
|
220
|
+
> ```typescript
|
|
221
|
+
> // DO NOT DO THIS - will break!
|
|
222
|
+
> const map = new Map<Uint8Array, u256>(); // AssemblyScript Map
|
|
223
|
+
> ```
|
|
224
|
+
>
|
|
225
|
+
> The btc-runtime Map is specifically designed to:
|
|
226
|
+
> - Handle Address and Uint8Array key comparisons correctly
|
|
227
|
+
> - Optimize for blockchain storage access patterns
|
|
228
|
+
> - Support proper serialization for persistent storage
|
|
229
|
+
> - Prevent key collisions with custom equality logic
|
|
230
|
+
|
|
231
|
+
## Pointer Allocation
|
|
232
|
+
|
|
233
|
+
### Allocating Pointers
|
|
234
|
+
|
|
235
|
+
Use `Blockchain.nextPointer` to allocate unique pointers:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { Blockchain } from '@btc-vision/btc-runtime/runtime';
|
|
239
|
+
|
|
240
|
+
@final
|
|
241
|
+
export class MyContract extends OP_NET {
|
|
242
|
+
// Each call to nextPointer returns a unique u16
|
|
243
|
+
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer;
|
|
244
|
+
private readonly namePointer: u16 = Blockchain.nextPointer;
|
|
245
|
+
private readonly balancesPointer: u16 = Blockchain.nextPointer;
|
|
246
|
+
private readonly allowancesPointer: u16 = Blockchain.nextPointer;
|
|
247
|
+
|
|
248
|
+
// ...
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```mermaid
|
|
253
|
+
---
|
|
254
|
+
config:
|
|
255
|
+
theme: dark
|
|
256
|
+
---
|
|
257
|
+
sequenceDiagram
|
|
258
|
+
participant User
|
|
259
|
+
participant Bitcoin as Bitcoin L1
|
|
260
|
+
participant OPNet as OPNet Node
|
|
261
|
+
participant WASM as WASM Runtime
|
|
262
|
+
participant Contract
|
|
263
|
+
participant Blockchain as Blockchain API
|
|
264
|
+
participant Storage as Storage System
|
|
265
|
+
|
|
266
|
+
User->>Bitcoin: Broadcast Transaction
|
|
267
|
+
Bitcoin->>OPNet: New Block with TX
|
|
268
|
+
OPNet->>WASM: Execute Contract
|
|
269
|
+
WASM->>Contract: Instantiate
|
|
270
|
+
Contract->>Blockchain: nextPointer
|
|
271
|
+
Blockchain-->>Contract: 0 (totalSupply)
|
|
272
|
+
Contract->>Blockchain: nextPointer
|
|
273
|
+
Blockchain-->>Contract: 1 (name)
|
|
274
|
+
Contract->>Blockchain: nextPointer
|
|
275
|
+
Blockchain-->>Contract: 2 (balances)
|
|
276
|
+
Contract->>Blockchain: nextPointer
|
|
277
|
+
Blockchain-->>Contract: 3 (allowances)
|
|
278
|
+
Contract->>Storage: Read/Write with pointers
|
|
279
|
+
Storage-->>Contract: Data
|
|
280
|
+
Contract-->>WASM: Execution Result
|
|
281
|
+
WASM-->>OPNet: State Changes
|
|
282
|
+
OPNet->>OPNet: Update State Root
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## System Architecture
|
|
286
|
+
|
|
287
|
+
The following diagram shows how storage fits into the overall OPNet architecture:
|
|
288
|
+
|
|
289
|
+
```mermaid
|
|
290
|
+
---
|
|
291
|
+
config:
|
|
292
|
+
theme: dark
|
|
293
|
+
---
|
|
294
|
+
flowchart TD
|
|
295
|
+
subgraph UserLayer["User Layer"]
|
|
296
|
+
USER[("👤 User")]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
subgraph BitcoinL1["Bitcoin L1"]
|
|
300
|
+
BTC_TX["Bitcoin Transaction"]
|
|
301
|
+
BTC_BLOCK["Bitcoin Block"]
|
|
302
|
+
UTXO["UTXOs"]
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
subgraph OPNetConsensus["OPNet Consensus Layer"]
|
|
306
|
+
INDEXER["OPNet Nodes"]
|
|
307
|
+
WASM["WASM Runtime"]
|
|
308
|
+
EPOCH["Epoch Mining<br/>SHA1 PoW"]
|
|
309
|
+
CHECKPOINT["State Checksum<br/>Root Hash"]
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
subgraph Contract["Smart Contract"]
|
|
313
|
+
ENTRY["Contract Entry Point"]
|
|
314
|
+
LOGIC["Business Logic"]
|
|
315
|
+
VERIFY["Output Verification<br/>blockchain.tx.outputs"]
|
|
316
|
+
PTR_ALLOC["Pointer Allocation<br/>Blockchain.nextPointer"]
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
subgraph StorageSystem["Storage System"]
|
|
320
|
+
PTR["Pointer (u16)<br/>0-65535 slots"]
|
|
321
|
+
SUBPTR["SubPointer (u256)<br/>mapping keys"]
|
|
322
|
+
HASH["SHA256(ptr || subPtr)"]
|
|
323
|
+
STORAGE[("Persistent State<br/>Key-Value Store")]
|
|
324
|
+
|
|
325
|
+
PTR --> HASH
|
|
326
|
+
SUBPTR --> HASH
|
|
327
|
+
HASH --> STORAGE
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
USER -->|"Signs & Broadcasts"| BTC_TX
|
|
331
|
+
BTC_TX -->|"Included in"| BTC_BLOCK
|
|
332
|
+
BTC_BLOCK -->|"Parsed by"| INDEXER
|
|
333
|
+
INDEXER -->|"Executes in"| WASM
|
|
334
|
+
WASM -->|"Runs"| ENTRY
|
|
335
|
+
ENTRY --> LOGIC
|
|
336
|
+
LOGIC -->|"Non-custodial verify"| VERIFY
|
|
337
|
+
LOGIC -->|"Allocates"| PTR_ALLOC
|
|
338
|
+
PTR_ALLOC -->|"Returns u16"| PTR
|
|
339
|
+
LOGIC -->|"Read/Write"| STORAGE
|
|
340
|
+
|
|
341
|
+
INDEXER -->|"Every 20 blocks"| EPOCH
|
|
342
|
+
EPOCH -->|"Produces"| CHECKPOINT
|
|
343
|
+
CHECKPOINT -->|"Anchors to"| BTC_BLOCK
|
|
344
|
+
|
|
345
|
+
VERIFY -->|"Validates"| UTXO
|
|
346
|
+
BTC_TX -->|"Creates/Spends"| UTXO
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Storage Types
|
|
350
|
+
|
|
351
|
+
OPNet provides typed storage classes for common data types:
|
|
352
|
+
|
|
353
|
+
### Primitive Storage
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import {
|
|
357
|
+
StoredU256,
|
|
358
|
+
StoredU64,
|
|
359
|
+
StoredU32,
|
|
360
|
+
StoredBoolean,
|
|
361
|
+
StoredString,
|
|
362
|
+
StoredAddress,
|
|
363
|
+
EMPTY_POINTER,
|
|
364
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
365
|
+
|
|
366
|
+
// Usage
|
|
367
|
+
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer;
|
|
368
|
+
private readonly _totalSupply: StoredU256 = new StoredU256(
|
|
369
|
+
this.totalSupplyPointer,
|
|
370
|
+
EMPTY_POINTER
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Read value
|
|
374
|
+
const supply = this._totalSupply.value;
|
|
375
|
+
|
|
376
|
+
// Write value
|
|
377
|
+
this._totalSupply.value = newSupply;
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Array Storage
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import {
|
|
384
|
+
ABIDataTypes,
|
|
385
|
+
BytesWriter,
|
|
386
|
+
Calldata,
|
|
387
|
+
StoredU256Array,
|
|
388
|
+
StoredU128Array,
|
|
389
|
+
StoredAddressArray,
|
|
390
|
+
StoredBooleanArray,
|
|
391
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
392
|
+
|
|
393
|
+
// Usage
|
|
394
|
+
private readonly holdersPointer: u16 = Blockchain.nextPointer;
|
|
395
|
+
private readonly holders: StoredAddressArray = new StoredAddressArray(this.holdersPointer, EMPTY_POINTER);
|
|
396
|
+
|
|
397
|
+
// Operations
|
|
398
|
+
@method({ name: 'holder', type: ABIDataTypes.ADDRESS })
|
|
399
|
+
public addHolder(calldata: Calldata): BytesWriter {
|
|
400
|
+
const newHolder = calldata.readAddress();
|
|
401
|
+
this.holders.push(newHolder);
|
|
402
|
+
this.holders.save(); // Commit changes
|
|
403
|
+
return new BytesWriter(0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const holder = this.holders.get(index);
|
|
407
|
+
const length = this.holders.getLength();
|
|
408
|
+
this.holders.deleteLast();
|
|
409
|
+
this.holders.save();
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Map Storage
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
416
|
+
import {
|
|
417
|
+
Address,
|
|
418
|
+
StoredMapU256,
|
|
419
|
+
AddressMemoryMap,
|
|
420
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
421
|
+
|
|
422
|
+
// Simple mapping
|
|
423
|
+
private readonly balancesPointer: u16 = Blockchain.nextPointer;
|
|
424
|
+
private readonly balances: StoredMapU256 = new StoredMapU256(this.balancesPointer);
|
|
425
|
+
|
|
426
|
+
// Address-keyed mapping (default value is u256.Zero)
|
|
427
|
+
private readonly balanceMap: AddressMemoryMap;
|
|
428
|
+
|
|
429
|
+
public constructor() {
|
|
430
|
+
super();
|
|
431
|
+
this.balanceMap = new AddressMemoryMap(this.balancesPointer);
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Storage Patterns
|
|
436
|
+
|
|
437
|
+
### Common Patterns Comparison
|
|
438
|
+
|
|
439
|
+
| Pattern | Solidity | OPNet |
|
|
440
|
+
|---------|----------|-------|
|
|
441
|
+
| Increment counter | `counter++;` | `counter.value = SafeMath.add(counter.value, u256.One);` |
|
|
442
|
+
| Read balance | `balances[addr]` | `balanceOf.get(addr)` |
|
|
443
|
+
| Write balance | `balances[addr] = x` | `balanceOf.set(addr, x)` |
|
|
444
|
+
| Check approval | `allowances[owner][spender]` | `allowances.get(owner).get(spender)` |
|
|
445
|
+
| Set approval | `allowances[owner][spender] = x` | `ownerMap = allowances.get(owner); ownerMap.set(spender, x); allowances.set(owner, ownerMap);` |
|
|
446
|
+
| Array push | `arr.push(x)` | `arr.push(x); arr.save()` |
|
|
447
|
+
| Array length | `arr.length` | `arr.getLength()` |
|
|
448
|
+
| Array access | `arr[i]` | `arr.get(i)` |
|
|
449
|
+
| Require/revert | `require(cond, "msg")` | `if (!cond) throw new Revert("msg")` |
|
|
450
|
+
| Get sender | `msg.sender` | `Blockchain.tx.sender` |
|
|
451
|
+
| Get origin | `tx.origin` | `Blockchain.tx.origin` |
|
|
452
|
+
| Block number | `block.number` | `Blockchain.block.number` |
|
|
453
|
+
| Block timestamp | `block.timestamp` | `Blockchain.block.medianTime` |
|
|
454
|
+
|
|
455
|
+
### Simple Value
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
459
|
+
import {
|
|
460
|
+
Blockchain,
|
|
461
|
+
SafeMath,
|
|
462
|
+
StoredU256,
|
|
463
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
464
|
+
|
|
465
|
+
// Solidity: uint256 public counter;
|
|
466
|
+
private readonly counterPointer: u16 = Blockchain.nextPointer;
|
|
467
|
+
private readonly counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
|
|
468
|
+
|
|
469
|
+
// Increment
|
|
470
|
+
this.counter.value = SafeMath.add(this.counter.value, u256.One);
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Mapping (address => uint256)
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
477
|
+
import {
|
|
478
|
+
ABIDataTypes,
|
|
479
|
+
Address,
|
|
480
|
+
AddressMemoryMap,
|
|
481
|
+
SafeMath,
|
|
482
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
483
|
+
|
|
484
|
+
// Solidity: mapping(address => uint256) public balances;
|
|
485
|
+
private readonly balancesPointer: u16 = Blockchain.nextPointer;
|
|
486
|
+
private readonly balanceOf: AddressMemoryMap;
|
|
487
|
+
|
|
488
|
+
public constructor() {
|
|
489
|
+
super();
|
|
490
|
+
this.balanceOf = new AddressMemoryMap(this.balancesPointer);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Get balance
|
|
494
|
+
@method({ name: 'address', type: ABIDataTypes.ADDRESS })
|
|
495
|
+
@returns({ name: 'balance', type: ABIDataTypes.UINT256 })
|
|
496
|
+
public getBalance(address: Address): u256 {
|
|
497
|
+
return this.balanceOf.get(address);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Set balance (using SafeMath for operations)
|
|
501
|
+
@method(
|
|
502
|
+
{ name: 'address', type: ABIDataTypes.ADDRESS },
|
|
503
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 }
|
|
504
|
+
)
|
|
505
|
+
public setBalance(address: Address, amount: u256): void {
|
|
506
|
+
this.balanceOf.set(address, amount);
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Nested Mapping (address => address => uint256)
|
|
511
|
+
|
|
512
|
+
For nested mappings like allowances, use `MapOfMap<T>` which provides a two-step get/set pattern:
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
516
|
+
import {
|
|
517
|
+
ABIDataTypes,
|
|
518
|
+
Address,
|
|
519
|
+
MapOfMap,
|
|
520
|
+
Nested,
|
|
521
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
522
|
+
|
|
523
|
+
// Solidity: mapping(address => mapping(address => uint256)) public allowances;
|
|
524
|
+
private readonly allowancesPointer: u16 = Blockchain.nextPointer;
|
|
525
|
+
private readonly allowances: MapOfMap<u256>;
|
|
526
|
+
|
|
527
|
+
public constructor() {
|
|
528
|
+
super();
|
|
529
|
+
this.allowances = new MapOfMap<u256>(this.allowancesPointer);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Getting nested value - two-step process
|
|
533
|
+
@method(
|
|
534
|
+
{ name: 'owner', type: ABIDataTypes.ADDRESS },
|
|
535
|
+
{ name: 'spender', type: ABIDataTypes.ADDRESS }
|
|
536
|
+
)
|
|
537
|
+
@returns({ name: 'allowance', type: ABIDataTypes.UINT256 })
|
|
538
|
+
public getAllowance(owner: Address, spender: Address): u256 {
|
|
539
|
+
const ownerMap = this.allowances.get(owner); // Returns Nested<u256>
|
|
540
|
+
return ownerMap.get(spender); // Returns u256
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Setting nested value - get, modify, commit back
|
|
544
|
+
protected setAllowance(owner: Address, spender: Address, amount: u256): void {
|
|
545
|
+
const ownerMap = this.allowances.get(owner);
|
|
546
|
+
ownerMap.set(spender, amount);
|
|
547
|
+
this.allowances.set(owner, ownerMap); // Commit back
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Struct-like Storage
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
// Solidity:
|
|
555
|
+
// struct User { address addr; uint256 balance; bool active; }
|
|
556
|
+
// mapping(uint256 => User) public users;
|
|
557
|
+
|
|
558
|
+
// OPNet: Use multiple pointers or encode into u256
|
|
559
|
+
private readonly userAddressPointer: u16 = Blockchain.nextPointer;
|
|
560
|
+
private readonly userBalancePointer: u16 = Blockchain.nextPointer;
|
|
561
|
+
private readonly userActivePointer: u16 = Blockchain.nextPointer;
|
|
562
|
+
|
|
563
|
+
private readonly userAddresses: StoredMapU256 = new StoredMapU256(this.userAddressPointer);
|
|
564
|
+
private readonly userBalances: StoredMapU256 = new StoredMapU256(this.userBalancePointer);
|
|
565
|
+
private readonly userActives: StoredMapU256 = new StoredMapU256(this.userActivePointer);
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Complete ERC-20 Style Comparison
|
|
569
|
+
|
|
570
|
+
Here's a side-by-side comparison of a complete token contract:
|
|
571
|
+
|
|
572
|
+
**Solidity:**
|
|
573
|
+
```solidity
|
|
574
|
+
// SPDX-License-Identifier: MIT
|
|
575
|
+
pragma solidity ^0.8.0;
|
|
576
|
+
|
|
577
|
+
contract SimpleToken {
|
|
578
|
+
string public name;
|
|
579
|
+
string public symbol;
|
|
580
|
+
uint8 public decimals = 18;
|
|
581
|
+
uint256 public totalSupply;
|
|
582
|
+
|
|
583
|
+
mapping(address => uint256) public balanceOf;
|
|
584
|
+
mapping(address => mapping(address => uint256)) public allowance;
|
|
585
|
+
|
|
586
|
+
event Transfer(address indexed from, address indexed to, uint256 value);
|
|
587
|
+
event Approval(address indexed owner, address indexed spender, uint256 value);
|
|
588
|
+
|
|
589
|
+
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
|
|
590
|
+
name = _name;
|
|
591
|
+
symbol = _symbol;
|
|
592
|
+
totalSupply = _initialSupply;
|
|
593
|
+
balanceOf[msg.sender] = _initialSupply;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function transfer(address to, uint256 amount) external returns (bool) {
|
|
597
|
+
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
|
|
598
|
+
balanceOf[msg.sender] -= amount;
|
|
599
|
+
balanceOf[to] += amount;
|
|
600
|
+
emit Transfer(msg.sender, to, amount);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function approve(address spender, uint256 amount) external returns (bool) {
|
|
605
|
+
allowance[msg.sender][spender] = amount;
|
|
606
|
+
emit Approval(msg.sender, spender, amount);
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
|
|
611
|
+
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
|
|
612
|
+
require(balanceOf[from] >= amount, "Insufficient balance");
|
|
613
|
+
allowance[from][msg.sender] -= amount;
|
|
614
|
+
balanceOf[from] -= amount;
|
|
615
|
+
balanceOf[to] += amount;
|
|
616
|
+
emit Transfer(from, to, amount);
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**OPNet:**
|
|
623
|
+
```typescript
|
|
624
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
625
|
+
import {
|
|
626
|
+
Address,
|
|
627
|
+
AddressMemoryMap,
|
|
628
|
+
Blockchain,
|
|
629
|
+
BytesWriter,
|
|
630
|
+
Calldata,
|
|
631
|
+
MapOfMap,
|
|
632
|
+
OP_NET,
|
|
633
|
+
Revert,
|
|
634
|
+
SafeMath,
|
|
635
|
+
StoredString,
|
|
636
|
+
StoredU256,
|
|
637
|
+
EMPTY_POINTER,
|
|
638
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
639
|
+
|
|
640
|
+
@final
|
|
641
|
+
export class SimpleToken extends OP_NET {
|
|
642
|
+
// Pointer allocation (equivalent to slot assignment)
|
|
643
|
+
private readonly namePointer: u16 = Blockchain.nextPointer;
|
|
644
|
+
private readonly symbolPointer: u16 = Blockchain.nextPointer;
|
|
645
|
+
private readonly totalSupplyPointer: u16 = Blockchain.nextPointer;
|
|
646
|
+
private readonly balancesPointer: u16 = Blockchain.nextPointer;
|
|
647
|
+
private readonly allowancesPointer: u16 = Blockchain.nextPointer;
|
|
648
|
+
|
|
649
|
+
// Storage variables
|
|
650
|
+
private readonly _name: StoredString = new StoredString(this.namePointer, 0);
|
|
651
|
+
private readonly _symbol: StoredString = new StoredString(this.symbolPointer, 0);
|
|
652
|
+
private readonly _decimals: u8 = 18; // Constant value, no storage needed
|
|
653
|
+
private readonly _totalSupply: StoredU256 = new StoredU256(this.totalSupplyPointer, EMPTY_POINTER);
|
|
654
|
+
private readonly _balanceOf: AddressMemoryMap;
|
|
655
|
+
private readonly _allowance: MapOfMap<u256>;
|
|
656
|
+
|
|
657
|
+
public constructor() {
|
|
658
|
+
super();
|
|
659
|
+
this._balanceOf = new AddressMemoryMap(this.balancesPointer);
|
|
660
|
+
this._allowance = new MapOfMap<u256>(this.allowancesPointer);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Equivalent to Solidity constructor
|
|
664
|
+
public override onDeployment(calldata: Calldata): void {
|
|
665
|
+
this._name.value = calldata.readString();
|
|
666
|
+
this._symbol.value = calldata.readString();
|
|
667
|
+
const initialSupply = calldata.readU256();
|
|
668
|
+
this._totalSupply.value = initialSupply;
|
|
669
|
+
this._balanceOf.set(Blockchain.tx.origin, initialSupply);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// function transfer(address to, uint256 amount) external returns (bool)
|
|
673
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
674
|
+
const to = calldata.readAddress();
|
|
675
|
+
const amount = calldata.readU256();
|
|
676
|
+
const sender = Blockchain.tx.sender;
|
|
677
|
+
|
|
678
|
+
const senderBalance = this._balanceOf.get(sender);
|
|
679
|
+
if (senderBalance < amount) {
|
|
680
|
+
throw new Revert('Insufficient balance');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
this._balanceOf.set(sender, SafeMath.sub(senderBalance, amount));
|
|
684
|
+
this._balanceOf.set(to, SafeMath.add(this._balanceOf.get(to), amount));
|
|
685
|
+
|
|
686
|
+
// Emit Transfer event (implementation depends on event system)
|
|
687
|
+
|
|
688
|
+
const writer = new BytesWriter(1);
|
|
689
|
+
writer.writeBoolean(true);
|
|
690
|
+
return writer;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// function approve(address spender, uint256 amount) external returns (bool)
|
|
694
|
+
public approve(calldata: Calldata): BytesWriter {
|
|
695
|
+
const spender = calldata.readAddress();
|
|
696
|
+
const amount = calldata.readU256();
|
|
697
|
+
const sender = Blockchain.tx.sender;
|
|
698
|
+
|
|
699
|
+
// MapOfMap pattern: get nested, modify, commit back
|
|
700
|
+
const senderAllowances = this._allowance.get(sender);
|
|
701
|
+
senderAllowances.set(spender, amount);
|
|
702
|
+
this._allowance.set(sender, senderAllowances);
|
|
703
|
+
|
|
704
|
+
const writer = new BytesWriter(1);
|
|
705
|
+
writer.writeBoolean(true);
|
|
706
|
+
return writer;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// function transferFrom(address from, address to, uint256 amount) external returns (bool)
|
|
710
|
+
public transferFrom(calldata: Calldata): BytesWriter {
|
|
711
|
+
const from = calldata.readAddress();
|
|
712
|
+
const to = calldata.readAddress();
|
|
713
|
+
const amount = calldata.readU256();
|
|
714
|
+
const sender = Blockchain.tx.sender;
|
|
715
|
+
|
|
716
|
+
// Check allowance
|
|
717
|
+
const fromAllowances = this._allowance.get(from);
|
|
718
|
+
const currentAllowance = fromAllowances.get(sender);
|
|
719
|
+
if (currentAllowance < amount) {
|
|
720
|
+
throw new Revert('Insufficient allowance');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Check balance
|
|
724
|
+
const fromBalance = this._balanceOf.get(from);
|
|
725
|
+
if (fromBalance < amount) {
|
|
726
|
+
throw new Revert('Insufficient balance');
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Update allowance
|
|
730
|
+
fromAllowances.set(sender, SafeMath.sub(currentAllowance, amount));
|
|
731
|
+
this._allowance.set(from, fromAllowances);
|
|
732
|
+
|
|
733
|
+
// Update balances
|
|
734
|
+
this._balanceOf.set(from, SafeMath.sub(fromBalance, amount));
|
|
735
|
+
this._balanceOf.set(to, SafeMath.add(this._balanceOf.get(to), amount));
|
|
736
|
+
|
|
737
|
+
const writer = new BytesWriter(1);
|
|
738
|
+
writer.writeBoolean(true);
|
|
739
|
+
return writer;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
## Reading and Writing
|
|
745
|
+
|
|
746
|
+
```mermaid
|
|
747
|
+
---
|
|
748
|
+
config:
|
|
749
|
+
theme: dark
|
|
750
|
+
---
|
|
751
|
+
sequenceDiagram
|
|
752
|
+
participant TX as Bitcoin Transaction
|
|
753
|
+
participant Contract as Contract
|
|
754
|
+
participant Logic as Business Logic
|
|
755
|
+
participant Storage as Storage System
|
|
756
|
+
participant Buffer as Memory Buffer
|
|
757
|
+
participant State as Persistent State
|
|
758
|
+
|
|
759
|
+
Note over TX,State: Read Operation
|
|
760
|
+
TX->>Contract: TX Input (User Signs)
|
|
761
|
+
Contract->>Logic: Method Call
|
|
762
|
+
Logic->>Storage: Get pointer + subPointer
|
|
763
|
+
Storage->>Storage: Compute SHA256 key
|
|
764
|
+
Storage->>Storage: Blockchain.getStorageAt
|
|
765
|
+
Storage-->>Logic: Decode to typed value
|
|
766
|
+
|
|
767
|
+
Note over TX,State: Write Operation
|
|
768
|
+
Logic->>Storage: Get pointer + subPointer
|
|
769
|
+
Storage->>Storage: Compute SHA256 key
|
|
770
|
+
Storage->>Storage: Encode typed value
|
|
771
|
+
Storage->>Buffer: Buffer in memory
|
|
772
|
+
|
|
773
|
+
Note over TX,State: Verification & Commit
|
|
774
|
+
Logic->>TX: Verify TX Outputs (UTXOs)
|
|
775
|
+
TX-->>Contract: TX Complete
|
|
776
|
+
Buffer->>State: Commit on TX Success
|
|
777
|
+
State->>State: Update State Checksum
|
|
778
|
+
Note over State: Every 20 blocks: Epoch Root
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### Read Operations
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
// Read primitive
|
|
785
|
+
const value = this._totalSupply.value;
|
|
786
|
+
|
|
787
|
+
// Read from map
|
|
788
|
+
const balance = this.balanceOf.get(address);
|
|
789
|
+
|
|
790
|
+
// Read from array
|
|
791
|
+
const holder = this.holders.get(index);
|
|
792
|
+
const length = this.holders.getLength();
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### Write Operations
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
// Write primitive
|
|
799
|
+
this._totalSupply.value = newValue;
|
|
800
|
+
|
|
801
|
+
// Write to map
|
|
802
|
+
this.balanceOf.set(address, newBalance);
|
|
803
|
+
|
|
804
|
+
// Write to array
|
|
805
|
+
this.holders.push(newAddress);
|
|
806
|
+
this.holders.set(index, address);
|
|
807
|
+
this.holders.save(); // Commit changes
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Commit Optimization
|
|
811
|
+
|
|
812
|
+
For complex operations, delay commits until necessary:
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
import { SafeMath } from '@btc-vision/btc-runtime/runtime';
|
|
816
|
+
|
|
817
|
+
// Multiple operations without intermediate commits
|
|
818
|
+
const currentBalance = this.balanceOf.get(from);
|
|
819
|
+
const newBalance = SafeMath.sub(currentBalance, amount);
|
|
820
|
+
this.balanceOf.set(from, newBalance); // Value is buffered
|
|
821
|
+
|
|
822
|
+
// Changes are committed when transaction completes
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
## Default Values
|
|
826
|
+
|
|
827
|
+
Always provide sensible defaults:
|
|
828
|
+
|
|
829
|
+
```typescript
|
|
830
|
+
// u256 with EMPTY_POINTER
|
|
831
|
+
private balance: StoredU256 = new StoredU256(pointer, EMPTY_POINTER);
|
|
832
|
+
|
|
833
|
+
// String with index 0
|
|
834
|
+
private name: StoredString = new StoredString(pointer, 0);
|
|
835
|
+
|
|
836
|
+
// Boolean with false default
|
|
837
|
+
private paused: StoredBoolean = new StoredBoolean(pointer, false);
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## Storage Limits
|
|
841
|
+
|
|
842
|
+
| Limit | Value | Notes |
|
|
843
|
+
|-------|-------|-------|
|
|
844
|
+
| Pointers per contract | 65,535 | `u16` range |
|
|
845
|
+
| Array length | ~4 billion | `u32` range (default maxLength configurable) |
|
|
846
|
+
| String length | 65,535 bytes | Encoded in storage |
|
|
847
|
+
| Sub-pointers | `u256` range | Effectively unlimited |
|
|
848
|
+
|
|
849
|
+
## Best Practices
|
|
850
|
+
|
|
851
|
+
### 1. Allocate Pointers in Order
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
// Good: Sequential allocation
|
|
855
|
+
private ptr1: u16 = Blockchain.nextPointer;
|
|
856
|
+
private ptr2: u16 = Blockchain.nextPointer;
|
|
857
|
+
private ptr3: u16 = Blockchain.nextPointer;
|
|
858
|
+
|
|
859
|
+
// Bad: Gaps or manual assignment
|
|
860
|
+
private ptr1: u16 = 0;
|
|
861
|
+
private ptr2: u16 = 5; // Gap!
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
### 2. Use Typed Storage
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
// Good: Type-safe storage
|
|
868
|
+
private balance: StoredU256 = new StoredU256(ptr, EMPTY_POINTER);
|
|
869
|
+
|
|
870
|
+
// Avoid: Raw storage access (only for special cases)
|
|
871
|
+
import { encodePointer } from '@btc-vision/btc-runtime/runtime';
|
|
872
|
+
const pointerHash = encodePointer(ptr, subPtr);
|
|
873
|
+
Blockchain.setStorageAt(pointerHash, value);
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### 3. Initialize in Constructor or onDeployment
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
880
|
+
import {
|
|
881
|
+
Address,
|
|
882
|
+
AddressMemoryMap,
|
|
883
|
+
Blockchain,
|
|
884
|
+
Calldata,
|
|
885
|
+
OP_NET,
|
|
886
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
887
|
+
|
|
888
|
+
export class MyContract extends OP_NET {
|
|
889
|
+
private readonly balancesPointer: u16 = Blockchain.nextPointer;
|
|
890
|
+
private readonly balanceOf: AddressMemoryMap;
|
|
891
|
+
|
|
892
|
+
public constructor() {
|
|
893
|
+
super();
|
|
894
|
+
// Initialize storage maps in constructor
|
|
895
|
+
this.balanceOf = new AddressMemoryMap(this.balancesPointer);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
public override onDeployment(calldata: Calldata): void {
|
|
899
|
+
// Set initial values here
|
|
900
|
+
this._totalSupply.value = initialSupply;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### 4. Optimize Storage Access
|
|
906
|
+
|
|
907
|
+
```mermaid
|
|
908
|
+
---
|
|
909
|
+
config:
|
|
910
|
+
theme: dark
|
|
911
|
+
---
|
|
912
|
+
flowchart TD
|
|
913
|
+
subgraph Bad["Expensive: Multiple Storage Reads"]
|
|
914
|
+
LOOP1["for (i = 0; i < 100; i++)"]
|
|
915
|
+
LOOP1 --> READ1["Storage Read #1"]
|
|
916
|
+
LOOP1 --> READ2["Storage Read #2"]
|
|
917
|
+
LOOP1 --> READ3["Storage Read #..."]
|
|
918
|
+
LOOP1 --> READ100["Storage Read #100"]
|
|
919
|
+
READ1 --> COST1["100x Storage I/O"]
|
|
920
|
+
READ2 --> COST1
|
|
921
|
+
READ3 --> COST1
|
|
922
|
+
READ100 --> COST1
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
subgraph Good["Optimized: Cache and Batch"]
|
|
926
|
+
CACHE["Single Storage Read"]
|
|
927
|
+
CACHE --> MEM["Cache in Memory"]
|
|
928
|
+
MEM --> LOOP2["for (i = 0; i < 100; i++)<br/>Use cached value"]
|
|
929
|
+
LOOP2 --> WRITE["Single Storage Write"]
|
|
930
|
+
WRITE --> COST2["1x Read + 1x Write"]
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
COST1 -->|"vs"| COST2
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
import { SafeMath } from '@btc-vision/btc-runtime/runtime';
|
|
938
|
+
|
|
939
|
+
// Inefficient: Reading same value multiple times
|
|
940
|
+
for (let i = 0; i < 100; i++) {
|
|
941
|
+
const balance = this.balanceOf.get(address); // Storage read each time
|
|
942
|
+
// ...
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Better: Cache the value
|
|
946
|
+
const balance = this.balanceOf.get(address); // One storage read
|
|
947
|
+
for (let i = 0; i < 100; i++) {
|
|
948
|
+
// Use cached balance
|
|
949
|
+
// When modifying, use SafeMath
|
|
950
|
+
const updated = SafeMath.add(balance, u256.One);
|
|
951
|
+
}
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
## Transient Storage
|
|
955
|
+
|
|
956
|
+
For temporary data that doesn't persist between transactions:
|
|
957
|
+
|
|
958
|
+
```typescript
|
|
959
|
+
// Transient storage is cleared after each transaction
|
|
960
|
+
// Useful for reentrancy guards, temporary calculations, etc.
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
See [Advanced Storage](../storage/stored-primitives.md) for transient storage details.
|
|
964
|
+
|
|
965
|
+
---
|
|
966
|
+
|
|
967
|
+
**Navigation:**
|
|
968
|
+
- Previous: [Blockchain Environment](./blockchain-environment.md)
|
|
969
|
+
- Next: [Pointers](./pointers.md)
|