@btc-vision/btc-runtime 1.10.11 → 1.11.0-alpha
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/README.md +48 -224
- package/SECURITY.md +38 -191
- package/docs/README.md +28 -0
- package/docs/advanced/contract-upgrades.md +537 -0
- package/docs/advanced/plugins.md +90 -25
- package/docs/api-reference/blockchain.md +48 -14
- package/docs/api-reference/storage.md +2 -111
- package/docs/contracts/op-net-base.md +22 -0
- package/docs/contracts/upgradeable.md +396 -0
- package/docs/core-concepts/blockchain-environment.md +0 -2
- package/docs/core-concepts/security.md +8 -111
- package/docs/core-concepts/storage-system.md +1 -32
- package/docs/examples/nft-with-reservations.md +8 -238
- package/docs/storage/memory-maps.md +1 -44
- package/docs/storage/stored-arrays.md +1 -65
- package/docs/storage/stored-maps.md +1 -73
- package/docs/storage/stored-primitives.md +2 -49
- package/docs/types/bytes-writer-reader.md +76 -0
- package/docs/types/safe-math.md +2 -45
- package/package.json +5 -5
- package/runtime/buffer/BytesReader.ts +90 -3
- package/runtime/buffer/BytesWriter.ts +81 -3
- package/runtime/contracts/OP721.ts +40 -4
- package/runtime/contracts/OP_NET.ts +83 -11
- package/runtime/contracts/Upgradeable.ts +242 -0
- package/runtime/env/BlockchainEnvironment.ts +124 -27
- package/runtime/env/global.ts +24 -0
- package/runtime/events/upgradeable/UpgradeableEvents.ts +41 -0
- package/runtime/generic/AddressMap.ts +20 -18
- package/runtime/generic/ExtendedAddressMap.ts +147 -0
- package/runtime/generic/MapUint8Array.ts +20 -18
- package/runtime/index.ts +8 -0
- package/runtime/plugins/Plugin.ts +34 -0
- package/runtime/plugins/UpgradeablePlugin.ts +279 -0
- package/runtime/storage/BaseStoredString.ts +1 -1
- package/runtime/storage/arrays/StoredPackedArray.ts +4 -0
- package/runtime/types/ExtendedAddress.ts +36 -24
- package/runtime/types/ExtendedAddressCache.ts +27 -0
- package/runtime/types/SafeMath.ts +109 -18
- package/runtime/types/SchnorrSignature.ts +44 -0
- package/runtime/utils/lengths.ts +2 -0
|
@@ -906,249 +906,19 @@ public reserve(calldata: Calldata): BytesWriter { }
|
|
|
906
906
|
public getSaleInfo(_calldata: Calldata): BytesWriter { }
|
|
907
907
|
```
|
|
908
908
|
|
|
909
|
-
## Solidity
|
|
910
|
-
|
|
911
|
-
For developers familiar with Solidity, here is an equivalent ERC721 implementation with reservations and reveal mechanics:
|
|
912
|
-
|
|
913
|
-
```solidity
|
|
914
|
-
// SPDX-License-Identifier: MIT
|
|
915
|
-
pragma solidity ^0.8.20;
|
|
916
|
-
|
|
917
|
-
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
918
|
-
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
919
|
-
import "@openzeppelin/contracts/utils/Strings.sol";
|
|
920
|
-
|
|
921
|
-
contract NFTWithReservations is ERC721, Ownable {
|
|
922
|
-
using Strings for uint256;
|
|
923
|
-
|
|
924
|
-
enum SalePhase { INACTIVE, WHITELIST, PUBLIC }
|
|
925
|
-
|
|
926
|
-
// Configuration
|
|
927
|
-
uint256 public maxSupply;
|
|
928
|
-
uint256 public price;
|
|
929
|
-
uint256 public maxPerWallet;
|
|
930
|
-
string private baseURI_;
|
|
931
|
-
string private hiddenURI;
|
|
932
|
-
bool public revealed;
|
|
933
|
-
SalePhase public salePhase;
|
|
934
|
-
uint256 private nextTokenId = 1;
|
|
935
|
-
|
|
936
|
-
// Reservation system
|
|
937
|
-
uint256 public reservationEnd;
|
|
938
|
-
mapping(address => uint256) public reservations;
|
|
939
|
-
|
|
940
|
-
// Whitelist
|
|
941
|
-
mapping(address => bool) public whitelist;
|
|
942
|
-
mapping(address => uint256) public mintedCount;
|
|
943
|
-
|
|
944
|
-
event Reserved(address indexed user, uint256 quantity);
|
|
945
|
-
event ReservationClaimed(address indexed user, uint256 quantity);
|
|
946
|
-
event ReservationCancelled(address indexed user, uint256 quantity);
|
|
947
|
-
|
|
948
|
-
constructor(
|
|
949
|
-
string memory name,
|
|
950
|
-
string memory symbol,
|
|
951
|
-
uint256 _maxSupply,
|
|
952
|
-
uint256 _price,
|
|
953
|
-
uint256 _maxPerWallet,
|
|
954
|
-
string memory _hiddenURI
|
|
955
|
-
) ERC721(name, symbol) Ownable(msg.sender) {
|
|
956
|
-
maxSupply = _maxSupply;
|
|
957
|
-
price = _price;
|
|
958
|
-
maxPerWallet = _maxPerWallet;
|
|
959
|
-
hiddenURI = _hiddenURI;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// ============ RESERVATION SYSTEM ============
|
|
963
|
-
|
|
964
|
-
function reserve(uint256 quantity) external payable {
|
|
965
|
-
require(block.timestamp < reservationEnd, "Reservation period ended");
|
|
966
|
-
require(reservations[msg.sender] + quantity <= maxPerWallet, "Exceeds max per wallet");
|
|
967
|
-
require(msg.value >= price * quantity, "Insufficient payment");
|
|
968
|
-
|
|
969
|
-
reservations[msg.sender] += quantity;
|
|
970
|
-
emit Reserved(msg.sender, quantity);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
function claimReserved() external {
|
|
974
|
-
require(block.timestamp >= reservationEnd, "Reservation period not ended");
|
|
975
|
-
uint256 quantity = reservations[msg.sender];
|
|
976
|
-
require(quantity > 0, "No reservations");
|
|
977
|
-
|
|
978
|
-
reservations[msg.sender] = 0;
|
|
979
|
-
|
|
980
|
-
for (uint256 i = 0; i < quantity; i++) {
|
|
981
|
-
_safeMint(msg.sender, nextTokenId++);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
emit ReservationClaimed(msg.sender, quantity);
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
function cancelReservation() external {
|
|
988
|
-
require(block.timestamp < reservationEnd, "Reservation period ended");
|
|
989
|
-
uint256 quantity = reservations[msg.sender];
|
|
990
|
-
require(quantity > 0, "No reservations");
|
|
991
|
-
|
|
992
|
-
reservations[msg.sender] = 0;
|
|
993
|
-
|
|
994
|
-
// Refund
|
|
995
|
-
uint256 refundAmount = price * quantity;
|
|
996
|
-
(bool success, ) = msg.sender.call{value: refundAmount}("");
|
|
997
|
-
require(success, "Refund failed");
|
|
998
|
-
|
|
999
|
-
emit ReservationCancelled(msg.sender, quantity);
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// ============ MINTING ============
|
|
1003
|
-
|
|
1004
|
-
function whitelistMint(uint256 quantity) external payable {
|
|
1005
|
-
require(salePhase == SalePhase.WHITELIST, "Whitelist sale not active");
|
|
1006
|
-
require(whitelist[msg.sender], "Not whitelisted");
|
|
1007
|
-
require(msg.value >= price * quantity, "Insufficient payment");
|
|
1008
|
-
|
|
1009
|
-
_mintInternal(msg.sender, quantity);
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
function publicMint(uint256 quantity) external payable {
|
|
1013
|
-
require(salePhase == SalePhase.PUBLIC, "Public sale not active");
|
|
1014
|
-
require(msg.value >= price * quantity, "Insufficient payment");
|
|
1015
|
-
|
|
1016
|
-
_mintInternal(msg.sender, quantity);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
function _mintInternal(address to, uint256 quantity) internal {
|
|
1020
|
-
require(nextTokenId + quantity - 1 <= maxSupply, "Exceeds max supply");
|
|
1021
|
-
require(mintedCount[to] + quantity <= maxPerWallet, "Exceeds max per wallet");
|
|
1022
|
-
|
|
1023
|
-
mintedCount[to] += quantity;
|
|
1024
|
-
|
|
1025
|
-
for (uint256 i = 0; i < quantity; i++) {
|
|
1026
|
-
_safeMint(to, nextTokenId++);
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// ============ REVEAL ============
|
|
1031
|
-
|
|
1032
|
-
function tokenURI(uint256 tokenId) public view override returns (string memory) {
|
|
1033
|
-
require(_ownerOf(tokenId) != address(0), "Token does not exist");
|
|
1034
|
-
|
|
1035
|
-
if (!revealed) {
|
|
1036
|
-
return hiddenURI;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
return string(abi.encodePacked(baseURI_, tokenId.toString(), ".json"));
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// ============ ADMIN FUNCTIONS ============
|
|
1043
|
-
|
|
1044
|
-
function startReservation(uint256 duration) external onlyOwner {
|
|
1045
|
-
reservationEnd = block.timestamp + duration;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
function setSalePhase(SalePhase phase) external onlyOwner {
|
|
1049
|
-
salePhase = phase;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
function setWhitelist(address[] calldata addresses, bool status) external onlyOwner {
|
|
1053
|
-
for (uint256 i = 0; i < addresses.length; i++) {
|
|
1054
|
-
whitelist[addresses[i]] = status;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
function reveal(string calldata _baseURI) external onlyOwner {
|
|
1059
|
-
baseURI_ = _baseURI;
|
|
1060
|
-
revealed = true;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
function setPrice(uint256 _price) external onlyOwner {
|
|
1064
|
-
price = _price;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
function withdraw() external onlyOwner {
|
|
1068
|
-
(bool success, ) = owner().call{value: address(this).balance}("");
|
|
1069
|
-
require(success, "Withdrawal failed");
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// ============ VIEW FUNCTIONS ============
|
|
1073
|
-
|
|
1074
|
-
function totalSupply() public view returns (uint256) {
|
|
1075
|
-
return nextTokenId - 1;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
function getSaleInfo() external view returns (
|
|
1079
|
-
SalePhase phase,
|
|
1080
|
-
uint256 currentPrice,
|
|
1081
|
-
uint256 maxSupply_,
|
|
1082
|
-
uint256 totalSupply_,
|
|
1083
|
-
bool isRevealed
|
|
1084
|
-
) {
|
|
1085
|
-
return (salePhase, price, maxSupply, totalSupply(), revealed);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
## Solidity vs OPNet Comparison
|
|
1091
|
-
|
|
1092
|
-
### Key Differences Table
|
|
909
|
+
## Solidity Comparison
|
|
1093
910
|
|
|
1094
911
|
| Aspect | Solidity (ERC721) | OPNet (OP721) |
|
|
1095
912
|
|--------|-------------------|---------------|
|
|
1096
|
-
|
|
|
1097
|
-
|
|
|
1098
|
-
|
|
|
1099
|
-
|
|
|
1100
|
-
|
|
|
1101
|
-
| **Timestamp** | `block.timestamp` | `Blockchain.block.medianTime` |
|
|
1102
|
-
| **Whitelist Storage** | `mapping(address => bool)` | `AddressMemoryMap` |
|
|
1103
|
-
| **Payment Handling** | `msg.value`, `payable` | Bitcoin UTXO model |
|
|
1104
|
-
| **String Concat** | `string(abi.encodePacked(...))` | `baseURI + tokenId.toString() + '.json'` |
|
|
1105
|
-
|
|
1106
|
-
### Reservation Pattern Comparison
|
|
1107
|
-
|
|
1108
|
-
**Solidity:**
|
|
1109
|
-
```solidity
|
|
1110
|
-
mapping(address => uint256) public reservations;
|
|
1111
|
-
uint256 public reservationEnd;
|
|
1112
|
-
|
|
1113
|
-
function reserve(uint256 quantity) external payable {
|
|
1114
|
-
require(block.timestamp < reservationEnd, "Reservation period ended");
|
|
1115
|
-
require(reservations[msg.sender] + quantity <= maxPerWallet, "Exceeds max per wallet");
|
|
1116
|
-
require(msg.value >= price * quantity, "Insufficient payment");
|
|
1117
|
-
|
|
1118
|
-
reservations[msg.sender] += quantity;
|
|
1119
|
-
}
|
|
1120
|
-
```
|
|
1121
|
-
|
|
1122
|
-
**OPNet:**
|
|
1123
|
-
```typescript
|
|
1124
|
-
private _reservedBy: AddressMemoryMap;
|
|
1125
|
-
private _reservationEnd: StoredU256;
|
|
913
|
+
| Inheritance | `contract NFT is ERC721, Ownable` | `class NFT extends OP721` |
|
|
914
|
+
| Constructor | `constructor() ERC721("Name", "SYM")` | `onDeployment()` + `this.instantiate(...)` |
|
|
915
|
+
| Mint | `_safeMint(to, tokenId)` | `this._mint(to, tokenId)` |
|
|
916
|
+
| Timestamp | `block.timestamp` | `Blockchain.block.medianTime` |
|
|
917
|
+
| Whitelist Storage | `mapping(address => bool)` | `AddressMemoryMap` |
|
|
1126
918
|
|
|
1127
|
-
|
|
1128
|
-
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
1129
|
-
@emit('Reserved')
|
|
1130
|
-
public reserve(calldata: Calldata): BytesWriter {
|
|
1131
|
-
const quantity = calldata.readU256();
|
|
1132
|
-
const sender = Blockchain.tx.sender;
|
|
1133
|
-
|
|
1134
|
-
const now = u256.fromU64(Blockchain.block.medianTime);
|
|
1135
|
-
if (now >= this._reservationEnd.value) {
|
|
1136
|
-
throw new Revert('Reservation period ended');
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
const currentReserved = this._reservedBy.get(sender);
|
|
1140
|
-
const newTotal = SafeMath.add(currentReserved, quantity);
|
|
1141
|
-
|
|
1142
|
-
if (newTotal > this._maxPerWallet.value) {
|
|
1143
|
-
throw new Revert('Exceeds max per wallet');
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
this._reservedBy.set(sender, newTotal);
|
|
1147
|
-
return new BytesWriter(0);
|
|
1148
|
-
}
|
|
1149
|
-
```
|
|
919
|
+
For detailed OP721 API documentation, see [OP721 Contract](../contracts/op721-nft.md).
|
|
1150
920
|
|
|
1151
|
-
### Whitelist
|
|
921
|
+
### Whitelist Implementation Comparison
|
|
1152
922
|
|
|
1153
923
|
**Solidity (Using Merkle Proofs):**
|
|
1154
924
|
```solidity
|
|
@@ -202,50 +202,7 @@ flowchart LR
|
|
|
202
202
|
| `transfer(to, amount)` | `balances[msg.sender] -= amount; balances[to] += amount;` | `this.balances.set(sender, SafeMath.sub(...)); this.balances.set(to, SafeMath.add(...));` |
|
|
203
203
|
| `approve(spender, amount)` | `allowances[msg.sender][spender] = amount;` | Use `MapOfMap<u256>` for nested mapping |
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
```solidity
|
|
208
|
-
// Solidity
|
|
209
|
-
contract Token {
|
|
210
|
-
mapping(address => uint256) public balances;
|
|
211
|
-
|
|
212
|
-
function transfer(address to, uint256 amount) external {
|
|
213
|
-
require(balances[msg.sender] >= amount, "Insufficient");
|
|
214
|
-
balances[msg.sender] -= amount;
|
|
215
|
-
balances[to] += amount;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
// OPNet
|
|
222
|
-
@final
|
|
223
|
-
export class Token extends OP_NET {
|
|
224
|
-
private balancesPointer: u16 = Blockchain.nextPointer;
|
|
225
|
-
private balances: AddressMemoryMap;
|
|
226
|
-
|
|
227
|
-
constructor() {
|
|
228
|
-
super();
|
|
229
|
-
this.balances = new AddressMemoryMap(this.balancesPointer);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
public transfer(calldata: Calldata): BytesWriter {
|
|
233
|
-
const to = calldata.readAddress();
|
|
234
|
-
const amount = calldata.readU256();
|
|
235
|
-
const sender = Blockchain.tx.sender;
|
|
236
|
-
|
|
237
|
-
const senderBalance = this.balances.get(sender);
|
|
238
|
-
if (senderBalance < amount) {
|
|
239
|
-
throw new Revert('Insufficient balance');
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
this.balances.set(sender, SafeMath.sub(senderBalance, amount));
|
|
243
|
-
this.balances.set(to, SafeMath.add(this.balances.get(to), amount));
|
|
244
|
-
|
|
245
|
-
return new BytesWriter(0);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
```
|
|
205
|
+
For a complete token implementation using AddressMemoryMap, see [Basic Token Example](../examples/basic-token.md).
|
|
249
206
|
|
|
250
207
|
## Side-by-Side Code Examples
|
|
251
208
|
|
|
@@ -238,71 +238,7 @@ if (this.holders.getLength() === 0) {
|
|
|
238
238
|
| Get last element | `arr[arr.length - 1]` | `arr.get(arr.getLength() - 1)` |
|
|
239
239
|
| Initialize with values | `arr = [1, 2, 3];` | Multiple `arr.push()` calls in `onDeployment`, then `arr.save()` |
|
|
240
240
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
```solidity
|
|
244
|
-
// Solidity
|
|
245
|
-
contract Registry {
|
|
246
|
-
address[] public members;
|
|
247
|
-
|
|
248
|
-
function addMember(address member) external {
|
|
249
|
-
members.push(member);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function removeMember(uint256 index) external {
|
|
253
|
-
members[index] = members[members.length - 1];
|
|
254
|
-
members.pop();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function getMemberCount() external view returns (uint256) {
|
|
258
|
-
return members.length;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
// OPNet
|
|
265
|
-
@final
|
|
266
|
-
export class Registry extends OP_NET {
|
|
267
|
-
private membersPointer: u16 = Blockchain.nextPointer;
|
|
268
|
-
private members: StoredAddressArray;
|
|
269
|
-
|
|
270
|
-
constructor() {
|
|
271
|
-
super();
|
|
272
|
-
this.members = new StoredAddressArray(this.membersPointer, EMPTY_POINTER);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
public addMember(calldata: Calldata): BytesWriter {
|
|
276
|
-
const member = calldata.readAddress();
|
|
277
|
-
this.members.push(member);
|
|
278
|
-
this.members.save();
|
|
279
|
-
return new BytesWriter(0);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
public removeMember(calldata: Calldata): BytesWriter {
|
|
283
|
-
const index = calldata.readU32();
|
|
284
|
-
const length = this.members.getLength();
|
|
285
|
-
|
|
286
|
-
if (index >= length) {
|
|
287
|
-
throw new Revert('Index out of bounds');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (index < length - 1) {
|
|
291
|
-
this.members.set(index, this.members.get(length - 1));
|
|
292
|
-
}
|
|
293
|
-
this.members.deleteLast();
|
|
294
|
-
this.members.save();
|
|
295
|
-
|
|
296
|
-
return new BytesWriter(0);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
public getMemberCount(_calldata: Calldata): BytesWriter {
|
|
300
|
-
const writer = new BytesWriter(4);
|
|
301
|
-
writer.writeU32(this.members.getLength());
|
|
302
|
-
return writer;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
241
|
+
For complete contract examples using stored arrays, see the [Examples](../examples/) section.
|
|
306
242
|
|
|
307
243
|
## Side-by-Side Code Examples
|
|
308
244
|
|
|
@@ -312,79 +312,7 @@ flowchart LR
|
|
|
312
312
|
| Token metadata | `mapping(uint256 => string)` | `StoredMapU256` with encoded strings |
|
|
313
313
|
| Checkpoints | `mapping(address => mapping(uint256 => uint256))` | `MapOfMap<u256>` or composite keys |
|
|
314
314
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
```solidity
|
|
318
|
-
// Solidity
|
|
319
|
-
contract Token {
|
|
320
|
-
mapping(address => uint256) public balances;
|
|
321
|
-
mapping(address => mapping(address => uint256)) public allowances;
|
|
322
|
-
|
|
323
|
-
function transfer(address to, uint256 amount) external {
|
|
324
|
-
require(balances[msg.sender] >= amount);
|
|
325
|
-
balances[msg.sender] -= amount;
|
|
326
|
-
balances[to] += amount;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function approve(address spender, uint256 amount) external {
|
|
330
|
-
allowances[msg.sender][spender] = amount;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
// OPNet
|
|
337
|
-
@final
|
|
338
|
-
export class Token extends OP_NET {
|
|
339
|
-
private balancesPointer: u16 = Blockchain.nextPointer;
|
|
340
|
-
private allowancesPointer: u16 = Blockchain.nextPointer;
|
|
341
|
-
|
|
342
|
-
private balances: StoredMapU256;
|
|
343
|
-
private allowances: MapOfMap<u256>;
|
|
344
|
-
|
|
345
|
-
constructor() {
|
|
346
|
-
super();
|
|
347
|
-
this.balances = new StoredMapU256(this.balancesPointer);
|
|
348
|
-
this.allowances = new MapOfMap<u256>(this.allowancesPointer);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
private addressKey(addr: Address): u256 {
|
|
352
|
-
return u256.fromBytes(addr.toBytes());
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
public transfer(calldata: Calldata): BytesWriter {
|
|
356
|
-
const to = calldata.readAddress();
|
|
357
|
-
const amount = calldata.readU256();
|
|
358
|
-
const sender = Blockchain.tx.sender;
|
|
359
|
-
|
|
360
|
-
const senderKey = this.addressKey(sender);
|
|
361
|
-
const toKey = this.addressKey(to);
|
|
362
|
-
|
|
363
|
-
const senderBalance = this.balances.get(senderKey);
|
|
364
|
-
if (senderBalance < amount) {
|
|
365
|
-
throw new Revert('Insufficient balance');
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
this.balances.set(senderKey, SafeMath.sub(senderBalance, amount));
|
|
369
|
-
this.balances.set(toKey, SafeMath.add(this.balances.get(toKey), amount));
|
|
370
|
-
|
|
371
|
-
return new BytesWriter(0);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
public approve(calldata: Calldata): BytesWriter {
|
|
375
|
-
const spender = calldata.readAddress();
|
|
376
|
-
const amount = calldata.readU256();
|
|
377
|
-
const sender = Blockchain.tx.sender;
|
|
378
|
-
|
|
379
|
-
// Correct two-step MapOfMap pattern
|
|
380
|
-
const senderMap = this.allowances.get(sender);
|
|
381
|
-
senderMap.set(spender, amount);
|
|
382
|
-
this.allowances.set(sender, senderMap);
|
|
383
|
-
|
|
384
|
-
return new BytesWriter(0);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
```
|
|
315
|
+
For a complete token implementation using these map types, see [Basic Token Example](../examples/basic-token.md).
|
|
388
316
|
|
|
389
317
|
## Side-by-Side Code Examples
|
|
390
318
|
|
|
@@ -102,14 +102,7 @@ classDiagram
|
|
|
102
102
|
|
|
103
103
|
## Storage Key Generation
|
|
104
104
|
|
|
105
|
-
Each stored primitive computes its storage key
|
|
106
|
-
|
|
107
|
-
```mermaid
|
|
108
|
-
flowchart LR
|
|
109
|
-
A["pointer: u16<br/>subPointer: u256"] --> B["32-byte buffer<br/>[0-1] = pointer<br/>[2-31] = subPointer"]
|
|
110
|
-
B --> C["SHA256"]
|
|
111
|
-
C --> D["Storage Key<br/>(32 bytes)"]
|
|
112
|
-
```
|
|
105
|
+
Each stored primitive computes its storage key using `SHA256(pointer || subPointer)`. See [Pointers](../core-concepts/pointers.md#encodepointer-function-flow) for the detailed flow diagram.
|
|
113
106
|
|
|
114
107
|
## Usage
|
|
115
108
|
|
|
@@ -323,47 +316,7 @@ public override onDeployment(calldata: Calldata): void {
|
|
|
323
316
|
| `bool public paused = false;` | `private pausedPtr: u16 = Blockchain.nextPointer;`<br>`private _paused: StoredBoolean = new StoredBoolean(this.pausedPtr, false);` |
|
|
324
317
|
| `address public owner;` | `private ownerPtr: u16 = Blockchain.nextPointer;`<br>`private _owner: StoredAddress = new StoredAddress(this.ownerPtr);` |
|
|
325
318
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
```solidity
|
|
329
|
-
// Solidity
|
|
330
|
-
contract Token {
|
|
331
|
-
string public name; // slot 0
|
|
332
|
-
uint256 public supply; // slot 1
|
|
333
|
-
bool public paused; // slot 2 (packed)
|
|
334
|
-
|
|
335
|
-
constructor(string memory _name, uint256 _supply) {
|
|
336
|
-
name = _name;
|
|
337
|
-
supply = _supply;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
// OPNet
|
|
344
|
-
@final
|
|
345
|
-
export class Token extends OP_NET {
|
|
346
|
-
private namePointer: u16 = Blockchain.nextPointer;
|
|
347
|
-
private supplyPointer: u16 = Blockchain.nextPointer;
|
|
348
|
-
private pausedPointer: u16 = Blockchain.nextPointer;
|
|
349
|
-
|
|
350
|
-
private _name: StoredString = new StoredString(this.namePointer, 0);
|
|
351
|
-
private _supply: StoredU256 = new StoredU256(this.supplyPointer, EMPTY_POINTER);
|
|
352
|
-
private _paused: StoredBoolean = new StoredBoolean(this.pausedPointer, false);
|
|
353
|
-
|
|
354
|
-
public override onDeployment(calldata: Calldata): void {
|
|
355
|
-
this._name.value = calldata.readString();
|
|
356
|
-
this._supply.value = calldata.readU256();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Manual getter
|
|
360
|
-
public name(_calldata: Calldata): BytesWriter {
|
|
361
|
-
const writer = new BytesWriter(256);
|
|
362
|
-
writer.writeString(this._name.value);
|
|
363
|
-
return writer;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
```
|
|
319
|
+
For complete token examples using stored primitives, see [Basic Token Example](../examples/basic-token.md).
|
|
367
320
|
|
|
368
321
|
## Side-by-Side Code Examples
|
|
369
322
|
|
|
@@ -31,6 +31,7 @@ classDiagram
|
|
|
31
31
|
-Uint8Array typedArray
|
|
32
32
|
-u32 currentOffset
|
|
33
33
|
+BytesWriter(length)
|
|
34
|
+
+write~T~(value) void
|
|
34
35
|
+writeU8(value)
|
|
35
36
|
+writeU16(value, be)
|
|
36
37
|
+writeU32(value, be)
|
|
@@ -38,9 +39,13 @@ classDiagram
|
|
|
38
39
|
+writeU128(value, be)
|
|
39
40
|
+writeU256(value, be)
|
|
40
41
|
+writeAddress(addr)
|
|
42
|
+
+writeExtendedAddress(extAddr)
|
|
43
|
+
+writeSchnorrSignature(addr, sig)
|
|
41
44
|
+writeString(str)
|
|
42
45
|
+writeBytes(data)
|
|
43
46
|
+writeBoolean(bool)
|
|
47
|
+
+writeExtendedAddressArray(arr)
|
|
48
|
+
+writeExtendedAddressMapU256(map)
|
|
44
49
|
+getBuffer() Uint8Array
|
|
45
50
|
}
|
|
46
51
|
|
|
@@ -48,6 +53,7 @@ classDiagram
|
|
|
48
53
|
-DataView buffer
|
|
49
54
|
-i32 currentOffset
|
|
50
55
|
+BytesReader(bytes)
|
|
56
|
+
+read~T~() T
|
|
51
57
|
+readU8() u8
|
|
52
58
|
+readU16(be) u16
|
|
53
59
|
+readU32(be) u32
|
|
@@ -55,9 +61,13 @@ classDiagram
|
|
|
55
61
|
+readU128(be) u128
|
|
56
62
|
+readU256(be) u256
|
|
57
63
|
+readAddress() Address
|
|
64
|
+
+readExtendedAddress() ExtendedAddress
|
|
65
|
+
+readSchnorrSignature() SchnorrSignature
|
|
58
66
|
+readString() string
|
|
59
67
|
+readBytes(length) Uint8Array
|
|
60
68
|
+readBoolean() bool
|
|
69
|
+
+readExtendedAddressArray() ExtendedAddress[]
|
|
70
|
+
+readExtendedAddressMapU256() ExtendedAddressMap
|
|
61
71
|
+hasMoreData() bool
|
|
62
72
|
}
|
|
63
73
|
|
|
@@ -113,6 +123,12 @@ writer.writeI64(value);
|
|
|
113
123
|
// Address (32 bytes)
|
|
114
124
|
writer.writeAddress(address);
|
|
115
125
|
|
|
126
|
+
// ExtendedAddress (64 bytes: 32 tweaked key + 32 ML-DSA key hash)
|
|
127
|
+
writer.writeExtendedAddress(extendedAddress);
|
|
128
|
+
|
|
129
|
+
// Schnorr signature with signer address (128 bytes total)
|
|
130
|
+
writer.writeSchnorrSignature(signerAddress, signature);
|
|
131
|
+
|
|
116
132
|
// String without length prefix
|
|
117
133
|
writer.writeString('Hello, World!');
|
|
118
134
|
|
|
@@ -129,6 +145,25 @@ writer.writeBytesWithLength(data);
|
|
|
129
145
|
writer.writeSelector(selector);
|
|
130
146
|
```
|
|
131
147
|
|
|
148
|
+
### Generic Write Method
|
|
149
|
+
|
|
150
|
+
The `write<T>()` method automatically selects the correct write method based on the type:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Automatically dispatches to the correct write method
|
|
154
|
+
writer.write<u64>(12345); // writeU64
|
|
155
|
+
writer.write<u256>(amount); // writeU256
|
|
156
|
+
writer.write<bool>(true); // writeBoolean
|
|
157
|
+
writer.write<Address>(addr); // writeAddress
|
|
158
|
+
writer.write<ExtendedAddress>(extAddr); // writeExtendedAddress
|
|
159
|
+
writer.write<string>('hello'); // writeStringWithLength
|
|
160
|
+
|
|
161
|
+
// Supported types:
|
|
162
|
+
// - Primitives: bool, u8, u16, u32, u64, i8, i16, i32, i64
|
|
163
|
+
// - Big numbers: u128, u256, i128
|
|
164
|
+
// - Complex: Address, ExtendedAddress, Selector, string, Uint8Array
|
|
165
|
+
```
|
|
166
|
+
|
|
132
167
|
### Writing Arrays
|
|
133
168
|
|
|
134
169
|
All array methods use a u16 length prefix (max 65535 elements):
|
|
@@ -137,6 +172,9 @@ All array methods use a u16 length prefix (max 65535 elements):
|
|
|
137
172
|
// Address array (u16 length prefix + addresses)
|
|
138
173
|
writer.writeAddressArray(addresses);
|
|
139
174
|
|
|
175
|
+
// ExtendedAddress array (u16 length prefix + 64-byte addresses)
|
|
176
|
+
writer.writeExtendedAddressArray(extendedAddresses);
|
|
177
|
+
|
|
140
178
|
// Numeric arrays (u16 length prefix + values)
|
|
141
179
|
writer.writeU8Array(u8Values);
|
|
142
180
|
writer.writeU16Array(u16Values);
|
|
@@ -150,6 +188,9 @@ writer.writeArrayOfBuffer(buffers);
|
|
|
150
188
|
|
|
151
189
|
// AddressMap<u256> (u16 count, then address + u256 pairs)
|
|
152
190
|
writer.writeAddressMapU256(addressMap);
|
|
191
|
+
|
|
192
|
+
// ExtendedAddressMap<u256> (u16 count, then 64-byte address + u256 pairs)
|
|
193
|
+
writer.writeExtendedAddressMapU256(extendedAddressMap);
|
|
153
194
|
```
|
|
154
195
|
|
|
155
196
|
### Getting Results
|
|
@@ -222,6 +263,14 @@ const i64val: i64 = reader.readI64();
|
|
|
222
263
|
// Address (32 bytes)
|
|
223
264
|
const addr: Address = reader.readAddress();
|
|
224
265
|
|
|
266
|
+
// ExtendedAddress (64 bytes: 32 tweaked key + 32 ML-DSA key hash)
|
|
267
|
+
const extAddr: ExtendedAddress = reader.readExtendedAddress();
|
|
268
|
+
|
|
269
|
+
// Schnorr signature with signer address (128 bytes)
|
|
270
|
+
const schnorrSig: SchnorrSignature = reader.readSchnorrSignature();
|
|
271
|
+
const signer: ExtendedAddress = schnorrSig.address;
|
|
272
|
+
const signature: Uint8Array = schnorrSig.signature;
|
|
273
|
+
|
|
225
274
|
// String with known length (bytes read, zeroStop = true)
|
|
226
275
|
const name: string = reader.readString(32); // reads up to 32 bytes, stops at null
|
|
227
276
|
|
|
@@ -238,6 +287,25 @@ const data2: Uint8Array = reader.readBytesWithLength();
|
|
|
238
287
|
const selector: Selector = reader.readSelector();
|
|
239
288
|
```
|
|
240
289
|
|
|
290
|
+
### Generic Read Method
|
|
291
|
+
|
|
292
|
+
The `read<T>()` method automatically selects the correct read method based on the type:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Automatically dispatches to the correct read method
|
|
296
|
+
const num: u64 = reader.read<u64>(); // readU64
|
|
297
|
+
const amount: u256 = reader.read<u256>(); // readU256
|
|
298
|
+
const flag: bool = reader.read<bool>(); // readBoolean
|
|
299
|
+
const addr: Address = reader.read<Address>(); // readAddress
|
|
300
|
+
const extAddr: ExtendedAddress = reader.read<ExtendedAddress>(); // readExtendedAddress
|
|
301
|
+
const text: string = reader.read<string>(); // readStringWithLength
|
|
302
|
+
|
|
303
|
+
// Supported types:
|
|
304
|
+
// - Primitives: bool, u8, u16, u32, u64, i8, i16, i32, i64
|
|
305
|
+
// - Big numbers: u128, u256, i128
|
|
306
|
+
// - Complex: Address, ExtendedAddress, Selector, string
|
|
307
|
+
```
|
|
308
|
+
|
|
241
309
|
### Reading Arrays
|
|
242
310
|
|
|
243
311
|
All array methods expect a u16 length prefix:
|
|
@@ -246,6 +314,9 @@ All array methods expect a u16 length prefix:
|
|
|
246
314
|
// Address array
|
|
247
315
|
const addresses: Address[] = reader.readAddressArray();
|
|
248
316
|
|
|
317
|
+
// ExtendedAddress array (64 bytes each)
|
|
318
|
+
const extAddresses: ExtendedAddress[] = reader.readExtendedAddressArray();
|
|
319
|
+
|
|
249
320
|
// Numeric arrays
|
|
250
321
|
const u8Values: u8[] = reader.readU8Array();
|
|
251
322
|
const u16Values: u16[] = reader.readU16Array();
|
|
@@ -259,6 +330,9 @@ const buffers: Uint8Array[] = reader.readArrayOfBuffer();
|
|
|
259
330
|
|
|
260
331
|
// AddressMap<u256>
|
|
261
332
|
const addressMap: AddressMap<u256> = reader.readAddressMapU256();
|
|
333
|
+
|
|
334
|
+
// ExtendedAddressMap<u256> (64-byte addresses as keys)
|
|
335
|
+
const extAddressMap: ExtendedAddressMap<u256> = reader.readExtendedAddressMapU256();
|
|
262
336
|
```
|
|
263
337
|
|
|
264
338
|
### Position Management
|
|
@@ -291,6 +365,8 @@ reader.verifyEnd(offset + 32); // Throws Revert if not enough bytes
|
|
|
291
365
|
| `u128`/`i128` | 16 | Big-endian (default) |
|
|
292
366
|
| `u256` | 32 | Big-endian (default) |
|
|
293
367
|
| `Address` | 32 | Raw bytes |
|
|
368
|
+
| `ExtendedAddress` | 64 | 32 bytes tweaked key + 32 bytes ML-DSA key hash |
|
|
369
|
+
| `SchnorrSignature` | 128 | 64 bytes ExtendedAddress + 64 bytes signature |
|
|
294
370
|
| `Selector` | 4 | Big-endian (u32) |
|
|
295
371
|
| `string` | 4 + n | Length prefix (u32 BE) + UTF-8 |
|
|
296
372
|
| `bytes` | 4 + n | Length prefix (u32 BE) + raw |
|