@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,744 @@
|
|
|
1
|
+
# Calldata
|
|
2
|
+
|
|
3
|
+
The `Calldata` type handles parsing of input parameters passed to contract methods. It provides methods to read various data types in sequence.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Calldata, Address, BytesWriter } from '@btc-vision/btc-runtime/runtime';
|
|
9
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
10
|
+
|
|
11
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
12
|
+
// Read parameters in order
|
|
13
|
+
const to: Address = calldata.readAddress();
|
|
14
|
+
const amount: u256 = calldata.readU256();
|
|
15
|
+
|
|
16
|
+
// ... process transfer
|
|
17
|
+
|
|
18
|
+
return new BytesWriter(0);
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Calldata Architecture
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
classDiagram
|
|
26
|
+
class Calldata {
|
|
27
|
+
<<extends BytesReader>>
|
|
28
|
+
+readU8() u8
|
|
29
|
+
+readU16(be) u16
|
|
30
|
+
+readU32(be) u32
|
|
31
|
+
+readU64(be) u64
|
|
32
|
+
+readU128(be) u128
|
|
33
|
+
+readU256(be) u256
|
|
34
|
+
+readI8() i8
|
|
35
|
+
+readI16() i16
|
|
36
|
+
+readI32() i32
|
|
37
|
+
+readI64(be) i64
|
|
38
|
+
+readI128(be) i128
|
|
39
|
+
+readAddress() Address
|
|
40
|
+
+readString(length) string
|
|
41
|
+
+readStringWithLength(be) string
|
|
42
|
+
+readBytes(length) Uint8Array
|
|
43
|
+
+readBytesWithLength(be) Uint8Array
|
|
44
|
+
+readBoolean() bool
|
|
45
|
+
+readSelector() Selector
|
|
46
|
+
+readAddressArray(be) Address[]
|
|
47
|
+
+readU256Array(be) u256[]
|
|
48
|
+
+readAddressMapU256(be) AddressMap~u256~
|
|
49
|
+
+getOffset() i32
|
|
50
|
+
+setOffset(offset) void
|
|
51
|
+
+byteLength i32
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class BytesReader {
|
|
55
|
+
-DataView buffer
|
|
56
|
+
-i32 currentOffset
|
|
57
|
+
+readU8() u8
|
|
58
|
+
+readU32() u32
|
|
59
|
+
+readU256() u256
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class Contract {
|
|
63
|
+
+transfer(calldata: Calldata) BytesWriter
|
|
64
|
+
+approve(calldata: Calldata) BytesWriter
|
|
65
|
+
+balanceOf(calldata: Calldata) BytesWriter
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
BytesReader <|-- Calldata : extends
|
|
69
|
+
Contract --> Calldata : receives
|
|
70
|
+
|
|
71
|
+
note for Calldata "Sequential parameter reading\nfrom encoded transaction data"
|
|
72
|
+
note for Contract "All public methods\nreceive Calldata"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Reading Data
|
|
76
|
+
|
|
77
|
+
### Primitive Types
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Boolean (1 byte)
|
|
81
|
+
const flag: bool = calldata.readBoolean();
|
|
82
|
+
|
|
83
|
+
// Unsigned integers
|
|
84
|
+
const u8val: u8 = calldata.readU8();
|
|
85
|
+
const u16val: u16 = calldata.readU16();
|
|
86
|
+
const u32val: u32 = calldata.readU32();
|
|
87
|
+
const u64val: u64 = calldata.readU64();
|
|
88
|
+
const u128val: u128 = calldata.readU128();
|
|
89
|
+
const u256val: u256 = calldata.readU256();
|
|
90
|
+
|
|
91
|
+
// Signed integers
|
|
92
|
+
const i8val: i8 = calldata.readI8();
|
|
93
|
+
const i16val: i16 = calldata.readI16();
|
|
94
|
+
const i32val: i32 = calldata.readI32();
|
|
95
|
+
const i64val: i64 = calldata.readI64();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Complex Types
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Address (32 bytes)
|
|
102
|
+
const addr: Address = calldata.readAddress();
|
|
103
|
+
|
|
104
|
+
// String with u32 length prefix
|
|
105
|
+
const name: string = calldata.readStringWithLength();
|
|
106
|
+
|
|
107
|
+
// String with known length (stops at null byte)
|
|
108
|
+
const fixedName: string = calldata.readString(32);
|
|
109
|
+
|
|
110
|
+
// Bytes with u32 length prefix
|
|
111
|
+
const data: Uint8Array = calldata.readBytesWithLength();
|
|
112
|
+
|
|
113
|
+
// Bytes with known length
|
|
114
|
+
const fixedData: Uint8Array = calldata.readBytes(64);
|
|
115
|
+
|
|
116
|
+
// Selector (4 bytes, big-endian)
|
|
117
|
+
const selector: Selector = calldata.readSelector();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Arrays
|
|
121
|
+
|
|
122
|
+
All array methods expect a u16 length prefix (max 65535 elements):
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Array of addresses
|
|
126
|
+
const addresses: Address[] = calldata.readAddressArray();
|
|
127
|
+
|
|
128
|
+
// Numeric arrays
|
|
129
|
+
const u8Values: u8[] = calldata.readU8Array();
|
|
130
|
+
const u16Values: u16[] = calldata.readU16Array();
|
|
131
|
+
const u32Values: u32[] = calldata.readU32Array();
|
|
132
|
+
const u64Values: u64[] = calldata.readU64Array();
|
|
133
|
+
const u128Values: u128[] = calldata.readU128Array();
|
|
134
|
+
const u256Values: u256[] = calldata.readU256Array();
|
|
135
|
+
|
|
136
|
+
// Array of variable-length buffers
|
|
137
|
+
const buffers: Uint8Array[] = calldata.readArrayOfBuffer();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Maps
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Address -> u256 mapping
|
|
144
|
+
const map: AddressMap<u256> = calldata.readAddressMapU256();
|
|
145
|
+
|
|
146
|
+
// Usage
|
|
147
|
+
const keys = map.keys();
|
|
148
|
+
for (let i = 0; i < keys.length; i++) {
|
|
149
|
+
const address = keys[i];
|
|
150
|
+
const value = map.get(address);
|
|
151
|
+
// Process...
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Read Order
|
|
156
|
+
|
|
157
|
+
**IMPORTANT:** Data must be read in the exact order it was written.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Correct order
|
|
161
|
+
public myMethod(calldata: Calldata): BytesWriter {
|
|
162
|
+
const address = calldata.readAddress(); // First
|
|
163
|
+
const amount = calldata.readU256(); // Second
|
|
164
|
+
const flag = calldata.readBoolean(); // Third
|
|
165
|
+
// ...
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Wrong order will read garbage!
|
|
169
|
+
public myMethod(calldata: Calldata): BytesWriter {
|
|
170
|
+
const amount = calldata.readU256(); // WRONG!
|
|
171
|
+
const address = calldata.readAddress(); // WRONG!
|
|
172
|
+
const flag = calldata.readBoolean();
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Sequential Read Flow
|
|
177
|
+
|
|
178
|
+
```mermaid
|
|
179
|
+
---
|
|
180
|
+
config:
|
|
181
|
+
theme: dark
|
|
182
|
+
---
|
|
183
|
+
flowchart LR
|
|
184
|
+
A["Calldata Buffer"] --> B["readAddress: 32 bytes"]
|
|
185
|
+
B --> C["readU256: 32 bytes"]
|
|
186
|
+
C --> D["readBoolean: 1 byte"]
|
|
187
|
+
D --> E{"hasMoreData?"}
|
|
188
|
+
E -->|"Yes"| F["Continue"]
|
|
189
|
+
E -->|"No"| G["Complete"]
|
|
190
|
+
|
|
191
|
+
B -.->|"Wrong Order"| H["Garbage Data/Revert"]
|
|
192
|
+
C -.->|"Wrong Order"| H
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Data Encoding
|
|
196
|
+
|
|
197
|
+
### Encoding Format
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
| Field 1 | Field 2 | Field 3 | ... |
|
|
201
|
+
|---------|---------|---------|-----|
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Each field is encoded according to its type:
|
|
205
|
+
|
|
206
|
+
| Type | Encoding |
|
|
207
|
+
|------|----------|
|
|
208
|
+
| `bool` | 1 byte (0 or 1) |
|
|
209
|
+
| `u8` | 1 byte |
|
|
210
|
+
| `u16` | 2 bytes (big-endian, default) |
|
|
211
|
+
| `u32` | 4 bytes (big-endian, default) |
|
|
212
|
+
| `u64` | 8 bytes (big-endian, default) |
|
|
213
|
+
| `u128` | 16 bytes (big-endian, default) |
|
|
214
|
+
| `u256` | 32 bytes (big-endian, default) |
|
|
215
|
+
| `Address` | 32 bytes |
|
|
216
|
+
| `Selector` | 4 bytes (big-endian u32) |
|
|
217
|
+
| `string` | 4-byte length (u32 BE) + UTF-8 bytes |
|
|
218
|
+
| `bytes` | 4-byte length (u32 BE) + raw bytes |
|
|
219
|
+
| `arrays` | 2-byte length (u16 BE) + elements |
|
|
220
|
+
|
|
221
|
+
### String Encoding (with length prefix)
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
| Length (4 bytes, BE) | UTF-8 Content |
|
|
225
|
+
|----------------------|---------------|
|
|
226
|
+
| 0x00 0x00 0x00 0x0B | "Hello World" |
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Array Encoding
|
|
230
|
+
|
|
231
|
+
Arrays use a u16 length prefix (max 65535 elements):
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
| Length (2 bytes, BE) | Element 1 | Element 2 | ... |
|
|
235
|
+
|----------------------|-----------|-----------|-----|
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Solidity vs OPNet Comparison
|
|
239
|
+
|
|
240
|
+
### Calldata Decoding Comparison Table
|
|
241
|
+
|
|
242
|
+
| Feature | Solidity | OPNet |
|
|
243
|
+
|---------|----------|-------|
|
|
244
|
+
| **Parameter access** | Automatic (named parameters) | Manual sequential reading |
|
|
245
|
+
| **Decode function** | `abi.decode(data, (T1, T2))` | `calldata.readT1(); calldata.readT2();` |
|
|
246
|
+
| **Type safety** | Compile-time | Runtime |
|
|
247
|
+
| **Read order** | Any order (named) | Must match encoding order |
|
|
248
|
+
| **Error on insufficient data** | Reverts | Reverts |
|
|
249
|
+
| **Dynamic types** | ABI-encoded with offset | Length-prefixed inline |
|
|
250
|
+
| **Memory location** | `calldata`, `memory` keywords | Always sequential buffer |
|
|
251
|
+
|
|
252
|
+
### Type-by-Type Decoding Comparison
|
|
253
|
+
|
|
254
|
+
| Solidity Decoding | OPNet Decoding |
|
|
255
|
+
|-------------------|----------------|
|
|
256
|
+
| `abi.decode(data, (uint256))` | `calldata.readU256()` |
|
|
257
|
+
| `abi.decode(data, (uint128))` | `calldata.readU128()` |
|
|
258
|
+
| `abi.decode(data, (uint64))` | `calldata.readU64()` |
|
|
259
|
+
| `abi.decode(data, (uint32))` | `calldata.readU32()` |
|
|
260
|
+
| `abi.decode(data, (uint16))` | `calldata.readU16()` |
|
|
261
|
+
| `abi.decode(data, (uint8))` | `calldata.readU8()` |
|
|
262
|
+
| `abi.decode(data, (int256))` | N/A (use u256 with sign handling) |
|
|
263
|
+
| `abi.decode(data, (bool))` | `calldata.readBoolean()` |
|
|
264
|
+
| `abi.decode(data, (address))` | `calldata.readAddress()` |
|
|
265
|
+
| `abi.decode(data, (bytes32))` | `calldata.readBytes()` (length-prefixed) |
|
|
266
|
+
| `abi.decode(data, (string))` | `calldata.readString()` |
|
|
267
|
+
| `abi.decode(data, (bytes))` | `calldata.readBytes()` |
|
|
268
|
+
| `abi.decode(data, (address[]))` | `calldata.readAddressArray()` |
|
|
269
|
+
| `abi.decode(data, (uint256[]))` | `calldata.readU256Array()` |
|
|
270
|
+
|
|
271
|
+
### Side-by-Side Code Examples
|
|
272
|
+
|
|
273
|
+
#### Simple Function Parameters
|
|
274
|
+
|
|
275
|
+
```solidity
|
|
276
|
+
// Solidity - Parameters decoded automatically
|
|
277
|
+
function transfer(address to, uint256 amount) public returns (bool) {
|
|
278
|
+
// 'to' and 'amount' are immediately available
|
|
279
|
+
_transfer(msg.sender, to, amount);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// OPNet - Parameters read sequentially
|
|
286
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
287
|
+
// Must read in exact order they were encoded
|
|
288
|
+
const to: Address = calldata.readAddress();
|
|
289
|
+
const amount: u256 = calldata.readU256();
|
|
290
|
+
|
|
291
|
+
this._transfer(Blockchain.tx.sender, to, amount);
|
|
292
|
+
|
|
293
|
+
const writer = new BytesWriter(1);
|
|
294
|
+
writer.writeBoolean(true);
|
|
295
|
+
return writer;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### Multiple Parameters
|
|
300
|
+
|
|
301
|
+
```solidity
|
|
302
|
+
// Solidity
|
|
303
|
+
function transferFrom(
|
|
304
|
+
address from,
|
|
305
|
+
address to,
|
|
306
|
+
uint256 amount
|
|
307
|
+
) public returns (bool) {
|
|
308
|
+
_spendAllowance(from, msg.sender, amount);
|
|
309
|
+
_transfer(from, to, amount);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// OPNet
|
|
316
|
+
public transferFrom(calldata: Calldata): BytesWriter {
|
|
317
|
+
const from: Address = calldata.readAddress();
|
|
318
|
+
const to: Address = calldata.readAddress();
|
|
319
|
+
const amount: u256 = calldata.readU256();
|
|
320
|
+
|
|
321
|
+
this._spendAllowance(from, Blockchain.tx.sender, amount);
|
|
322
|
+
this._transfer(from, to, amount);
|
|
323
|
+
|
|
324
|
+
const writer = new BytesWriter(1);
|
|
325
|
+
writer.writeBoolean(true);
|
|
326
|
+
return writer;
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### Decoding Complex Types
|
|
331
|
+
|
|
332
|
+
```solidity
|
|
333
|
+
// Solidity - Decoding from raw bytes
|
|
334
|
+
function decodeTransfer(bytes calldata data) public pure returns (address, uint256) {
|
|
335
|
+
(address to, uint256 amount) = abi.decode(data, (address, uint256));
|
|
336
|
+
return (to, amount);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Or with explicit offset
|
|
340
|
+
function decodeWithOffset(bytes calldata data) public pure {
|
|
341
|
+
address to = abi.decode(data[0:32], (address));
|
|
342
|
+
uint256 amount = abi.decode(data[32:64], (uint256));
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
// OPNet - Sequential reading handles offset automatically
|
|
348
|
+
public decodeTransfer(calldata: Calldata): BytesWriter {
|
|
349
|
+
const to: Address = calldata.readAddress(); // Reads bytes 0-31
|
|
350
|
+
const amount: u256 = calldata.readU256(); // Reads bytes 32-63
|
|
351
|
+
|
|
352
|
+
const writer = new BytesWriter(64);
|
|
353
|
+
writer.writeAddress(to);
|
|
354
|
+
writer.writeU256(amount);
|
|
355
|
+
return writer;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### String and Bytes Handling
|
|
360
|
+
|
|
361
|
+
```solidity
|
|
362
|
+
// Solidity
|
|
363
|
+
function setName(string calldata name) public {
|
|
364
|
+
require(bytes(name).length > 0, "Empty name");
|
|
365
|
+
_name = name;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function processData(bytes calldata data) public {
|
|
369
|
+
require(data.length >= 4, "Too short");
|
|
370
|
+
// Process data...
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// OPNet
|
|
376
|
+
public setName(calldata: Calldata): BytesWriter {
|
|
377
|
+
const name: string = calldata.readString(); // Length-prefixed
|
|
378
|
+
if (name.length == 0) {
|
|
379
|
+
throw new Revert('Empty name');
|
|
380
|
+
}
|
|
381
|
+
this._name.value = name;
|
|
382
|
+
return new BytesWriter(0);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
public processData(calldata: Calldata): BytesWriter {
|
|
386
|
+
const data: Uint8Array = calldata.readBytes(); // Length-prefixed
|
|
387
|
+
if (data.length < 4) {
|
|
388
|
+
throw new Revert('Too short');
|
|
389
|
+
}
|
|
390
|
+
// Process data...
|
|
391
|
+
return new BytesWriter(0);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### Array Parameters
|
|
396
|
+
|
|
397
|
+
```solidity
|
|
398
|
+
// Solidity
|
|
399
|
+
function batchTransfer(
|
|
400
|
+
address[] calldata recipients,
|
|
401
|
+
uint256[] calldata amounts
|
|
402
|
+
) public {
|
|
403
|
+
require(recipients.length == amounts.length, "Length mismatch");
|
|
404
|
+
for (uint i = 0; i < recipients.length; i++) {
|
|
405
|
+
_transfer(msg.sender, recipients[i], amounts[i]);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
// OPNet
|
|
412
|
+
public batchTransfer(calldata: Calldata): BytesWriter {
|
|
413
|
+
const recipients: Address[] = calldata.readAddressArray();
|
|
414
|
+
const amounts: u256[] = calldata.readU256Array();
|
|
415
|
+
|
|
416
|
+
if (recipients.length != amounts.length) {
|
|
417
|
+
throw new Revert('Length mismatch');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
for (let i = 0; i < recipients.length; i++) {
|
|
421
|
+
this._transfer(Blockchain.tx.sender, recipients[i], amounts[i]);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return new BytesWriter(0);
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
#### Optional/Variable Parameters
|
|
429
|
+
|
|
430
|
+
```solidity
|
|
431
|
+
// Solidity - Using bytes for optional data
|
|
432
|
+
function safeTransferFrom(
|
|
433
|
+
address from,
|
|
434
|
+
address to,
|
|
435
|
+
uint256 tokenId,
|
|
436
|
+
bytes calldata data
|
|
437
|
+
) public {
|
|
438
|
+
_transfer(from, to, tokenId);
|
|
439
|
+
if (data.length > 0) {
|
|
440
|
+
// Call onERC721Received
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// OPNet - Check for remaining data
|
|
447
|
+
public safeTransferFrom(calldata: Calldata): BytesWriter {
|
|
448
|
+
const from: Address = calldata.readAddress();
|
|
449
|
+
const to: Address = calldata.readAddress();
|
|
450
|
+
const tokenId: u256 = calldata.readU256();
|
|
451
|
+
|
|
452
|
+
// Check if optional data is present by comparing offset to total length
|
|
453
|
+
let data: Uint8Array = new Uint8Array(0);
|
|
454
|
+
if (calldata.getOffset() < calldata.byteLength) {
|
|
455
|
+
data = calldata.readBytesWithLength(); // Read length-prefixed bytes
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this._transfer(from, to, tokenId);
|
|
459
|
+
if (data.length > 0) {
|
|
460
|
+
// Handle callback
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return new BytesWriter(0);
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Encoding Format Differences
|
|
468
|
+
|
|
469
|
+
| Aspect | Solidity ABI | OPNet |
|
|
470
|
+
|--------|--------------|-------|
|
|
471
|
+
| **Byte order** | Big-endian | Big-endian (default) |
|
|
472
|
+
| **Address padding** | Left-padded to 32 bytes | 32 bytes (native size) |
|
|
473
|
+
| **Dynamic offset** | Pointer + data section | Inline length prefix |
|
|
474
|
+
| **String encoding** | Offset + length + data | 4-byte u32 length + UTF-8 |
|
|
475
|
+
| **Array encoding** | Offset + length + elements | 2-byte u16 length + elements |
|
|
476
|
+
| **Boolean** | 32 bytes (padded) | 1 byte |
|
|
477
|
+
| **uint8-uint248** | 32 bytes (padded) | Native size |
|
|
478
|
+
|
|
479
|
+
### Encoding Size Comparison
|
|
480
|
+
|
|
481
|
+
| Type | Solidity ABI Size | OPNet Size |
|
|
482
|
+
|------|-------------------|------------|
|
|
483
|
+
| `bool` | 32 bytes | 1 byte |
|
|
484
|
+
| `uint8` | 32 bytes | 1 byte |
|
|
485
|
+
| `uint16` | 32 bytes | 2 bytes |
|
|
486
|
+
| `uint32` | 32 bytes | 4 bytes |
|
|
487
|
+
| `uint64` | 32 bytes | 8 bytes |
|
|
488
|
+
| `uint128` | 32 bytes | 16 bytes |
|
|
489
|
+
| `uint256` | 32 bytes | 32 bytes |
|
|
490
|
+
| `address` | 32 bytes | 32 bytes |
|
|
491
|
+
| `string "Hello"` | 96 bytes (offset+len+data) | 9 bytes (len+data) |
|
|
492
|
+
|
|
493
|
+
### Key Differences Summary
|
|
494
|
+
|
|
495
|
+
| Solidity | OPNet |
|
|
496
|
+
|----------|-------|
|
|
497
|
+
| Named parameters in function signature | Single `Calldata` parameter |
|
|
498
|
+
| Automatic ABI decoding | Manual `read*()` methods |
|
|
499
|
+
| Can access parameters in any order | Must read in sequential order |
|
|
500
|
+
| Type info in function signature | Type determined by read method |
|
|
501
|
+
| `calldata` keyword optimization | All calldata is read-only by default |
|
|
502
|
+
| `msg.data` for raw bytes | Calldata object wraps the buffer |
|
|
503
|
+
|
|
504
|
+
## Common Patterns
|
|
505
|
+
|
|
506
|
+
### Method Call Flow
|
|
507
|
+
|
|
508
|
+
```mermaid
|
|
509
|
+
sequenceDiagram
|
|
510
|
+
participant U as 👤 User/Client
|
|
511
|
+
participant BC as Blockchain
|
|
512
|
+
participant C as Contract Method
|
|
513
|
+
participant CD as Calldata
|
|
514
|
+
participant W as BytesWriter
|
|
515
|
+
|
|
516
|
+
U->>BC: Transaction with encoded params
|
|
517
|
+
Note over BC: Selector + Parameters
|
|
518
|
+
BC->>C: Invoke method(calldata)
|
|
519
|
+
C->>CD: readAddress()
|
|
520
|
+
CD-->>C: Address value
|
|
521
|
+
C->>CD: readU256()
|
|
522
|
+
CD-->>C: u256 value
|
|
523
|
+
|
|
524
|
+
C->>C: Execute business logic
|
|
525
|
+
Note over C: Process transfer,<br/>update balances, etc.
|
|
526
|
+
|
|
527
|
+
C->>W: new BytesWriter(size)
|
|
528
|
+
C->>W: Write return values
|
|
529
|
+
W-->>C: Encoded response
|
|
530
|
+
C-->>BC: Return BytesWriter
|
|
531
|
+
BC-->>U: Transaction result
|
|
532
|
+
|
|
533
|
+
Note over CD: All parameters read<br/>sequentially in order
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Single Value Methods
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
// balanceOf(address)
|
|
540
|
+
public balanceOf(calldata: Calldata): BytesWriter {
|
|
541
|
+
const account = calldata.readAddress();
|
|
542
|
+
const balance = this.balances.get(account);
|
|
543
|
+
|
|
544
|
+
const writer = new BytesWriter(32);
|
|
545
|
+
writer.writeU256(balance);
|
|
546
|
+
return writer;
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Multi-Parameter Methods
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// transferFrom(from, to, amount)
|
|
554
|
+
public transferFrom(calldata: Calldata): BytesWriter {
|
|
555
|
+
const from = calldata.readAddress();
|
|
556
|
+
const to = calldata.readAddress();
|
|
557
|
+
const amount = calldata.readU256();
|
|
558
|
+
|
|
559
|
+
this._transfer(from, to, amount);
|
|
560
|
+
return new BytesWriter(0);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Optional Parameters
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
// Method with optional data field
|
|
568
|
+
public safeTransfer(calldata: Calldata): BytesWriter {
|
|
569
|
+
const to = calldata.readAddress();
|
|
570
|
+
const tokenId = calldata.readU256();
|
|
571
|
+
|
|
572
|
+
// Check if there's more data by comparing offset to total length
|
|
573
|
+
let data: Uint8Array = new Uint8Array(0);
|
|
574
|
+
if (calldata.getOffset() < calldata.byteLength) {
|
|
575
|
+
data = calldata.readBytesWithLength(); // Read length-prefixed bytes
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
this._safeTransfer(Blockchain.tx.sender, to, tokenId, data);
|
|
579
|
+
return new BytesWriter(0);
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Batch Operations
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
// Airdrop to multiple addresses
|
|
587
|
+
public airdrop(calldata: Calldata): BytesWriter {
|
|
588
|
+
const recipients = calldata.readAddressMapU256();
|
|
589
|
+
const addresses = recipients.keys();
|
|
590
|
+
|
|
591
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
592
|
+
const addr = addresses[i];
|
|
593
|
+
const amount = recipients.get(addr);
|
|
594
|
+
this._mint(addr, amount);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return new BytesWriter(0);
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
## Error Handling
|
|
602
|
+
|
|
603
|
+
### Insufficient Data
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
// If calldata doesn't have enough bytes, read will fail
|
|
607
|
+
public myMethod(calldata: Calldata): BytesWriter {
|
|
608
|
+
// If only 32 bytes provided...
|
|
609
|
+
const addr = calldata.readAddress(); // OK (32 bytes)
|
|
610
|
+
const amount = calldata.readU256(); // FAILS! No more data
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Validation
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
public myMethod(calldata: Calldata): BytesWriter {
|
|
618
|
+
const to = calldata.readAddress();
|
|
619
|
+
const amount = calldata.readU256();
|
|
620
|
+
|
|
621
|
+
// Validate after reading
|
|
622
|
+
if (to.equals(Address.zero())) {
|
|
623
|
+
throw new Revert('Invalid recipient');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (amount.isZero()) {
|
|
627
|
+
throw new Revert('Amount is zero');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ... proceed
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Deployment Calldata
|
|
635
|
+
|
|
636
|
+
The `onDeployment` method receives initialization parameters:
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
public override onDeployment(calldata: Calldata): void {
|
|
640
|
+
// Read deployment parameters
|
|
641
|
+
const name = calldata.readString();
|
|
642
|
+
const symbol = calldata.readString();
|
|
643
|
+
const maxSupply = calldata.readU256();
|
|
644
|
+
const decimals = calldata.readU8();
|
|
645
|
+
|
|
646
|
+
// Initialize contract
|
|
647
|
+
this._name.value = name;
|
|
648
|
+
this._symbol.value = symbol;
|
|
649
|
+
this._maxSupply.value = maxSupply;
|
|
650
|
+
this._decimals.value = decimals;
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Deployment Parameter Flow
|
|
655
|
+
|
|
656
|
+
```mermaid
|
|
657
|
+
---
|
|
658
|
+
config:
|
|
659
|
+
theme: dark
|
|
660
|
+
---
|
|
661
|
+
flowchart LR
|
|
662
|
+
A["Deploy Transaction"] --> B["Calldata Buffer"]
|
|
663
|
+
B --> C["name: string"]
|
|
664
|
+
C --> D["symbol: string"]
|
|
665
|
+
D --> E["maxSupply: u256"]
|
|
666
|
+
E --> F["decimals: u8"]
|
|
667
|
+
F --> G["onDeployment Method"]
|
|
668
|
+
G --> H["Initialize State"]
|
|
669
|
+
H --> I["Contract Ready"]
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
## Best Practices
|
|
673
|
+
|
|
674
|
+
### 1. Document Parameter Order
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
/**
|
|
678
|
+
* Transfer tokens to recipient.
|
|
679
|
+
* @param calldata Contains:
|
|
680
|
+
* - to: Address (32 bytes) - Recipient address
|
|
681
|
+
* - amount: u256 (32 bytes) - Amount to transfer
|
|
682
|
+
*/
|
|
683
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
684
|
+
const to = calldata.readAddress();
|
|
685
|
+
const amount = calldata.readU256();
|
|
686
|
+
// ...
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### 2. Validate Early
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
694
|
+
// Read all parameters first
|
|
695
|
+
const to = calldata.readAddress();
|
|
696
|
+
const amount = calldata.readU256();
|
|
697
|
+
|
|
698
|
+
// Then validate
|
|
699
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
700
|
+
|
|
701
|
+
if (to.equals(Address.zero())) {
|
|
702
|
+
throw new Revert('Cannot mint to zero address');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (amount.isZero()) {
|
|
706
|
+
throw new Revert('Amount must be positive');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Then execute
|
|
710
|
+
this._mint(to, amount);
|
|
711
|
+
return new BytesWriter(0);
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### 3. Handle Arrays Carefully
|
|
716
|
+
|
|
717
|
+
```typescript
|
|
718
|
+
public batchTransfer(calldata: Calldata): BytesWriter {
|
|
719
|
+
const recipients = calldata.readAddressArray();
|
|
720
|
+
const amounts = calldata.readU256Array();
|
|
721
|
+
|
|
722
|
+
// Validate array lengths match
|
|
723
|
+
if (recipients.length !== amounts.length) {
|
|
724
|
+
throw new Revert('Array length mismatch');
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Limit array size to prevent DoS attacks
|
|
728
|
+
if (recipients.length > 100) {
|
|
729
|
+
throw new Revert('Too many recipients');
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
for (let i = 0; i < recipients.length; i++) {
|
|
733
|
+
this._transfer(Blockchain.tx.sender, recipients[i], amounts[i]);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return new BytesWriter(0);
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
**Navigation:**
|
|
743
|
+
- Previous: [SafeMath](./safe-math.md)
|
|
744
|
+
- Next: [BytesWriter/Reader](./bytes-writer-reader.md)
|