@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,579 @@
|
|
|
1
|
+
# Cross-Contract Calls
|
|
2
|
+
|
|
3
|
+
Cross-contract calls enable contracts to interact with each other, building composable protocols. This guide covers making calls, handling responses, and security considerations.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
---
|
|
9
|
+
config:
|
|
10
|
+
theme: dark
|
|
11
|
+
---
|
|
12
|
+
sequenceDiagram
|
|
13
|
+
participant ContractA as Contract A
|
|
14
|
+
participant Blockchain as OPNet Runtime
|
|
15
|
+
participant ContractB as Contract B
|
|
16
|
+
|
|
17
|
+
Note over ContractA: Prepare calldata
|
|
18
|
+
ContractA->>ContractA: Create BytesWriter
|
|
19
|
+
ContractA->>ContractA: writeSelector(TRANSFER_SELECTOR)
|
|
20
|
+
ContractA->>ContractA: writeAddress(recipient)
|
|
21
|
+
ContractA->>ContractA: writeU256(amount)
|
|
22
|
+
|
|
23
|
+
Note over ContractA,Blockchain: Make cross-contract call
|
|
24
|
+
ContractA->>Blockchain: call(ContractB, calldata, stopOnFailure)
|
|
25
|
+
|
|
26
|
+
Note over Blockchain: Runtime validates and dispatches
|
|
27
|
+
Blockchain->>ContractB: execute(selector, calldata)
|
|
28
|
+
|
|
29
|
+
Note over ContractB: Process method
|
|
30
|
+
ContractB->>ContractB: Parse calldata
|
|
31
|
+
ContractB->>ContractB: Execute transfer logic
|
|
32
|
+
ContractB->>ContractB: Create BytesWriter response
|
|
33
|
+
|
|
34
|
+
Note over ContractB,Blockchain: Return result
|
|
35
|
+
ContractB-->>Blockchain: BytesWriter response
|
|
36
|
+
|
|
37
|
+
Note over Blockchain: Wrap in CallResult
|
|
38
|
+
Blockchain-->>ContractA: CallResult{success: true, data: bytes}
|
|
39
|
+
|
|
40
|
+
Note over ContractA: Process response
|
|
41
|
+
ContractA->>ContractA: Check result.success
|
|
42
|
+
ContractA->>ContractA: Parse result.data
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Overview
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { Blockchain, Address, CallResult, BytesWriter, encodeSelector } from '@btc-vision/btc-runtime/runtime';
|
|
49
|
+
|
|
50
|
+
// Define method selectors (sha256 first 4 bytes of method signature)
|
|
51
|
+
const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256)
|
|
52
|
+
const BALANCE_OF_SELECTOR: u32 = 0x70a08231; // balanceOf(address)
|
|
53
|
+
|
|
54
|
+
// Make a call to another contract
|
|
55
|
+
const result: CallResult = Blockchain.call(
|
|
56
|
+
targetContract, // Address of contract to call
|
|
57
|
+
calldata, // BytesWriter with encoded function call
|
|
58
|
+
stopOnFailure // Revert entire tx on failure?
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (result.success) {
|
|
62
|
+
// Process result.data
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Making Calls
|
|
67
|
+
|
|
68
|
+
### Basic Call
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Define method selector
|
|
72
|
+
const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256)
|
|
73
|
+
|
|
74
|
+
// Encode the call
|
|
75
|
+
const writer = new BytesWriter(68);
|
|
76
|
+
writer.writeSelector(TRANSFER_SELECTOR);
|
|
77
|
+
writer.writeAddress(recipient);
|
|
78
|
+
writer.writeU256(amount);
|
|
79
|
+
|
|
80
|
+
// Make the call
|
|
81
|
+
const result = Blockchain.call(tokenContract, writer, true);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Call Parameters
|
|
85
|
+
|
|
86
|
+
| Parameter | Type | Description |
|
|
87
|
+
|-----------|------|-------------|
|
|
88
|
+
| `destinationContract` | `Address` | Contract address to call |
|
|
89
|
+
| `calldata` | `BytesWriter` | Encoded method + parameters |
|
|
90
|
+
| `stopExecutionOnFailure` | `boolean` | If true, revert on call failure (default: `true`) |
|
|
91
|
+
|
|
92
|
+
### stopOnFailure Behavior
|
|
93
|
+
|
|
94
|
+
```mermaid
|
|
95
|
+
flowchart LR
|
|
96
|
+
subgraph OPNet["OPNet Cross-Contract Call Flow"]
|
|
97
|
+
A["Blockchain.call<br/>target, data, stopOnFailure"] --> B{"stopOnFailure?"}
|
|
98
|
+
|
|
99
|
+
B -->|"true"| C["Execute call"]
|
|
100
|
+
B -->|"false"| D["Execute call"]
|
|
101
|
+
|
|
102
|
+
C --> E{"Call successful?"}
|
|
103
|
+
D --> F{"Call successful?"}
|
|
104
|
+
|
|
105
|
+
E -->|"Yes"| G["Return CallResult<br/>success: true<br/>data: response"]
|
|
106
|
+
E -->|"No"| H["REVERT ENTIRE TX<br/>Execution stops here"]
|
|
107
|
+
|
|
108
|
+
F -->|"Yes"| I["Return CallResult<br/>success: true<br/>data: response"]
|
|
109
|
+
F -->|"No"| J["Return CallResult<br/>success: false<br/>data: empty"]
|
|
110
|
+
|
|
111
|
+
G --> K["Continue execution"]
|
|
112
|
+
I --> L["Continue execution"]
|
|
113
|
+
J --> M["Continue execution<br/>Check result.success"]
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// stopOnFailure = true: Entire transaction reverts if call fails
|
|
119
|
+
const result = Blockchain.call(target, data, true);
|
|
120
|
+
// If call fails, we never reach this line
|
|
121
|
+
|
|
122
|
+
// stopOnFailure = false: Continue execution on failure
|
|
123
|
+
const result = Blockchain.call(target, data, false);
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
// Handle failure gracefully
|
|
126
|
+
// Transaction continues
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Handling Results
|
|
131
|
+
|
|
132
|
+
### CallResult Structure
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
class CallResult {
|
|
136
|
+
public readonly success: boolean; // Did the call succeed?
|
|
137
|
+
public readonly data: BytesReader; // Return data reader for parsing response
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Processing Return Data
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Make call
|
|
145
|
+
const result = Blockchain.call(tokenContract, getBalanceCalldata, true);
|
|
146
|
+
|
|
147
|
+
// Parse return data (result.data is already a BytesReader)
|
|
148
|
+
const balance: u256 = result.data.readU256();
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Error Handling
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Option 1: Strict (revert on failure)
|
|
155
|
+
const result = Blockchain.call(target, data, true);
|
|
156
|
+
// Execution only continues if successful
|
|
157
|
+
|
|
158
|
+
// Option 2: Graceful handling
|
|
159
|
+
const result = Blockchain.call(target, data, false);
|
|
160
|
+
if (!result.success) {
|
|
161
|
+
// Log, emit event, try fallback, etc.
|
|
162
|
+
Blockchain.emit(new CallFailedEvent(target));
|
|
163
|
+
throw new Revert('External call failed');
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Common Patterns
|
|
168
|
+
|
|
169
|
+
### Calling Token Transfers
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// Define method selector at the top of your contract
|
|
173
|
+
const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256)
|
|
174
|
+
|
|
175
|
+
@method(
|
|
176
|
+
{ name: 'token', type: ABIDataTypes.ADDRESS },
|
|
177
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
178
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
179
|
+
)
|
|
180
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
181
|
+
public transferToken(calldata: Calldata): BytesWriter {
|
|
182
|
+
const token = calldata.readAddress();
|
|
183
|
+
const to = calldata.readAddress();
|
|
184
|
+
const amount = calldata.readU256();
|
|
185
|
+
|
|
186
|
+
// Encode transfer(address,uint256)
|
|
187
|
+
const writer = new BytesWriter(68);
|
|
188
|
+
writer.writeSelector(TRANSFER_SELECTOR);
|
|
189
|
+
writer.writeAddress(to);
|
|
190
|
+
writer.writeU256(amount);
|
|
191
|
+
|
|
192
|
+
// Make call
|
|
193
|
+
const result = Blockchain.call(token, writer, true);
|
|
194
|
+
|
|
195
|
+
// Parse result (transfer returns bool in many implementations)
|
|
196
|
+
// result.data is already a BytesReader
|
|
197
|
+
if (result.data.byteLength > 0) {
|
|
198
|
+
const success = result.data.readBoolean();
|
|
199
|
+
if (!success) {
|
|
200
|
+
throw new Revert('Token transfer failed');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const response = new BytesWriter(1);
|
|
205
|
+
response.writeBoolean(true);
|
|
206
|
+
return response;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Calling TransferFrom
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Define method selector at the top of your contract
|
|
214
|
+
const TRANSFER_FROM_SELECTOR: u32 = 0x23b872dd; // transferFrom(address,address,uint256)
|
|
215
|
+
|
|
216
|
+
@method(
|
|
217
|
+
{ name: 'token', type: ABIDataTypes.ADDRESS },
|
|
218
|
+
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
219
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
220
|
+
)
|
|
221
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
222
|
+
public pullTokens(calldata: Calldata): BytesWriter {
|
|
223
|
+
const token = calldata.readAddress();
|
|
224
|
+
const from = calldata.readAddress();
|
|
225
|
+
const amount = calldata.readU256();
|
|
226
|
+
|
|
227
|
+
// Encode transferFrom(address,address,uint256)
|
|
228
|
+
const writer = new BytesWriter(100);
|
|
229
|
+
writer.writeSelector(TRANSFER_FROM_SELECTOR);
|
|
230
|
+
writer.writeAddress(from);
|
|
231
|
+
writer.writeAddress(Blockchain.contract.address);
|
|
232
|
+
writer.writeU256(amount);
|
|
233
|
+
|
|
234
|
+
const result = Blockchain.call(token, writer, true);
|
|
235
|
+
|
|
236
|
+
// Verify success - result.data is already a BytesReader
|
|
237
|
+
if (result.data.byteLength > 0) {
|
|
238
|
+
if (!result.data.readBoolean()) {
|
|
239
|
+
throw new Revert('TransferFrom failed');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const response = new BytesWriter(1);
|
|
244
|
+
response.writeBoolean(true);
|
|
245
|
+
return response;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Querying Another Contract
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Define method selector at the top of your contract
|
|
253
|
+
const BALANCE_OF_SELECTOR: u32 = 0x70a08231; // balanceOf(address)
|
|
254
|
+
|
|
255
|
+
@method(
|
|
256
|
+
{ name: 'token', type: ABIDataTypes.ADDRESS },
|
|
257
|
+
{ name: 'account', type: ABIDataTypes.ADDRESS },
|
|
258
|
+
)
|
|
259
|
+
@returns({ name: 'balance', type: ABIDataTypes.UINT256 })
|
|
260
|
+
public getExternalBalance(calldata: Calldata): BytesWriter {
|
|
261
|
+
const token = calldata.readAddress();
|
|
262
|
+
const account = calldata.readAddress();
|
|
263
|
+
|
|
264
|
+
// Encode balanceOf(address)
|
|
265
|
+
const writer = new BytesWriter(36);
|
|
266
|
+
writer.writeSelector(BALANCE_OF_SELECTOR);
|
|
267
|
+
writer.writeAddress(account);
|
|
268
|
+
|
|
269
|
+
const result = Blockchain.call(token, writer, true);
|
|
270
|
+
|
|
271
|
+
// result.data is already a BytesReader
|
|
272
|
+
const balance = result.data.readU256();
|
|
273
|
+
|
|
274
|
+
const response = new BytesWriter(32);
|
|
275
|
+
response.writeU256(balance);
|
|
276
|
+
return response;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Multi-Call Pattern
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Define method selector at the top of your contract
|
|
284
|
+
const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256)
|
|
285
|
+
|
|
286
|
+
@method(
|
|
287
|
+
{ name: 'tokens', type: ABIDataTypes.ADDRESS_ARRAY },
|
|
288
|
+
{ name: 'recipients', type: ABIDataTypes.ADDRESS_ARRAY },
|
|
289
|
+
{ name: 'amounts', type: ABIDataTypes.UINT256_ARRAY },
|
|
290
|
+
)
|
|
291
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
292
|
+
public batchTransfer(calldata: Calldata): BytesWriter {
|
|
293
|
+
const tokens = calldata.readAddressArray();
|
|
294
|
+
const recipients = calldata.readAddressArray();
|
|
295
|
+
const amounts = calldata.readU256Array();
|
|
296
|
+
|
|
297
|
+
for (let i: i32 = 0; i < tokens.length; i++) {
|
|
298
|
+
// Encode transfer(address,uint256)
|
|
299
|
+
const writer = new BytesWriter(68);
|
|
300
|
+
writer.writeSelector(TRANSFER_SELECTOR);
|
|
301
|
+
writer.writeAddress(recipients[i]);
|
|
302
|
+
writer.writeU256(amounts[i]);
|
|
303
|
+
|
|
304
|
+
Blockchain.call(tokens[i], writer, true);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const response = new BytesWriter(1);
|
|
308
|
+
response.writeBoolean(true);
|
|
309
|
+
return response;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Solidity Comparison
|
|
314
|
+
|
|
315
|
+
| Solidity | OPNet |
|
|
316
|
+
|----------|-------|
|
|
317
|
+
| `token.transfer(to, amount)` | `Blockchain.call(token, encodeTransfer(...), true)` |
|
|
318
|
+
| `(bool s, bytes memory d) = target.call(data)` | `Blockchain.call(target, data, false)` |
|
|
319
|
+
| `target.delegatecall(data)` | Not supported |
|
|
320
|
+
| `try/catch` | `stopOnFailure=false` + manual check |
|
|
321
|
+
|
|
322
|
+
### Example Comparison
|
|
323
|
+
|
|
324
|
+
```solidity
|
|
325
|
+
// Solidity
|
|
326
|
+
contract Router {
|
|
327
|
+
function swap(address token, uint256 amount) external {
|
|
328
|
+
IERC20(token).transferFrom(msg.sender, address(this), amount);
|
|
329
|
+
// ... swap logic ...
|
|
330
|
+
IERC20(outputToken).transfer(msg.sender, outputAmount);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// OPNet
|
|
337
|
+
import { OP_NET, Blockchain, Address, Calldata, BytesWriter, ABIDataTypes, method, returns } from '@btc-vision/btc-runtime/runtime';
|
|
338
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
339
|
+
|
|
340
|
+
// Selectors for calling OTHER contracts (cross-contract calls only)
|
|
341
|
+
const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256)
|
|
342
|
+
const TRANSFER_FROM_SELECTOR: u32 = 0x23b872dd; // transferFrom(address,address,uint256)
|
|
343
|
+
|
|
344
|
+
@final
|
|
345
|
+
export class Router extends OP_NET {
|
|
346
|
+
public constructor() {
|
|
347
|
+
super();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@method(
|
|
351
|
+
{ name: 'token', type: ABIDataTypes.ADDRESS },
|
|
352
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
353
|
+
)
|
|
354
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
355
|
+
public swap(calldata: Calldata): BytesWriter {
|
|
356
|
+
const token = calldata.readAddress();
|
|
357
|
+
const amount = calldata.readU256();
|
|
358
|
+
|
|
359
|
+
// Pull tokens
|
|
360
|
+
this.pullTokens(token, Blockchain.tx.sender, amount);
|
|
361
|
+
|
|
362
|
+
// ... swap logic ...
|
|
363
|
+
|
|
364
|
+
// Send output
|
|
365
|
+
this.transferToken(outputToken, Blockchain.tx.sender, outputAmount);
|
|
366
|
+
|
|
367
|
+
const response = new BytesWriter(1);
|
|
368
|
+
response.writeBoolean(true);
|
|
369
|
+
return response;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private pullTokens(token: Address, from: Address, amount: u256): void {
|
|
373
|
+
const writer = new BytesWriter(100);
|
|
374
|
+
writer.writeSelector(TRANSFER_FROM_SELECTOR);
|
|
375
|
+
writer.writeAddress(from);
|
|
376
|
+
writer.writeAddress(Blockchain.contract.address);
|
|
377
|
+
writer.writeU256(amount);
|
|
378
|
+
|
|
379
|
+
Blockchain.call(token, writer, true);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private transferToken(token: Address, to: Address, amount: u256): void {
|
|
383
|
+
const writer = new BytesWriter(68);
|
|
384
|
+
writer.writeSelector(TRANSFER_SELECTOR);
|
|
385
|
+
writer.writeAddress(to);
|
|
386
|
+
writer.writeU256(amount);
|
|
387
|
+
|
|
388
|
+
Blockchain.call(token, writer, true);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Security Considerations
|
|
394
|
+
|
|
395
|
+
### 1. Reentrancy Risk
|
|
396
|
+
|
|
397
|
+
External calls can trigger callbacks:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// Define method selector at the top
|
|
401
|
+
const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256)
|
|
402
|
+
|
|
403
|
+
// VULNERABLE
|
|
404
|
+
@method()
|
|
405
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
406
|
+
public withdraw(_calldata: Calldata): BytesWriter {
|
|
407
|
+
const sender = Blockchain.tx.sender;
|
|
408
|
+
const amount = balances.get(sender);
|
|
409
|
+
|
|
410
|
+
const writer = new BytesWriter(68);
|
|
411
|
+
writer.writeSelector(TRANSFER_SELECTOR);
|
|
412
|
+
writer.writeAddress(sender);
|
|
413
|
+
writer.writeU256(amount);
|
|
414
|
+
|
|
415
|
+
Blockchain.call(token, writer, true);
|
|
416
|
+
// ^ Called contract could call back into this function
|
|
417
|
+
balances.set(sender, u256.Zero);
|
|
418
|
+
|
|
419
|
+
return new BytesWriter(0);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// SAFE: Update state before call
|
|
423
|
+
@method()
|
|
424
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
425
|
+
public withdraw(_calldata: Calldata): BytesWriter {
|
|
426
|
+
const sender = Blockchain.tx.sender;
|
|
427
|
+
const amount = balances.get(sender);
|
|
428
|
+
balances.set(sender, u256.Zero); // State update first
|
|
429
|
+
|
|
430
|
+
const writer = new BytesWriter(68);
|
|
431
|
+
writer.writeSelector(TRANSFER_SELECTOR);
|
|
432
|
+
writer.writeAddress(sender);
|
|
433
|
+
writer.writeU256(amount);
|
|
434
|
+
|
|
435
|
+
Blockchain.call(token, writer, true);
|
|
436
|
+
|
|
437
|
+
return new BytesWriter(0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Or use ReentrancyGuard
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### 2. Return Value Validation
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// Always validate return data
|
|
447
|
+
const result = Blockchain.call(token, data, true);
|
|
448
|
+
|
|
449
|
+
// Don't assume success based only on not reverting
|
|
450
|
+
// result.data is a BytesReader with byteLength property
|
|
451
|
+
if (result.data.byteLength > 0) {
|
|
452
|
+
const success = result.data.readBoolean();
|
|
453
|
+
if (!success) {
|
|
454
|
+
throw new Revert('Call returned false');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### 3. Trust Assumptions
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
@method({ name: 'target', type: ABIDataTypes.ADDRESS })
|
|
463
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
464
|
+
public callExternalContract(calldata: Calldata): BytesWriter {
|
|
465
|
+
const target = calldata.readAddress();
|
|
466
|
+
|
|
467
|
+
// Only call trusted contracts
|
|
468
|
+
// Malicious contracts can:
|
|
469
|
+
// - Consume excessive resources
|
|
470
|
+
// - Return malicious data
|
|
471
|
+
// - Re-enter your contract
|
|
472
|
+
|
|
473
|
+
// Validate contract addresses
|
|
474
|
+
if (!this.trustedContracts.has(target)) {
|
|
475
|
+
throw new Revert('Untrusted contract');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Make the call...
|
|
479
|
+
return new BytesWriter(0);
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Advanced Patterns
|
|
484
|
+
|
|
485
|
+
### Interface Abstraction
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// Define method selectors at the top
|
|
489
|
+
const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256)
|
|
490
|
+
const BALANCE_OF_SELECTOR: u32 = 0x70a08231; // balanceOf(address)
|
|
491
|
+
|
|
492
|
+
// Create helper class for common calls
|
|
493
|
+
class TokenInterface {
|
|
494
|
+
constructor(private address: Address) {}
|
|
495
|
+
|
|
496
|
+
public transfer(to: Address, amount: u256): void {
|
|
497
|
+
const writer = new BytesWriter(68);
|
|
498
|
+
writer.writeSelector(TRANSFER_SELECTOR);
|
|
499
|
+
writer.writeAddress(to);
|
|
500
|
+
writer.writeU256(amount);
|
|
501
|
+
Blockchain.call(this.address, writer, true);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
public balanceOf(account: Address): u256 {
|
|
505
|
+
const writer = new BytesWriter(36);
|
|
506
|
+
writer.writeSelector(BALANCE_OF_SELECTOR);
|
|
507
|
+
writer.writeAddress(account);
|
|
508
|
+
|
|
509
|
+
const result = Blockchain.call(this.address, writer, true);
|
|
510
|
+
// result.data is already a BytesReader
|
|
511
|
+
return result.data.readU256();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Usage
|
|
516
|
+
const token = new TokenInterface(tokenAddress);
|
|
517
|
+
token.transfer(recipient, amount);
|
|
518
|
+
const balance = token.balanceOf(user);
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Callback Pattern
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
// Define method selector at the top
|
|
525
|
+
const ON_TOKEN_RECEIVED_SELECTOR: u32 = 0x150b7a02; // onTokenReceived(address,uint256,bytes)
|
|
526
|
+
|
|
527
|
+
// Contract that accepts callbacks
|
|
528
|
+
@method(
|
|
529
|
+
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
530
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
531
|
+
{ name: 'data', type: ABIDataTypes.BYTES },
|
|
532
|
+
)
|
|
533
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
534
|
+
public onTokenReceived(calldata: Calldata): BytesWriter {
|
|
535
|
+
const from = calldata.readAddress();
|
|
536
|
+
const amount = calldata.readU256();
|
|
537
|
+
const data = calldata.readBytesWithLength();
|
|
538
|
+
|
|
539
|
+
// Process callback
|
|
540
|
+
// ...
|
|
541
|
+
|
|
542
|
+
return new BytesWriter(0);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Calling contract notifies via callback
|
|
546
|
+
@method(
|
|
547
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
548
|
+
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
549
|
+
{ name: 'data', type: ABIDataTypes.BYTES },
|
|
550
|
+
)
|
|
551
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
552
|
+
public safeTransfer(calldata: Calldata): BytesWriter {
|
|
553
|
+
const to = calldata.readAddress();
|
|
554
|
+
const tokenId = calldata.readU256();
|
|
555
|
+
const data = calldata.readBytesWithLength();
|
|
556
|
+
const from = Blockchain.tx.sender;
|
|
557
|
+
|
|
558
|
+
this._transfer(from, to, tokenId);
|
|
559
|
+
|
|
560
|
+
// Check if receiver is a contract
|
|
561
|
+
// If so, call onTokenReceived
|
|
562
|
+
const writer = new BytesWriter(/* size */);
|
|
563
|
+
writer.writeSelector(ON_TOKEN_RECEIVED_SELECTOR);
|
|
564
|
+
writer.writeAddress(from);
|
|
565
|
+
writer.writeU256(tokenId);
|
|
566
|
+
writer.writeBytesWithLength(data);
|
|
567
|
+
|
|
568
|
+
const result = Blockchain.call(to, writer, false);
|
|
569
|
+
// Validate response...
|
|
570
|
+
|
|
571
|
+
return new BytesWriter(0);
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
**Navigation:**
|
|
578
|
+
- Previous: [Memory Maps](../storage/memory-maps.md)
|
|
579
|
+
- Next: [Signature Verification](./signature-verification.md)
|