@btc-vision/btc-runtime 1.10.8 → 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 +52 -27
- package/runtime/memory/MapOfMap.ts +1 -0
- package/LICENSE.md +0 -21
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
# OP_NET Base Contract
|
|
2
|
+
|
|
3
|
+
`OP_NET` is the base class for all OPNet smart contracts. It implements the `IBTC` interface and provides the foundational structure for contract lifecycle, method dispatching, event emission, and access control.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { OP_NET, Calldata, BytesWriter, ABIDataTypes } from '@btc-vision/btc-runtime/runtime';
|
|
9
|
+
|
|
10
|
+
@final
|
|
11
|
+
export class MyContract extends OP_NET {
|
|
12
|
+
public constructor() {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public override onDeployment(calldata: Calldata): void {
|
|
17
|
+
// One-time initialization
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@method({ name: 'param', type: ABIDataTypes.UINT256 })
|
|
21
|
+
@returns({ name: 'result', type: ABIDataTypes.UINT256 })
|
|
22
|
+
public myMethod(calldata: Calldata): BytesWriter {
|
|
23
|
+
// Method implementation - routing is AUTOMATIC via @method decorator
|
|
24
|
+
return new BytesWriter(0);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Note:** Method routing is handled AUTOMATICALLY by the runtime via `@method` decorators. You do NOT need to override the `execute` method - the decorator system handles selector generation and call routing.
|
|
30
|
+
|
|
31
|
+
**Solidity Comparison:**
|
|
32
|
+
|
|
33
|
+
```solidity
|
|
34
|
+
// Solidity: Automatic method routing via ABI
|
|
35
|
+
contract MyContract {
|
|
36
|
+
constructor() {
|
|
37
|
+
// Runs once at deployment
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function myMethod() public returns (bytes memory) {
|
|
41
|
+
// Method implementation
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// OPNet: AUTOMATIC method routing via @method decorators
|
|
46
|
+
// - Constructor runs on EVERY call
|
|
47
|
+
// - Routing is automatic via decorator system
|
|
48
|
+
// - One-time init in onDeployment()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Contract Lifecycle
|
|
52
|
+
|
|
53
|
+
### Inheritance Hierarchy
|
|
54
|
+
|
|
55
|
+
OPNet contracts follow a clear inheritance pattern:
|
|
56
|
+
|
|
57
|
+
```mermaid
|
|
58
|
+
classDiagram
|
|
59
|
+
class OP_NET {
|
|
60
|
+
Base Contract
|
|
61
|
+
implements IBTC
|
|
62
|
+
+address Address (getter)
|
|
63
|
+
+contractDeployer Address (getter)
|
|
64
|
+
+onDeployment(_calldata: Calldata) void
|
|
65
|
+
+onExecutionStarted(_selector: Selector, _calldata: Calldata) void
|
|
66
|
+
+onExecutionCompleted(_selector: Selector, _calldata: Calldata) void
|
|
67
|
+
+execute(method: Selector, _calldata: Calldata) BytesWriter
|
|
68
|
+
#emitEvent(event: NetEvent) void
|
|
69
|
+
#onlyDeployer(caller: Address) void
|
|
70
|
+
#isSelf(address: Address) boolean
|
|
71
|
+
#_buildDomainSeparator() Uint8Array
|
|
72
|
+
Note: @method decorator handles routing
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class MyContract {
|
|
76
|
+
Custom Contract
|
|
77
|
+
-balancesPointer: u16
|
|
78
|
+
-balances: AddressMemoryMap
|
|
79
|
+
+constructor()
|
|
80
|
+
+onDeployment(calldata: Calldata) void
|
|
81
|
+
+myMethod(calldata: Calldata) BytesWriter
|
|
82
|
+
Note: @method decorator handles routing
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class ReentrancyGuard {
|
|
86
|
+
Reentrancy Protection
|
|
87
|
+
extends OP_NET
|
|
88
|
+
#_locked: StoredBoolean
|
|
89
|
+
#_reentrancyDepth: StoredU256
|
|
90
|
+
+nonReentrantBefore() void
|
|
91
|
+
+nonReentrantAfter() void
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class OP20 {
|
|
95
|
+
Fungible Token Standard
|
|
96
|
+
extends ReentrancyGuard
|
|
97
|
+
-_totalSupply: StoredU256
|
|
98
|
+
-balanceOfMap: AddressMemoryMap
|
|
99
|
+
+transfer(calldata: Calldata) BytesWriter
|
|
100
|
+
+approve(calldata: Calldata) BytesWriter
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class OP721 {
|
|
104
|
+
NFT Standard
|
|
105
|
+
extends ReentrancyGuard
|
|
106
|
+
-_owners: AddressMemoryMap
|
|
107
|
+
-_balances: AddressMemoryMap
|
|
108
|
+
+transferFrom(calldata: Calldata) BytesWriter
|
|
109
|
+
+mint(to: Address, tokenId: u256) void
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
OP_NET <|-- MyContract : extends
|
|
113
|
+
OP_NET <|-- ReentrancyGuard : extends
|
|
114
|
+
ReentrancyGuard <|-- OP20 : extends
|
|
115
|
+
ReentrancyGuard <|-- OP721 : extends
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Deployment and Execution Flow
|
|
119
|
+
|
|
120
|
+
The following diagram shows how contracts are deployed and executed on OPNet:
|
|
121
|
+
|
|
122
|
+
```mermaid
|
|
123
|
+
---
|
|
124
|
+
config:
|
|
125
|
+
theme: dark
|
|
126
|
+
---
|
|
127
|
+
sequenceDiagram
|
|
128
|
+
participant User as User
|
|
129
|
+
participant Bitcoin as Bitcoin L1
|
|
130
|
+
participant WASM as WASM Runtime
|
|
131
|
+
participant Contract as Contract
|
|
132
|
+
participant Storage as Storage
|
|
133
|
+
|
|
134
|
+
Note over User,Storage: Deployment Phase (Once)
|
|
135
|
+
User->>Bitcoin: Submit deployment TX
|
|
136
|
+
Bitcoin->>WASM: Create contract
|
|
137
|
+
WASM->>Contract: constructor()
|
|
138
|
+
Contract->>Contract: Initialize storage pointers
|
|
139
|
+
Contract->>Contract: Create storage map instances
|
|
140
|
+
WASM->>Contract: onDeployment(calldata)
|
|
141
|
+
Contract->>Contract: Read deployment calldata
|
|
142
|
+
Contract->>Storage: Set initial state
|
|
143
|
+
Contract->>WASM: Emit deployment events
|
|
144
|
+
Note over Contract: Contract Ready
|
|
145
|
+
|
|
146
|
+
Note over User,Storage: Execution Phase (Every Transaction)
|
|
147
|
+
User->>Bitcoin: Submit transaction
|
|
148
|
+
Bitcoin->>WASM: Route to contract
|
|
149
|
+
WASM->>Contract: constructor() runs AGAIN
|
|
150
|
+
Contract->>Contract: Re-initialize storage maps
|
|
151
|
+
WASM->>Contract: onExecutionStarted(selector, calldata)
|
|
152
|
+
Contract->>Contract: Read method selector from TX
|
|
153
|
+
WASM->>Contract: execute(method, calldata)
|
|
154
|
+
|
|
155
|
+
alt Selector matches
|
|
156
|
+
Contract->>Contract: Call method handler
|
|
157
|
+
Contract->>Contract: Read calldata parameters
|
|
158
|
+
Contract->>Contract: Validate inputs
|
|
159
|
+
alt Valid inputs
|
|
160
|
+
Contract->>Storage: Read current state
|
|
161
|
+
Contract->>Contract: Execute business logic
|
|
162
|
+
Contract->>Storage: Write updated state
|
|
163
|
+
Contract->>WASM: emitEvent()
|
|
164
|
+
else Invalid inputs
|
|
165
|
+
Contract->>WASM: Revert transaction
|
|
166
|
+
end
|
|
167
|
+
else No match
|
|
168
|
+
Contract->>Contract: super.execute(parent)
|
|
169
|
+
alt Parent has method
|
|
170
|
+
Contract->>Storage: Execute parent method
|
|
171
|
+
Contract->>WASM: emitEvent()
|
|
172
|
+
else No handler
|
|
173
|
+
Contract->>WASM: Revert: Unknown selector
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
WASM->>Contract: onExecutionCompleted(selector, calldata)
|
|
178
|
+
Contract->>WASM: Return BytesWriter result
|
|
179
|
+
WASM->>Bitcoin: Commit state changes
|
|
180
|
+
Bitcoin->>User: Transaction complete
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 1. Construction
|
|
184
|
+
|
|
185
|
+
The constructor runs on **every** contract interaction:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
public constructor() {
|
|
189
|
+
super(); // Always call parent constructor
|
|
190
|
+
|
|
191
|
+
// Initialize storage maps (these run every time)
|
|
192
|
+
this.balances = new AddressMemoryMap(this.balancesPointer);
|
|
193
|
+
|
|
194
|
+
// DON'T do one-time initialization here!
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Key Difference from Solidity:**
|
|
199
|
+
|
|
200
|
+
| Solidity | OPNet |
|
|
201
|
+
|----------|-------|
|
|
202
|
+
| Constructor runs once at deployment | Constructor runs every call |
|
|
203
|
+
| Initialize state in constructor | Initialize state in `onDeployment` |
|
|
204
|
+
|
|
205
|
+
### 2. Deployment (onDeployment)
|
|
206
|
+
|
|
207
|
+
Runs exactly **once** when the contract is first deployed:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
public override onDeployment(calldata: Calldata): void {
|
|
211
|
+
// Read deployment parameters
|
|
212
|
+
const initialSupply = calldata.readU256();
|
|
213
|
+
const tokenName = calldata.readString();
|
|
214
|
+
|
|
215
|
+
// Set initial state
|
|
216
|
+
this._totalSupply.value = initialSupply;
|
|
217
|
+
this._name.value = tokenName;
|
|
218
|
+
|
|
219
|
+
// Mint initial tokens
|
|
220
|
+
this._mint(Blockchain.tx.origin, initialSupply);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Solidity Comparison:**
|
|
225
|
+
|
|
226
|
+
```solidity
|
|
227
|
+
// Solidity: One-time init in constructor
|
|
228
|
+
constructor(uint256 initialSupply, string memory tokenName) {
|
|
229
|
+
_totalSupply = initialSupply;
|
|
230
|
+
_name = tokenName;
|
|
231
|
+
_mint(msg.sender, initialSupply);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// OPNet: One-time init in onDeployment()
|
|
235
|
+
// Constructor runs every call, onDeployment runs once
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 3. Method Execution
|
|
239
|
+
|
|
240
|
+
Methods are automatically routed via `@method` decorators:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
@method(
|
|
244
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
245
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
246
|
+
)
|
|
247
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
248
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
249
|
+
const to = calldata.readAddress();
|
|
250
|
+
const amount = calldata.readU256();
|
|
251
|
+
// ... implementation
|
|
252
|
+
return new BytesWriter(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@method({ name: 'spender', type: ABIDataTypes.ADDRESS }, { name: 'amount', type: ABIDataTypes.UINT256 })
|
|
256
|
+
public approve(calldata: Calldata): BytesWriter {
|
|
257
|
+
// ... implementation
|
|
258
|
+
return new BytesWriter(0);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@method({ name: 'account', type: ABIDataTypes.ADDRESS })
|
|
262
|
+
@returns({ name: 'balance', type: ABIDataTypes.UINT256 })
|
|
263
|
+
public balanceOf(calldata: Calldata): BytesWriter {
|
|
264
|
+
const account = calldata.readAddress();
|
|
265
|
+
// ... implementation
|
|
266
|
+
const writer = new BytesWriter(32);
|
|
267
|
+
writer.writeU256(balance);
|
|
268
|
+
return writer;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Note:** The runtime automatically generates selectors and routes calls based on `@method` decorators. You do NOT need to override the `execute` method.
|
|
273
|
+
|
|
274
|
+
### Transaction Sequence
|
|
275
|
+
|
|
276
|
+
The following sequence diagram shows the complete flow of a transaction through the system:
|
|
277
|
+
|
|
278
|
+
```mermaid
|
|
279
|
+
sequenceDiagram
|
|
280
|
+
participant User as 👤 User Wallet
|
|
281
|
+
participant Blockchain as Bitcoin L1
|
|
282
|
+
participant TxPool as Transaction Pool
|
|
283
|
+
participant VM as WASM Runtime
|
|
284
|
+
participant Contract as OP_NET Contract
|
|
285
|
+
participant Storage as Storage Pointers
|
|
286
|
+
participant EventLog as Event Log
|
|
287
|
+
|
|
288
|
+
User->>TxPool: Submit signed transaction
|
|
289
|
+
Note over User,TxPool: Contains: contract address,<br/>method selector, calldata
|
|
290
|
+
|
|
291
|
+
TxPool->>Blockchain: Transaction confirmed
|
|
292
|
+
Blockchain->>VM: Route to contract address
|
|
293
|
+
|
|
294
|
+
VM->>Contract: Instantiate contract instance
|
|
295
|
+
activate Contract
|
|
296
|
+
|
|
297
|
+
Contract->>Contract: constructor()
|
|
298
|
+
Note over Contract: Runs EVERY call<br/>Initialize storage maps
|
|
299
|
+
|
|
300
|
+
Contract->>Storage: Allocate storage pointers
|
|
301
|
+
Storage-->>Contract: Pointer addresses
|
|
302
|
+
|
|
303
|
+
VM->>Contract: onExecutionStarted(selector, calldata)
|
|
304
|
+
Note over Contract: Pre-execution hook<br/>Can add logging/validation
|
|
305
|
+
|
|
306
|
+
VM->>Contract: execute(selector, calldata)
|
|
307
|
+
|
|
308
|
+
Contract->>Contract: switch(selector)
|
|
309
|
+
Note over Contract: Method routing logic
|
|
310
|
+
|
|
311
|
+
alt Known Method Selector
|
|
312
|
+
Contract->>Contract: methodHandler(calldata)
|
|
313
|
+
|
|
314
|
+
Contract->>Contract: calldata.readAddress()
|
|
315
|
+
Contract->>Contract: calldata.readU256()
|
|
316
|
+
Note over Contract: Parse parameters
|
|
317
|
+
|
|
318
|
+
Contract->>Storage: Read current state
|
|
319
|
+
Storage-->>Contract: Current values
|
|
320
|
+
|
|
321
|
+
Contract->>Contract: Business logic
|
|
322
|
+
Note over Contract: SafeMath operations,<br/>validations, state changes
|
|
323
|
+
|
|
324
|
+
Contract->>Storage: Write updated state
|
|
325
|
+
Note over Storage: Persistent storage<br/>committed on success
|
|
326
|
+
|
|
327
|
+
Contract->>EventLog: emitEvent(TransferEvent)
|
|
328
|
+
Note over EventLog: Events for indexing<br/>off-chain systems
|
|
329
|
+
|
|
330
|
+
else Unknown Method
|
|
331
|
+
Contract->>Contract: super.execute(selector, calldata)
|
|
332
|
+
|
|
333
|
+
alt Parent Has Method
|
|
334
|
+
Note over Contract: OP_NET parent<br/>or OP20/OP721 parent
|
|
335
|
+
Contract->>Storage: Parent method logic
|
|
336
|
+
Contract->>EventLog: Parent method events
|
|
337
|
+
else No Handler
|
|
338
|
+
Contract->>VM: throw Revert Unknown method
|
|
339
|
+
VM->>User: Transaction reverted
|
|
340
|
+
Note over User: No state changes,<br/>fees still consumed
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
Contract->>Contract: onExecutionCompleted(selector, calldata)
|
|
345
|
+
Note over Contract: Post-execution hook<br/>Cleanup, final checks
|
|
346
|
+
|
|
347
|
+
Contract->>VM: Return BytesWriter
|
|
348
|
+
deactivate Contract
|
|
349
|
+
|
|
350
|
+
VM->>Blockchain: Commit state changes
|
|
351
|
+
Blockchain->>User: Transaction receipt
|
|
352
|
+
Note over User: Success with events<br/>or revert with error
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Method Selectors
|
|
356
|
+
|
|
357
|
+
Methods are identified by selectors (4-byte identifiers). The `@method` decorator automatically generates and registers selectors:
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
// Selectors are generated AUTOMATICALLY from @method decorators
|
|
361
|
+
@method(
|
|
362
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
363
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
364
|
+
)
|
|
365
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
366
|
+
// Runtime automatically routes calls to this method
|
|
367
|
+
return new BytesWriter(0);
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Solidity Comparison
|
|
372
|
+
|
|
373
|
+
```solidity
|
|
374
|
+
// Solidity: Automatic selector generation
|
|
375
|
+
function transfer(address to, uint256 amount) public { }
|
|
376
|
+
// Selector: keccak256("transfer(address,uint256)")[:4]
|
|
377
|
+
|
|
378
|
+
// OPNet: ALSO automatic via @method decorator
|
|
379
|
+
// @method({ name: 'to', type: ABIDataTypes.ADDRESS }, ...)
|
|
380
|
+
// public transfer(calldata: Calldata): BytesWriter { }
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Note:** Both Solidity and OPNet handle selector generation automatically. In OPNet, use `@method` decorators and the runtime handles routing.
|
|
384
|
+
|
|
385
|
+
### Built-in Methods
|
|
386
|
+
|
|
387
|
+
The base `OP_NET` class provides a built-in `deployer()` method that returns the contract deployer address:
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
// Built-in method handled by OP_NET.execute()
|
|
391
|
+
// Selector: encodeSelector('deployer()')
|
|
392
|
+
// Returns: Address (the contract deployer)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
This method is automatically available on all contracts that extend `OP_NET`. When called, it returns the `contractDeployer` address.
|
|
396
|
+
|
|
397
|
+
## Access Control
|
|
398
|
+
|
|
399
|
+
### onlyDeployer
|
|
400
|
+
|
|
401
|
+
Restrict function access to the contract deployer:
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
@method({ name: 'parameter', type: ABIDataTypes.UINT256 })
|
|
405
|
+
public adminFunction(calldata: Calldata): BytesWriter {
|
|
406
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
407
|
+
|
|
408
|
+
// Only deployer reaches here
|
|
409
|
+
return new BytesWriter(0);
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Solidity Comparison:**
|
|
414
|
+
|
|
415
|
+
```solidity
|
|
416
|
+
// Solidity: Using OpenZeppelin Ownable
|
|
417
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
418
|
+
|
|
419
|
+
contract MyContract is Ownable {
|
|
420
|
+
function adminFunction(uint256 parameter) public onlyOwner {
|
|
421
|
+
// Only owner reaches here
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// OPNet: Built-in onlyDeployer check
|
|
426
|
+
// this.onlyDeployer(Blockchain.tx.sender);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Custom Access Control
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
private adminPointer: u16 = Blockchain.nextPointer;
|
|
433
|
+
private admin: StoredAddress = new StoredAddress(this.adminPointer, Address.zero());
|
|
434
|
+
|
|
435
|
+
private onlyAdmin(): void {
|
|
436
|
+
if (!Blockchain.tx.sender.equals(this.admin.value)) {
|
|
437
|
+
throw new Revert('Caller is not admin');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
@method({ name: 'value', type: ABIDataTypes.UINT256 })
|
|
442
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
443
|
+
public setParameter(calldata: Calldata): BytesWriter {
|
|
444
|
+
this.onlyAdmin();
|
|
445
|
+
// ...
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Solidity Comparison:**
|
|
450
|
+
|
|
451
|
+
```solidity
|
|
452
|
+
// Solidity: Custom access control
|
|
453
|
+
address private admin;
|
|
454
|
+
|
|
455
|
+
modifier onlyAdmin() {
|
|
456
|
+
require(msg.sender == admin, "Caller is not admin");
|
|
457
|
+
_;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function setParameter(uint256 value) public onlyAdmin {
|
|
461
|
+
// ...
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// OPNet: Similar pattern but with explicit method call
|
|
465
|
+
// this.onlyAdmin(); at start of method
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Event Emission
|
|
469
|
+
|
|
470
|
+
Emit events to notify off-chain systems:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import { NetEvent, TransferEvent } from '@btc-vision/btc-runtime/runtime';
|
|
474
|
+
|
|
475
|
+
// Using built-in events
|
|
476
|
+
this.emitEvent(new TransferEvent(from, to, amount));
|
|
477
|
+
|
|
478
|
+
// Using custom events
|
|
479
|
+
this.emitEvent(new MyCustomEvent(data1, data2));
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Solidity Comparison:**
|
|
483
|
+
|
|
484
|
+
```solidity
|
|
485
|
+
// Solidity: Emit keyword
|
|
486
|
+
event Transfer(address indexed from, address indexed to, uint256 value);
|
|
487
|
+
|
|
488
|
+
emit Transfer(from, to, amount);
|
|
489
|
+
|
|
490
|
+
// OPNet: emitEvent method
|
|
491
|
+
this.emitEvent(new TransferEvent(from, to, amount));
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Protected Helper Methods
|
|
495
|
+
|
|
496
|
+
The `OP_NET` base class provides several protected helper methods:
|
|
497
|
+
|
|
498
|
+
### isSelf
|
|
499
|
+
|
|
500
|
+
Checks if a given address is the contract's own address:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
protected isSelf(address: Address): boolean {
|
|
504
|
+
return this.address === address;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Usage example
|
|
508
|
+
if (this.isSelf(targetAddress)) {
|
|
509
|
+
// Handle self-call case
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### _buildDomainSeparator
|
|
514
|
+
|
|
515
|
+
A method stub for building EIP-712 style domain separators. Must be overridden in derived classes that need signature verification:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
protected _buildDomainSeparator(): Uint8Array {
|
|
519
|
+
// Override in derived class to provide domain separator
|
|
520
|
+
throw new Error('Method not implemented.');
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Storage Patterns
|
|
525
|
+
|
|
526
|
+
### Pointer Allocation
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
export class MyContract extends OP_NET {
|
|
530
|
+
// Allocate storage pointers at class level
|
|
531
|
+
private counterPointer: u16 = Blockchain.nextPointer;
|
|
532
|
+
private ownerPointer: u16 = Blockchain.nextPointer;
|
|
533
|
+
private dataPointer: u16 = Blockchain.nextPointer;
|
|
534
|
+
|
|
535
|
+
// Create storage instances
|
|
536
|
+
private counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
|
|
537
|
+
private owner: StoredAddress = new StoredAddress(this.ownerPointer, Address.zero());
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Solidity Comparison:**
|
|
542
|
+
|
|
543
|
+
```solidity
|
|
544
|
+
// Solidity: Automatic storage slot allocation
|
|
545
|
+
contract MyContract {
|
|
546
|
+
uint256 private counter; // slot 0
|
|
547
|
+
address private owner; // slot 1
|
|
548
|
+
bytes private data; // slot 2
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// OPNet: Explicit pointer allocation
|
|
552
|
+
// private counterPointer: u16 = Blockchain.nextPointer;
|
|
553
|
+
// private counter: StoredU256 = new StoredU256(this.counterPointer, EMPTY_POINTER);
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Storage Maps
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
export class MyContract extends OP_NET {
|
|
560
|
+
private balancesPointer: u16 = Blockchain.nextPointer;
|
|
561
|
+
private balances: AddressMemoryMap;
|
|
562
|
+
|
|
563
|
+
public constructor() {
|
|
564
|
+
super();
|
|
565
|
+
// Initialize maps in constructor (runs every time, but that's OK)
|
|
566
|
+
this.balances = new AddressMemoryMap(this.balancesPointer);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**Solidity Comparison:**
|
|
572
|
+
|
|
573
|
+
```solidity
|
|
574
|
+
// Solidity: mapping declaration
|
|
575
|
+
mapping(address => uint256) private balances;
|
|
576
|
+
|
|
577
|
+
// OPNet: AddressMemoryMap with pointer
|
|
578
|
+
// private balancesPointer: u16 = Blockchain.nextPointer;
|
|
579
|
+
// this.balances = new AddressMemoryMap(this.balancesPointer);
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
## Complete Example
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
586
|
+
import {
|
|
587
|
+
OP_NET,
|
|
588
|
+
Blockchain,
|
|
589
|
+
Address,
|
|
590
|
+
Calldata,
|
|
591
|
+
BytesWriter,
|
|
592
|
+
StoredU256,
|
|
593
|
+
AddressMemoryMap,
|
|
594
|
+
SafeMath,
|
|
595
|
+
Revert,
|
|
596
|
+
ABIDataTypes,
|
|
597
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
598
|
+
|
|
599
|
+
@final
|
|
600
|
+
export class SimpleToken extends OP_NET {
|
|
601
|
+
// Storage pointers
|
|
602
|
+
private totalSupplyPointer: u16 = Blockchain.nextPointer;
|
|
603
|
+
private balancesPointer: u16 = Blockchain.nextPointer;
|
|
604
|
+
|
|
605
|
+
// Storage
|
|
606
|
+
private _totalSupply: StoredU256 = new StoredU256(this.totalSupplyPointer, EMPTY_POINTER);
|
|
607
|
+
private balances: AddressMemoryMap;
|
|
608
|
+
|
|
609
|
+
public constructor() {
|
|
610
|
+
super();
|
|
611
|
+
this.balances = new AddressMemoryMap(this.balancesPointer);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
public override onDeployment(calldata: Calldata): void {
|
|
615
|
+
const initialSupply = calldata.readU256();
|
|
616
|
+
|
|
617
|
+
this._totalSupply.value = initialSupply;
|
|
618
|
+
this.balances.set(Blockchain.tx.origin, initialSupply);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
@method(
|
|
622
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
623
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
624
|
+
)
|
|
625
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
626
|
+
@emit('Transfer')
|
|
627
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
628
|
+
const to = calldata.readAddress();
|
|
629
|
+
const amount = calldata.readU256();
|
|
630
|
+
const from = Blockchain.tx.sender;
|
|
631
|
+
|
|
632
|
+
// Validation
|
|
633
|
+
if (to.equals(Address.zero())) {
|
|
634
|
+
throw new Revert('Cannot transfer to zero address');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Get balances
|
|
638
|
+
const fromBalance = this.balances.get(from);
|
|
639
|
+
if (fromBalance < amount) {
|
|
640
|
+
throw new Revert('Insufficient balance');
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Update balances
|
|
644
|
+
this.balances.set(from, SafeMath.sub(fromBalance, amount));
|
|
645
|
+
this.balances.set(to, SafeMath.add(this.balances.get(to), amount));
|
|
646
|
+
|
|
647
|
+
return new BytesWriter(0);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
@method({ name: 'account', type: ABIDataTypes.ADDRESS })
|
|
651
|
+
@returns({ name: 'balance', type: ABIDataTypes.UINT256 })
|
|
652
|
+
public balanceOf(calldata: Calldata): BytesWriter {
|
|
653
|
+
const address = calldata.readAddress();
|
|
654
|
+
const balance = this.balances.get(address);
|
|
655
|
+
|
|
656
|
+
const writer = new BytesWriter(32);
|
|
657
|
+
writer.writeU256(balance);
|
|
658
|
+
return writer;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
@method()
|
|
662
|
+
@returns({ name: 'supply', type: ABIDataTypes.UINT256 })
|
|
663
|
+
public totalSupply(_calldata: Calldata): BytesWriter {
|
|
664
|
+
const writer = new BytesWriter(32);
|
|
665
|
+
writer.writeU256(this._totalSupply.value);
|
|
666
|
+
return writer;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**Note:** Method routing is handled AUTOMATICALLY via `@method` decorators. No `execute` override is needed.
|
|
672
|
+
|
|
673
|
+
## Inheritance
|
|
674
|
+
|
|
675
|
+
### Extending OP_NET
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
// Direct extension
|
|
679
|
+
export class MyContract extends OP_NET { }
|
|
680
|
+
|
|
681
|
+
// Extend with reentrancy protection
|
|
682
|
+
export class MySecureContract extends ReentrancyGuard { }
|
|
683
|
+
|
|
684
|
+
// Extend with additional features (OP20/OP721 extend ReentrancyGuard which extends OP_NET)
|
|
685
|
+
export class MyToken extends OP20 { } // OP20 extends ReentrancyGuard extends OP_NET
|
|
686
|
+
export class MyNFT extends OP721 { } // OP721 extends ReentrancyGuard extends OP_NET
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Adding Functionality
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
// Create a base class with shared functionality
|
|
693
|
+
export abstract class Pausable extends OP_NET {
|
|
694
|
+
private pausedPointer: u16 = Blockchain.nextPointer;
|
|
695
|
+
protected _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
|
|
696
|
+
|
|
697
|
+
protected whenNotPaused(): void {
|
|
698
|
+
if (this._paused.value) {
|
|
699
|
+
throw new Revert('Contract is paused');
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Use in your contract
|
|
705
|
+
export class MyToken extends Pausable {
|
|
706
|
+
@method(
|
|
707
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
708
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
709
|
+
)
|
|
710
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
711
|
+
@emit('Transfer')
|
|
712
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
713
|
+
this.whenNotPaused();
|
|
714
|
+
// ...
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**Solidity Comparison:**
|
|
720
|
+
|
|
721
|
+
```solidity
|
|
722
|
+
// Solidity: OpenZeppelin Pausable
|
|
723
|
+
import "@openzeppelin/contracts/security/Pausable.sol";
|
|
724
|
+
|
|
725
|
+
contract MyToken is ERC20, Pausable {
|
|
726
|
+
function transfer(address to, uint256 amount) public whenNotPaused {
|
|
727
|
+
// ...
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// OPNet: Custom Pausable base class
|
|
732
|
+
// export abstract class Pausable extends OP_NET { ... }
|
|
733
|
+
// this.whenNotPaused(); at start of method
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
## Best Practices
|
|
737
|
+
|
|
738
|
+
### 1. Always Use @final
|
|
739
|
+
|
|
740
|
+
```typescript
|
|
741
|
+
@final // Prevents further inheritance, enables optimizations
|
|
742
|
+
export class MyContract extends OP_NET { }
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### 2. Call super() in Constructor
|
|
746
|
+
|
|
747
|
+
```typescript
|
|
748
|
+
public constructor() {
|
|
749
|
+
super(); // Always first!
|
|
750
|
+
// Then your initialization...
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### 3. Use @method Decorators for Public Methods
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
// CORRECT: Use @method decorator for automatic routing
|
|
758
|
+
@method({ name: 'to', type: ABIDataTypes.ADDRESS }, { name: 'amount', type: ABIDataTypes.UINT256 })
|
|
759
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
760
|
+
@emit('Transfer')
|
|
761
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
762
|
+
// Implementation...
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// DO NOT manually override execute() - routing is automatic
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### 4. Document Your Methods
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
/**
|
|
772
|
+
* Transfers tokens from sender to recipient.
|
|
773
|
+
* @param calldata Contains: to (Address), amount (u256)
|
|
774
|
+
* @returns Empty BytesWriter on success
|
|
775
|
+
* @throws Revert if insufficient balance or zero address
|
|
776
|
+
*/
|
|
777
|
+
private transfer(calldata: Calldata): BytesWriter {
|
|
778
|
+
// ...
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
**Navigation:**
|
|
785
|
+
- Previous: [Security](../core-concepts/security.md)
|
|
786
|
+
- Next: [OP20 Token](./op20-token.md)
|