@btc-vision/btc-runtime 1.10.11 → 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.
@@ -21,38 +21,7 @@ import {
21
21
  } from '@btc-vision/btc-runtime/runtime';
22
22
  ```
23
23
 
24
- ## CRITICAL: Map Implementation Warning
25
-
26
- > **DO NOT USE AssemblyScript's Built-in Map**
27
- >
28
- > When creating custom map implementations or extending map functionality, you **MUST** use the Map class from `@btc-vision/btc-runtime/runtime`, NOT the built-in AssemblyScript Map.
29
- >
30
- > **Why the AssemblyScript Map is broken for blockchain:**
31
- > - NOT optimized for blockchain storage patterns
32
- > - Does NOT handle Uint8Array buffers as keys correctly
33
- > - Does NOT work properly with Address key comparisons
34
- > - Will cause silent data corruption or key collisions
35
- >
36
- > **CORRECT:**
37
- > ```typescript
38
- > import { Map } from '@btc-vision/btc-runtime/runtime';
39
- >
40
- > export class MyCustomMap<V> extends Map<Address, V> {
41
- > // Your implementation
42
- > }
43
- > ```
44
- >
45
- > **WRONG:**
46
- > ```typescript
47
- > // DO NOT DO THIS - will break!
48
- > const map = new Map<Uint8Array, u256>(); // AssemblyScript Map
49
- > ```
50
- >
51
- > The btc-runtime Map is specifically designed to:
52
- > - Handle Address and Uint8Array key comparisons correctly
53
- > - Optimize for blockchain storage access patterns
54
- > - Support proper serialization for persistent storage
55
- > - Prevent key collisions with custom equality logic
24
+ > **Important:** Do NOT use AssemblyScript's built-in Map for blockchain storage. See [CRITICAL: Map Implementation Warning](../storage/stored-maps.md#critical-map-implementation-warning) for details.
56
25
 
57
26
  ## Storage Type Hierarchy
58
27
 
@@ -609,87 +578,9 @@ class Nested<T> {
609
578
  }
610
579
  ```
611
580
 
612
- ```typescript
613
- import { MapOfMap, Nested } from '@btc-vision/btc-runtime/runtime';
614
-
615
- // mapping(address => mapping(address => uint256))
616
- private allowancesPointer: u16 = Blockchain.nextPointer;
617
- private allowances: MapOfMap<u256>;
618
-
619
- constructor() {
620
- super();
621
- this.allowances = new MapOfMap<u256>(this.allowancesPointer);
622
- }
623
-
624
- // Get nested value - two-step process
625
- protected getAllowance(owner: Address, spender: Address): u256 {
626
- const ownerMap = this.allowances.get(owner); // Returns Nested<u256>
627
- return ownerMap.get(spender); // Returns u256
628
- }
629
-
630
- // Set nested value - get, modify, commit back
631
- protected setAllowance(owner: Address, spender: Address, amount: u256): void {
632
- const ownerMap = this.allowances.get(owner); // Get the nested map
633
- ownerMap.set(spender, amount); // Modify it
634
- this.allowances.set(owner, ownerMap); // Commit back
635
- }
636
- ```
637
-
638
581
  > **Important:** `MapOfMap.get(key)` returns a `Nested<T>` object, not the final value. You must call `.get()` on the nested object to retrieve the actual value.
639
582
 
