@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.
- package/docs/api-reference/storage.md +2 -111
- 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/safe-math.md +2 -45
- package/package.json +1 -1
- package/runtime/types/SafeMath.ts +121 -1
|
@@ -21,38 +21,7 @@ import {
|
|
|
21
21
|
} from '@btc-vision/btc-runtime/runtime';
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
package/docs/types/safe-math.md
CHANGED
|
@@ -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
|
-
##
|
|
239
|
+
## Other Integer Sizes
|
|
240
240
|
|
|
241
|
-
SafeMath provides
|
|
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.
|
|
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
|
-
|
|
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
|
}
|