640
- The following diagram shows the two-level nested storage pattern:
641
-
642
- ```mermaid
643
- flowchart LR
644
- subgraph "Read Path - Two-Level Mapping"
645
- A[MapOfMap allowances] --> B[First Level:<br/>Owner Address]
646
- B --> C[allowances.get owner]
647
- C --> D[Returns Nested<br/>u256 Map]
648
- D --> E[Second Level:<br/>Spender Address]
649
- E --> F[nested.get spender]
650
- F --> G[Returns u256<br/>allowance]
651
- end
652
-
653
- subgraph "Write Path - Set Allowance"
654
- H[Set Allowance] --> I[Get owner's<br/>nested map]
655
- I --> J[nested.set<br/>spender, amount]
656
- J --> K[Commit back<br/>to MapOfMap]
657
- K --> L[allowances.set<br/>owner, nested]
658
- end
659
- ```
660
-
661
- The following sequence diagram shows the nested allowance operations:
662
-
663
- ```mermaid
664
- sequenceDiagram
665
- participant Contract
666
- participant MapOfMap as allowances MapOfMap
667
- participant Nested as Nested Map
668
- participant BC as Blockchain
669
-
670
- Note over Contract,BC: Get Nested Allowance
671
-
672
- Contract->>MapOfMap: allowances.get(owner)
673
- MapOfMap->>MapOfMap: Compute owner's map hash
674
- MapOfMap->>Contract: Return Nested~u256~ map
675
-
676
- Contract->>Nested: nested.get(spender)
677
- Nested->>Nested: Compute spender hash within owner's space
678
- Nested->>BC: getStorageAt(combined_hash)
679
- BC->>Nested: Return allowance bytes
680
- Nested->>Contract: Return u256 allowance
681
-
682
- Note over Contract,BC: Set Nested Allowance
683
-
684
- Contract->>MapOfMap: allowances.get(owner)
685
- MapOfMap->>Contract: Return Nested map
686
-
687
- Contract->>Nested: nested.set(spender, new_amount)
688
- Nested->>BC: setStorageAt(combined_hash, new_amount)
689
-
690
- Contract->>MapOfMap: allowances.set(owner, nested)
691
- Note over Contract: Commit nested map changes
692
- ```
583
+ See [Stored Maps - MapOfMap](../storage/stored-maps.md#mapofmap) for detailed usage patterns and diagrams.
693
584
 
694
585
  ## Storage Patterns
695
586
 
@@ -76,137 +76,34 @@ See [SafeMath API](../api-reference/safe-math.md) for complete reference.
76
76
 
77
77
  ## Reentrancy Protection
78
78
 
79
- ### The Problem
80
-
81
- Reentrancy attacks occur when a contract calls back into itself before completing:
82
-
83
- ```
84
- 1. User calls withdraw()
85
- 2. Contract sends funds to User
86
- 3. User's receive function calls withdraw() again
87
- 4. Contract sends funds again (before updating balance)
88
- 5. Repeat until drained
89
- ```
90
-
91
- ### Reentrancy Attack Sequence
92
-
93
- ```mermaid
94
- sequenceDiagram
95
- participant Attacker
96
- participant VulnerableContract
97
- participant AttackerContract
98
-
99
- Attacker->>VulnerableContract: withdraw()
100
- activate VulnerableContract
101
-
102
- Note over VulnerableContract: balance = 100<br/>Check balance
103
-
104
- VulnerableContract->>AttackerContract: Transfer 100 tokens
105
- activate AttackerContract
106
-
107
- Note over AttackerContract: Receive callback triggered
79
+ Reentrancy attacks occur when a contract calls back into itself before completing, allowing attackers to drain funds by repeatedly calling withdraw before the balance is updated.
108
80
 
109
- AttackerContract->>VulnerableContract: withdraw() [RE-ENTER!]
110
- activate VulnerableContract
111
-
112
- Note over VulnerableContract: balance still = 100<br/>Check balance [BUG!]
113
-
114
- VulnerableContract->>AttackerContract: Transfer 100 tokens AGAIN
115
- deactivate VulnerableContract
116
-
117
- AttackerContract-->>VulnerableContract: Return
118
- deactivate AttackerContract
119
-
120
- Note over VulnerableContract: balance = 0<br/>(Too late!)
121
-
122
- VulnerableContract-->>Attacker: Complete
123
- deactivate VulnerableContract
124
-
125
- Note over Attacker: Stole 200 tokens!<br/>Expected: 100
126
- ```
127
-
128
- ### The Solution: ReentrancyGuard
81
+ **Solution:** Extend `ReentrancyGuard` to automatically protect all methods:
129
82
 
130
83
  ```typescript
131
84
  import { ReentrancyGuard, ReentrancyLevel } from '@btc-vision/btc-runtime/runtime';
132
85
 
133
86
  @final
134
87
  export class MyContract extends ReentrancyGuard {
135
- // Override the reentrancy level (STANDARD is the default)
136
88
  protected override readonly reentrancyLevel: ReentrancyLevel = ReentrancyLevel.STANDARD;
137
89
 
138
- public constructor() {
139
- super();
140
- }
141
-
142
90
  @method()
143
91
  public withdraw(calldata: Calldata): BytesWriter {
144
- // ReentrancyGuard automatically protects this method
92
+ // Protected automatically - re-entry attempts will revert
145
93
  const amount = this.balances.get(Blockchain.tx.sender);
146
-
147
- // Even if external call tries to re-enter, it will fail
148
94
  this.balances.set(Blockchain.tx.sender, u256.Zero);
149
95
  // ... transfer funds ...
150
-
151
96
  return new BytesWriter(0);
152
97
  }
153
98
  }
154
99
  ```
155
100
 
156
- ### ReentrancyGuard Mechanism Flow
157
-
158
- ```mermaid
159
- ---
160
- config:
161
- theme: dark
162
- ---
163
- flowchart LR
164
- Start["Method Called"] --> Check{"Guard Active?"}
165
- Check -->|Yes| Revert["REVERT"]
166
- Check -->|No| SetGuard["Set Guard Flag"]
167
- SetGuard --> Execute["Execute Logic"]
168
- Execute --> External{"External Call?"}
169
- External -->|Yes| Call["Call External"]
170
- Call --> Detect{"Re-enter?"}
171
- Detect -->|Yes| Block["REVERT: Blocked"]
172
- Detect -->|No| Clear["Clear Guard"]
173
- External -->|No| Clear
174
- Clear --> Success["Transaction Success"]
175
- ```
176
-
177
- ### Guard Modes
178
-
179
- | Mode | Description | Use Case |
180
- |------|-------------|----------|
181
- | `ReentrancyLevel.STANDARD` | Uses boolean lock, strict mutual exclusion | Default for most contracts |
182
- | `ReentrancyLevel.CALLBACK` | Uses depth counter, still blocks reentrancy | Depth tracking use cases |
183
-
184
- ```typescript
185
- // STANDARD: No re-entry allowed, uses boolean lock (default)
186
- protected override readonly reentrancyLevel: ReentrancyLevel = ReentrancyLevel.STANDARD;
187
-
188
- // CALLBACK: No re-entry allowed, uses depth counter for tracking
189
- protected override readonly reentrancyLevel: ReentrancyLevel = ReentrancyLevel.CALLBACK;
190
- ```
191
-
192
- ### Guard Mode Decision Tree
193
-
194
- ```mermaid
195
- ---
196
- config:
197
- theme: dark
198
- ---
199
- flowchart LR
200
- Start{"External Calls?"}
201
- Start -->|No| None["No Guard Needed"]
202
- Start -->|Yes| Tracking{"Need Depth Tracking?"}
203
- Tracking -->|No| Standard["STANDARD Mode"]
204
- Tracking -->|Yes| CallbackMode["CALLBACK Mode"]
205
- ```
206
-
207
- Note: Both modes block reentrancy. STANDARD uses a boolean lock; CALLBACK uses a depth counter.
101
+ | Mode | Description |
102
+ |------|-------------|
103
+ | `ReentrancyLevel.STANDARD` | Boolean lock (default) |
104
+ | `ReentrancyLevel.CALLBACK` | Depth counter for tracking |
208
105
 
209
- See [ReentrancyGuard](../contracts/reentrancy-guard.md) for detailed usage.
106
+ Both modes block reentrancy. See [ReentrancyGuard](../contracts/reentrancy-guard.md) for detailed explanation, attack diagrams, and advanced usage.
210
107
 
211
108
  ## Access Control
212
109
 
@@ -195,38 +195,7 @@ flowchart LR
195
195
  end
196
196
  ```
197
197
 
198
- ## CRITICAL: Map Implementation Warning
199
-
200
- > **DO NOT USE AssemblyScript's Built-in Map**
201
- >
202
- > When creating custom map implementations or extending map functionality, you **MUST** use the Map class from `@btc-vision/btc-runtime/runtime`, NOT the built-in AssemblyScript Map.
203
- >
204
- > **Why the AssemblyScript Map is broken for blockchain:**
205
- > - NOT optimized for blockchain storage patterns
206
- > - Does NOT handle Uint8Array buffers as keys correctly
207
- > - Does NOT work properly with Address key comparisons
208
- > - Will cause silent data corruption or key collisions
209
- >
210
- > **CORRECT:**
211
- > ```typescript
212
- > import { Map } from '@btc-vision/btc-runtime/runtime';
213
- >
214
- > export class MyCustomMap<V> extends Map<Address, V> {
215
- > // Your implementation
216
- > }
217
- > ```
218
- >
219
- > **WRONG:**
220
- > ```typescript
221
- > // DO NOT DO THIS - will break!
222
- > const map = new Map<Uint8Array, u256>(); // AssemblyScript Map
223
- > ```
224
- >
225
- > The btc-runtime Map is specifically designed to:
226
- > - Handle Address and Uint8Array key comparisons correctly
227
- > - Optimize for blockchain storage access patterns
228
- > - Support proper serialization for persistent storage
229
- > - Prevent key collisions with custom equality logic
198
+ > **Important:** Do NOT use AssemblyScript's built-in Map for blockchain storage. See [CRITICAL: Map Implementation Warning](../storage/stored-maps.md#critical-map-implementation-warning) for details.
230
199
 
231
200
  ## Pointer Allocation
232
201
 
@@ -906,249 +906,19 @@ public reserve(calldata: Calldata): BytesWriter { }
906
906
  public getSaleInfo(_calldata: Calldata): BytesWriter { }
907
907
  ```
908
908
 
909
- ## Solidity Equivalent
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
- | **Inheritance** | `contract NFT is ERC721, Ownable` | `class NFT extends OP721` |
1097
- | **Constructor** | `constructor() ERC721("Name", "SYM")` | `onDeployment()` + `this.instantiate(...)` |
1098
- | **Enum Definition** | `enum SalePhase { INACTIVE, WHITELIST }` | `const PHASE_INACTIVE: u8 = 0` |
1099
- | **Mint** | `_safeMint(to, tokenId)` | `this._mint(to, tokenId)` |
1100
- | **Token Counter** | `uint256 private nextTokenId` | `StoredU256` with pointer |
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
- @method({ name: 'quantity', type: ABIDataTypes.UINT256 })
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 Verification Comparison
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
- ### Full Comparison Example
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
- ### Full Example Comparison
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
- ### Full Example Comparison
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 by combining the pointer and subPointer:
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
- ### Full Example Comparison
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
 
@@ -236,52 +236,9 @@ const approxLn = SafeMath.approxLog(value); // Approximate ln(x) * 1e6
236
236
  const bits = SafeMath.bitLength256(value); // Returns u32
237
237
  ```
238
238
 
239
- ## Operations for Other Integer Sizes
239
+ ## Other Integer Sizes
240
240
 
241
- SafeMath provides variants for u128 and u64 for more efficient operations when u256 is not needed:
242
-
243
- ### u128 Operations
244
-
245
- ```typescript
246
- import { u128 } from '@btc-vision/as-bignum/assembly';
247
-
248
- const a = u128.fromU64(100);
249
- const b = u128.fromU64(50);
250
-
251
- SafeMath.add128(a, b); // Addition
252
- SafeMath.sub128(a, b); // Subtraction
253
- SafeMath.mul128(a, b); // Multiplication
254
- SafeMath.div128(a, b); // Division
255
- SafeMath.min128(a, b); // Minimum
256
- SafeMath.max128(a, b); // Maximum
257
- SafeMath.shl128(a, 10); // Left shift
258
- ```
259
-
260
- ### u64 Operations
261
-
262
- ```typescript
263
- const x: u64 = 100;
264
- const y: u64 = 50;
265
-
266
- SafeMath.add64(x, y); // Addition
267
- SafeMath.sub64(x, y); // Subtraction
268
- SafeMath.mul64(x, y); // Multiplication
269
- SafeMath.div64(x, y); // Division
270
- SafeMath.min64(x, y); // Minimum
271
- SafeMath.max64(x, y); // Maximum
272
- ```
273
-
274
- ## Solidity Comparison
275
-
276
- | Solidity | SafeMath |
277
- |----------|----------|
278
- | `a + b` (checked) | `SafeMath.add(a, b)` |
279
- | `a - b` (checked) | `SafeMath.sub(a, b)` |
280
- | `a * b` (checked) | `SafeMath.mul(a, b)` |
281
- | `a / b` | `SafeMath.div(a, b)` |
282
- | `a % b` | `SafeMath.mod(a, b)` |
283
- | `a ** b` | `SafeMath.pow(a, b)` |
284
- | `mulmod(a, b, n)` | `SafeMath.mulmod(a, b, n)` |
241
+ SafeMath also provides u128 and u64 variants. See [SafeMath API Reference](../api-reference/safe-math.md#u128-operations) for the complete list.
285
242
 
286
243
  ## SafeMathI128
287
244
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/btc-runtime",
3
- "version": "1.10.11",
3
+ "version": "1.10.12",
4
4
  "description": "Bitcoin L1 Smart Contract Runtime for OPNet. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.",
5
5
  "main": "btc/index.ts",
6
6
  "types": "btc/index.ts",
@@ -1249,7 +1249,91 @@ export class SafeMath {
1249
1249
  return atanhSum << 1; // Multiply by 2 using bit shift
1250
1250
  }
1251
1251
 
1252
- // ==================== Internal Helper Functions ====================
1252
+ /**
1253
+ * Calculate ln(a/b) with precision, avoiding bit-length mismatch issues.
1254
+ * Returns the result scaled by 1e6 (i.e., ln(a/b) * 1,000,000)
1255
+ *
1256
+ * This function correctly handles the case where a and b have different
1257
+ * bit lengths, which would cause incorrect results if computing
1258
+ * preciseLog(a) - preciseLog(b) directly.
1259
+ *
1260
+ * @param a - Numerator (must be > 0)
1261
+ * @param b - Denominator (must be > 0)
1262
+ * @returns ln(a/b) * 1,000,000
1263
+ */
1264
+ public static preciseLogRatio(a: u256, b: u256): u256 {
1265
+ if (a.isZero() || b.isZero()) {
1266
+ return u256.Zero;
1267
+ }
1268
+
1269
+ // If a == b, ln(1) = 0
1270
+ if (u256.eq(a, b)) {
1271
+ return u256.Zero;
1272
+ }
1273
+
1274
+ const SCALE = u256.fromU64(1_000_000);
1275
+ const LN2_SCALED = u256.fromU64(693147); // ln(2) * 1e6
1276
+
1277
+ // Compute ratio = a / b with scaling to preserve precision
1278
+ // scaledRatio = (a * SCALE) / b represents (a/b) * SCALE
1279
+ const scaledRatio = SafeMath.div(SafeMath.mul(a, SCALE), b);
1280
+
1281
+ if (scaledRatio.isZero()) {
1282
+ // a/b is very small, return negative (but we only handle positive ln)
1283
+ // For a < b, ln(a/b) < 0. Return 0 or handle separately if needed.
1284
+ return u256.Zero;
1285
+ }
1286
+
1287
+ // If scaledRatio == SCALE, then a/b == 1, ln = 0
1288
+ if (u256.eq(scaledRatio, SCALE)) {
1289
+ return u256.Zero;
1290
+ }
1291
+
1292
+ // If scaledRatio < SCALE (i.e., a < b), ln is negative
1293
+ // We only return positive values, so return 0 for this case
1294
+ if (u256.lt(scaledRatio, SCALE)) {
1295
+ return u256.Zero;
1296
+ }
1297
+
1298
+ // Now scaledRatio > SCALE, meaning a/b > 1, so ln(a/b) > 0
1299
+ // We want to compute ln(scaledRatio / SCALE) = ln(scaledRatio) - ln(SCALE)
1300
+ // But we do this correctly by computing ln(1 + (scaledRatio - SCALE) / SCALE)
1301
+
1302
+ // fraction = (scaledRatio - SCALE) / SCALE = (a/b - 1)
1303
+ // fractionScaled = scaledRatio - SCALE represents fraction * SCALE
1304
+ const fractionScaled = SafeMath.sub(scaledRatio, SCALE);
1305
+
1306
+ // For small fractions (a/b < 2, i.e., fractionScaled < SCALE), use Taylor series
1307
+ // Note: Use strict less-than to ensure ratio = 2 uses the k*ln(2) decomposition
1308
+ // This ensures continuity at the boundary - both paths give ln(2) for ratio = 2
1309
+ if (u256.lt(fractionScaled, SCALE)) {
1310
+ return this.calculateLnOnePlusFraction(fractionScaled, SCALE);
1311
+ }
1312
+
1313
+ // For ratios >= 2, use the decomposition:
1314
+ // ln(a/b) = k * ln(2) + ln(normalized)
1315
+ // where normalized = (a/b) / 2^k is in range [1, 2)
1316
+
1317
+ // Find k such that scaledRatio / 2^k is in [SCALE, 2*SCALE)
1318
+ let temp = scaledRatio;
1319
+ let k: u32 = 0;
1320
+ const twoScale = SafeMath.mul(SCALE, u256.fromU32(2));
1321
+
1322
+ while (u256.ge(temp, twoScale)) {
1323
+ temp = SafeMath.shr(temp, 1);
1324
+ k++;
1325
+ }
1326
+
1327
+ // Now temp is in [SCALE, 2*SCALE), representing a value in [1, 2)
1328
+ // ln(a/b) = k * ln(2) + ln(temp/SCALE)
1329
+ // temp/SCALE is in [1, 2), so (temp - SCALE)/SCALE is in [0, 1)
1330
+
1331
+ const normalizedFraction = SafeMath.sub(temp, SCALE);
1332
+ const lnNormalized = this.calculateLnOnePlusFraction(normalizedFraction, SCALE);
1333
+ const base = SafeMath.mul(u256.fromU32(k), LN2_SCALED);
1334
+
1335
+ return SafeMath.add(base, lnNormalized);
1336
+ }
1253
1337
 
1254
1338
  /**
1255
1339
  * @internal
@@ -1270,4 +1354,40 @@ export class SafeMath {
1270
1354
  const mMinusX = u256.sub(m, x);
1271
1355
  return u256.ge(x, mMinusX) ? u256.sub(x, mMinusX) : u256.add(x, x);
1272
1356
  }
1357
+
1358
+ // ==================== Internal Helper Functions ====================
1359
+
1360
+ /**
1361
+ * Helper function: Calculate ln(1 + x) where x is provided as xScaled = x * scale
1362
+ * Returns the result scaled by scale (i.e., ln(1+x) * scale)
1363
+ *
1364
+ * Uses Taylor series: ln(1+x) ≈ x - x²/2 + x³/3 - x⁴/4 + x⁵/5
1365
+ * Valid for 0 <= x <= 1 (i.e., xScaled <= scale)
1366
+ */
1367
+ private static calculateLnOnePlusFraction(xScaled: u256, scale: u256): u256 {
1368
+ if (xScaled.isZero()) {
1369
+ return u256.Zero;
1370
+ }
1371
+
1372
+ // x² scaled
1373
+ const x2 = SafeMath.div(SafeMath.mul(xScaled, xScaled), scale);
1374
+
1375
+ // x³ scaled
1376
+ const x3 = SafeMath.div(SafeMath.mul(x2, xScaled), scale);
1377
+
1378
+ // x⁴ scaled
1379
+ const x4 = SafeMath.div(SafeMath.mul(x3, xScaled), scale);
1380
+
1381
+ // x⁵ scaled
1382
+ const x5 = SafeMath.div(SafeMath.mul(x4, xScaled), scale);
1383
+
1384
+ // ln(1+x) ≈ x - x²/2 + x³/3 - x⁴/4 + x⁵/5
1385
+ let result = xScaled;
1386
+ result = SafeMath.sub(result, SafeMath.div(x2, u256.fromU32(2)));
1387
+ result = SafeMath.add(result, SafeMath.div(x3, u256.fromU32(3)));
1388
+ result = SafeMath.sub(result, SafeMath.div(x4, u256.fromU32(4)));
1389
+ result = SafeMath.add(result, SafeMath.div(x5, u256.fromU32(5)));
1390
+
1391
+ return result;
1392
+ }
1273
1393
  }