@btc-vision/btc-runtime 1.5.0 → 1.5.2
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/package.json +1 -1
- package/runtime/buffer/BytesReader.ts +72 -1
- package/runtime/buffer/BytesWriter.ts +74 -0
- package/runtime/contracts/DeployableOP_20.ts +6 -5
- package/runtime/contracts/OP_NET.ts +3 -0
- package/runtime/env/BlockchainEnvironment.ts +37 -0
- package/runtime/exports/index.ts +7 -3
- package/runtime/index.ts +12 -9
- package/runtime/memory/{MultiAddressMemoryMap.ts → MapOfMap.ts} +6 -6
- package/runtime/memory/Nested.ts +113 -0
- package/runtime/nested/PointerManager.ts +55 -0
- package/runtime/nested/codecs/AddressCodec.ts +19 -0
- package/runtime/nested/codecs/BooleanCodec.ts +17 -0
- package/runtime/nested/codecs/Ids.ts +10 -0
- package/runtime/nested/codecs/NumericCodec.ts +58 -0
- package/runtime/nested/codecs/StringCodec.ts +24 -0
- package/runtime/nested/codecs/U256Codec.ts +20 -0
- package/runtime/nested/codecs/VariableBytesCodec.ts +134 -0
- package/runtime/nested/interfaces/ICodec.ts +12 -0
- package/runtime/nested/storage/StorageMap.ts +227 -0
- package/runtime/nested/storage/StorageSet.ts +57 -0
- package/runtime/plugins/Plugin.ts +12 -0
- package/runtime/storage/arrays/StoredAddressArray.ts +1 -1
- package/runtime/memory/FastUint8Array.ts +0 -122
- package/runtime/memory/MultiStringMemoryMap.ts +0 -65
- package/runtime/memory/StringMemoryMap.ts +0 -56
- package/runtime/memory/Uint8ArrayMerger.ts +0 -72
- package/runtime/storage/Serializable.ts +0 -113
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { i128, u128, u256 } from '@btc-vision/as-bignum/assembly';
|
|
2
|
+
import { ICodec } from '../interfaces/ICodec';
|
|
3
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
4
|
+
import { BytesReader } from '../../buffer/BytesReader';
|
|
5
|
+
import { idOfI128, idOfU128, idOfU256 } from './Ids';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A generic NumericCodec<T> that handles:
|
|
9
|
+
* - `u256` from @btc-vision/as-bignum (big-endian)
|
|
10
|
+
* - Any built-in integer type (i32, u32, i64, etc.) also stored big-endian
|
|
11
|
+
*/
|
|
12
|
+
export class NumericCodec<T> implements ICodec<T> {
|
|
13
|
+
public encode(value: T): Uint8Array {
|
|
14
|
+
const id = idof<T>();
|
|
15
|
+
switch (id) {
|
|
16
|
+
case idOfU256: {
|
|
17
|
+
// T is `u256`
|
|
18
|
+
const val = changetype<u256>(value);
|
|
19
|
+
return val.toUint8Array(true); // big-endian
|
|
20
|
+
}
|
|
21
|
+
case idOfU128: {
|
|
22
|
+
// T is `u128`
|
|
23
|
+
const val = changetype<u128>(value);
|
|
24
|
+
return val.toUint8Array(true); // big-endian
|
|
25
|
+
}
|
|
26
|
+
case idOfI128: {
|
|
27
|
+
// T is `i128`
|
|
28
|
+
const val = changetype<i128>(value);
|
|
29
|
+
return val.toUint8Array(true); // big-endian
|
|
30
|
+
}
|
|
31
|
+
default: {
|
|
32
|
+
const writer = new BytesWriter(sizeof<T>());
|
|
33
|
+
writer.write<T>(value);
|
|
34
|
+
|
|
35
|
+
return writer.getBuffer();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public decode(buffer: Uint8Array): T {
|
|
41
|
+
const id = idof<T>();
|
|
42
|
+
switch (id) {
|
|
43
|
+
case idOfU256:
|
|
44
|
+
// T is `u256`
|
|
45
|
+
return changetype<T>(u256.fromBytes(buffer, true));
|
|
46
|
+
case idOfU128:
|
|
47
|
+
// T is `u128`
|
|
48
|
+
return changetype<T>(u128.fromBytes(buffer, true));
|
|
49
|
+
case idOfI128:
|
|
50
|
+
// T is `i128`
|
|
51
|
+
return changetype<T>(i128.fromBytes(buffer, true));
|
|
52
|
+
default: {
|
|
53
|
+
const writer = new BytesReader(buffer);
|
|
54
|
+
return writer.read<T>();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ICodec } from '../interfaces/ICodec';
|
|
2
|
+
import { VariableBytesCodec } from './VariableBytesCodec';
|
|
3
|
+
|
|
4
|
+
class IStringCodec implements ICodec<string> {
|
|
5
|
+
public encode(value: string): Uint8Array {
|
|
6
|
+
// Convert string -> UTF8 bytes
|
|
7
|
+
const utf8 = String.UTF8.encode(value, false);
|
|
8
|
+
|
|
9
|
+
// Pass to variable-bytes
|
|
10
|
+
return VariableBytesCodec.encode(Uint8Array.wrap(utf8));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public decode(buffer: Uint8Array): string {
|
|
14
|
+
const raw = VariableBytesCodec.decode(buffer);
|
|
15
|
+
if (raw.length == 0) {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return String.UTF8.decode(raw.buffer, false);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const idOfStringCodec = idof<IStringCodec>();
|
|
24
|
+
export const StringCodec = new IStringCodec();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
2
|
+
import { ICodec } from '../interfaces/ICodec';
|
|
3
|
+
|
|
4
|
+
class _U256Codec implements ICodec<u256> {
|
|
5
|
+
public encode(value: u256): Uint8Array {
|
|
6
|
+
// big-endian
|
|
7
|
+
return value.toUint8Array(true);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public decode(buffer: Uint8Array): u256 {
|
|
11
|
+
if (buffer.length == 0) {
|
|
12
|
+
return u256.Zero;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return u256.fromUint8ArrayBE(buffer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const idOfU256Codec = idof<_U256Codec>();
|
|
20
|
+
export const U256Codec = new _U256Codec();
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { ICodec } from '../interfaces/ICodec';
|
|
2
|
+
import { PointerManager } from '../PointerManager';
|
|
3
|
+
import { Blockchain } from '../../env';
|
|
4
|
+
import { bigEndianAdd } from '../../math/bytes';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A generic codec to store an arbitrary-length byte array ("payload") in chunked 32-byte slots.
|
|
8
|
+
* Layout:
|
|
9
|
+
* - chunk 0 (pointer+0):
|
|
10
|
+
* first 4 bytes = length (u32 big-endian),
|
|
11
|
+
* next 28 bytes = partial data
|
|
12
|
+
* - chunk i>0 (pointer + i): 32 bytes of subsequent data
|
|
13
|
+
*/
|
|
14
|
+
export class IVariableBytesCodec implements ICodec<Uint8Array> {
|
|
15
|
+
public encode(value: Uint8Array): Uint8Array {
|
|
16
|
+
const length = value.length;
|
|
17
|
+
|
|
18
|
+
// Number of bytes that fit in "the first chunk" = 28 (since 4 bytes used by length)
|
|
19
|
+
const firstChunkDataLen = length < 28 ? length : 28;
|
|
20
|
+
let remaining = length - firstChunkDataLen;
|
|
21
|
+
|
|
22
|
+
// If remaining > 0, each chunk is 32 bytes.
|
|
23
|
+
// So total chunks needed = 1 (for first chunk) + ceil(remaining / 32).
|
|
24
|
+
const additionalChunks: u32 = remaining == 0 ? 0 : ((remaining + 32 - 1) / 32);
|
|
25
|
+
const totalChunks: u32 = 1 + additionalChunks;
|
|
26
|
+
|
|
27
|
+
// 1) Allocate `totalChunks` from PointerManager
|
|
28
|
+
const pointerBytes = PointerManager.allocateSlots(totalChunks);
|
|
29
|
+
|
|
30
|
+
// 2) Write chunk 0: length + up to 28 bytes
|
|
31
|
+
const chunk0 = new Uint8Array(32);
|
|
32
|
+
|
|
33
|
+
// store length in big-endian (4 bytes)
|
|
34
|
+
chunk0[0] = <u8>((length >> 24) & 0xff);
|
|
35
|
+
chunk0[1] = <u8>((length >> 16) & 0xff);
|
|
36
|
+
chunk0[2] = <u8>((length >> 8) & 0xff);
|
|
37
|
+
chunk0[3] = <u8>(length & 0xff);
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < firstChunkDataLen; i++) {
|
|
40
|
+
chunk0[4 + i] = value[i];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// store chunk0
|
|
44
|
+
Blockchain.setStorageAt(pointerBytes, chunk0);
|
|
45
|
+
|
|
46
|
+
// 3) Write subsequent chunks
|
|
47
|
+
let offset: u32 = firstChunkDataLen;
|
|
48
|
+
for (let i: u64 = 1; i < u64(totalChunks); i++) {
|
|
49
|
+
const chunk = new Uint8Array(32);
|
|
50
|
+
const chunkSize: u32 = remaining < 32 ? remaining : 32;
|
|
51
|
+
|
|
52
|
+
for (let j: u32 = 0; j < chunkSize; j++) {
|
|
53
|
+
chunk[j] = value[offset + j];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
offset += chunkSize;
|
|
57
|
+
remaining -= chunkSize;
|
|
58
|
+
|
|
59
|
+
// compute pointer + i in big-endian
|
|
60
|
+
const chunkPointer = bigEndianAdd(pointerBytes, i);
|
|
61
|
+
Blockchain.setStorageAt(chunkPointer, chunk);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 4) Return the pointer as the "encoded" data (32 bytes).
|
|
65
|
+
return pointerBytes;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public decode(buffer: Uint8Array): Uint8Array {
|
|
69
|
+
// If buffer is 0 or all zero => means no data
|
|
70
|
+
if (buffer.length == 0 || isAllZero(buffer)) {
|
|
71
|
+
return new Uint8Array(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const pointer = buffer; // the pointer (32 bytes)
|
|
75
|
+
|
|
76
|
+
// chunk0
|
|
77
|
+
const chunk0 = Blockchain.getStorageAt(pointer);
|
|
78
|
+
if (chunk0.length == 0) {
|
|
79
|
+
// No data stored => empty
|
|
80
|
+
return new Uint8Array(32);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// read length from first 4 bytes
|
|
84
|
+
const b0 = <u32>chunk0[0];
|
|
85
|
+
const b1 = <u32>chunk0[1];
|
|
86
|
+
const b2 = <u32>chunk0[2];
|
|
87
|
+
const b3 = <u32>chunk0[3];
|
|
88
|
+
const length = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
|
|
89
|
+
|
|
90
|
+
if (length == 0) {
|
|
91
|
+
return new Uint8Array(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// read the data
|
|
95
|
+
const out = new Uint8Array(length);
|
|
96
|
+
const firstChunkLen = length < 28 ? length : 28;
|
|
97
|
+
|
|
98
|
+
// copy from chunk0
|
|
99
|
+
for (let i: u32 = 0; i < firstChunkLen; i++) {
|
|
100
|
+
out[i] = chunk0[4 + i];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let offset = firstChunkLen;
|
|
104
|
+
let remaining = length - firstChunkLen;
|
|
105
|
+
|
|
106
|
+
// read subsequent chunks
|
|
107
|
+
let chunkIndex: u64 = 1;
|
|
108
|
+
while (remaining > 0) {
|
|
109
|
+
const chunkPointer = bigEndianAdd(pointer, chunkIndex);
|
|
110
|
+
const chunkData = Blockchain.getStorageAt(chunkPointer);
|
|
111
|
+
const chunkSize: u32 = remaining < 32 ? remaining : 32;
|
|
112
|
+
|
|
113
|
+
for (let j: u32 = 0; j < chunkSize; j++) {
|
|
114
|
+
out[offset + j] = chunkData[j];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
offset += chunkSize;
|
|
118
|
+
remaining -= chunkSize;
|
|
119
|
+
chunkIndex++;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isAllZero(arr: Uint8Array): bool {
|
|
127
|
+
for (let i = 0; i < arr.length; i++) {
|
|
128
|
+
if (arr[i] != 0) return false;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const idOfVariableBytes = idof<IVariableBytesCodec>();
|
|
134
|
+
export const VariableBytesCodec = new IVariableBytesCodec();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ICodec<T> {
|
|
2
|
+
/**
|
|
3
|
+
* Encode in-memory `value` into a buffer that will be stored in blockchain storage.
|
|
4
|
+
* Possibly a single 32-byte chunk or a pointer if more is needed.
|
|
5
|
+
*/
|
|
6
|
+
encode(value: T): Uint8Array;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Decode a buffer from storage into an in-memory `T`.
|
|
10
|
+
*/
|
|
11
|
+
decode(buffer: Uint8Array): T;
|
|
12
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Blockchain } from '../../env';
|
|
2
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
3
|
+
import { BytesReader } from '../../buffer/BytesReader';
|
|
4
|
+
import { encodePointerUnknownLength } from '../../math/abi';
|
|
5
|
+
|
|
6
|
+
import { i128, u128, u256 } from '@btc-vision/as-bignum/assembly';
|
|
7
|
+
import { idOfAddress, idOfI128, idOfString, idOfU128, idOfU256, idOfUint8Array } from '../codecs/Ids';
|
|
8
|
+
|
|
9
|
+
import { AddressCodec } from '../codecs/AddressCodec';
|
|
10
|
+
import { BooleanCodec } from '../codecs/BooleanCodec';
|
|
11
|
+
import { StringCodec } from '../codecs/StringCodec';
|
|
12
|
+
import { VariableBytesCodec } from '../codecs/VariableBytesCodec';
|
|
13
|
+
import { U256Codec } from '../codecs/U256Codec';
|
|
14
|
+
|
|
15
|
+
import { Address } from '../../types/Address';
|
|
16
|
+
import { Revert } from '../../types/Revert';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A reflection-based StorageMap<K, V>.
|
|
20
|
+
* - `pointer` => a u16 "namespace"
|
|
21
|
+
* - `set(key, value)` => store bytes
|
|
22
|
+
* - `get(key)` => decode bytes
|
|
23
|
+
*
|
|
24
|
+
* This version uses a chain of `if/else` for each possible type T,
|
|
25
|
+
* so the compiler doesn't complain about mismatched returns.
|
|
26
|
+
*/
|
|
27
|
+
@final
|
|
28
|
+
export class StorageMap<K, V> {
|
|
29
|
+
private readonly pointer: u16;
|
|
30
|
+
|
|
31
|
+
constructor(pointer: u16) {
|
|
32
|
+
this.pointer = pointer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ----------------------------------------------------------
|
|
36
|
+
// PUBLIC API
|
|
37
|
+
// ----------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
public set(key: K, value: V): this {
|
|
40
|
+
const storageKey = this.getStorageKey(key);
|
|
41
|
+
this.storeValue<V>(storageKey, value);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public get(key: K): V {
|
|
46
|
+
const storageKey = this.getStorageKey(key);
|
|
47
|
+
return this.decodeValue<V>(storageKey);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public has(key: K): bool {
|
|
51
|
+
const storageKey = this.getStorageKey(key);
|
|
52
|
+
return Blockchain.hasStorageAt(storageKey);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@unsafe
|
|
56
|
+
public delete(key: K): bool {
|
|
57
|
+
const storageKey = this.getStorageKey(key);
|
|
58
|
+
if (!Blockchain.hasStorageAt(storageKey)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Blockchain.setStorageAt(storageKey, new Uint8Array(0));
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@unsafe
|
|
67
|
+
public clear(): void {
|
|
68
|
+
// Not implemented: we'd need key-tracking to remove them all
|
|
69
|
+
throw new Error('clear() not implemented; no key-tracking logic here.');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ----------------------------------------------------------
|
|
73
|
+
// INTERNAL: Derive the final storage key
|
|
74
|
+
// ----------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
private getStorageKey(k: K): Uint8Array {
|
|
77
|
+
// 1) encode the key
|
|
78
|
+
const keyBytes = this.encodeValue<K>(k);
|
|
79
|
+
|
|
80
|
+
// 2) transform with pointer
|
|
81
|
+
return encodePointerUnknownLength(this.pointer, keyBytes);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ----------------------------------------------------------
|
|
85
|
+
// ENCODE / DECODE
|
|
86
|
+
// ----------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Retrieve the value of type P from the given storage pointer (32 bytes).
|
|
90
|
+
* If it's a variable-length type, we decode from pointer-based approach
|
|
91
|
+
* (like VariableBytesCodec or StringCodec).
|
|
92
|
+
* Otherwise, we get the raw from storage and decode.
|
|
93
|
+
*/
|
|
94
|
+
private decodeValue<P>(pointer: Uint8Array): P {
|
|
95
|
+
// For some types like `Uint8Array` or `string`, we might want
|
|
96
|
+
// to treat the pointer as an allocated "variable" location.
|
|
97
|
+
const typeId = idof<P>();
|
|
98
|
+
|
|
99
|
+
// For variable-length things:
|
|
100
|
+
if (typeId == idOfUint8Array) {
|
|
101
|
+
// decode variable bytes from pointer
|
|
102
|
+
const arr = VariableBytesCodec.decode(pointer);
|
|
103
|
+
return changetype<P>(arr);
|
|
104
|
+
}
|
|
105
|
+
if (typeId == idOfString) {
|
|
106
|
+
const str = StringCodec.decode(pointer);
|
|
107
|
+
return changetype<P>(str);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// For everything else, we read raw from storage
|
|
111
|
+
const raw = Blockchain.getStorageAt(pointer);
|
|
112
|
+
|
|
113
|
+
return this.decodeBytesAsType<P>(raw);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Store a value of type P at the given storage key.
|
|
118
|
+
* - Some types (uint8array, string) -> chunked (pointer-based).
|
|
119
|
+
* - Others -> direct bytes in that slot.
|
|
120
|
+
*/
|
|
121
|
+
private storeValue<P>(storageKey: Uint8Array, value: P): void {
|
|
122
|
+
const raw = this.encodeValue<P>(value);
|
|
123
|
+
Blockchain.setStorageAt(storageKey, raw);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ----------------------------------------------------------
|
|
127
|
+
// decodeBytesAsType: interpret raw bytes as type P
|
|
128
|
+
// ----------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
private decodeBytesAsType<P>(raw: Uint8Array): P {
|
|
131
|
+
// isInteger => built-in numeric types (i32, u32, etc.)
|
|
132
|
+
if (isInteger<P>()) {
|
|
133
|
+
if (raw.length < <i32>sizeof<P>()) {
|
|
134
|
+
return changetype<P>(0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const reader = new BytesReader(raw);
|
|
138
|
+
return reader.read<P>();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// isBoolean => decode with BooleanCodec
|
|
142
|
+
if (isBoolean<P>()) {
|
|
143
|
+
const boolVal = BooleanCodec.decode(raw);
|
|
144
|
+
return changetype<P>(boolVal);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const typeId = idof<P>();
|
|
148
|
+
|
|
149
|
+
// u256
|
|
150
|
+
if (typeId == idOfU256) {
|
|
151
|
+
const decoded = U256Codec.decode(raw);
|
|
152
|
+
return changetype<P>(decoded);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// i128
|
|
156
|
+
if (typeId == idOfI128) {
|
|
157
|
+
const val = i128.fromUint8ArrayBE(raw);
|
|
158
|
+
return changetype<P>(val);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// u128
|
|
162
|
+
if (typeId == idOfU128) {
|
|
163
|
+
const val = u128.fromUint8ArrayBE(raw);
|
|
164
|
+
return changetype<P>(val);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Address
|
|
168
|
+
if (typeId == idOfAddress) {
|
|
169
|
+
const addr = AddressCodec.decode(raw);
|
|
170
|
+
return changetype<P>(addr);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Revert(`Unsupported type ${typeId}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ----------------------------------------------------------
|
|
177
|
+
// encodeValue<P>: convert a value of type P into raw bytes
|
|
178
|
+
// ----------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
private encodeValue<P>(value: P): Uint8Array {
|
|
181
|
+
// built-in integer => write with BytesWriter
|
|
182
|
+
if (isInteger<P>()) {
|
|
183
|
+
const writer = new BytesWriter(sizeof<P>());
|
|
184
|
+
writer.write<P>(value);
|
|
185
|
+
return writer.getBuffer();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// bool => BooleanCodec
|
|
189
|
+
if (isBoolean<P>()) {
|
|
190
|
+
return BooleanCodec.encode(changetype<bool>(value));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check reflection for bigger types
|
|
194
|
+
if (value instanceof Uint8Array) {
|
|
195
|
+
return VariableBytesCodec.encode(value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (isString<P>()) {
|
|
199
|
+
const strVal = changetype<string>(value);
|
|
200
|
+
return StringCodec.encode(strVal);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (value instanceof u256) {
|
|
204
|
+
return U256Codec.encode(changetype<u256>(value));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (value instanceof u128) {
|
|
208
|
+
// store big-endian
|
|
209
|
+
const uval = changetype<u128>(value);
|
|
210
|
+
return uval.toUint8Array(true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (value instanceof i128) {
|
|
214
|
+
const ival = changetype<i128>(value);
|
|
215
|
+
return ival.toUint8Array(true);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (value instanceof Address) {
|
|
219
|
+
return AddressCodec.encode(changetype<Address>(value));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If nested map => handle in a custom branch (not shown here):
|
|
223
|
+
// if (value instanceof StorageMap<...>) { ... }
|
|
224
|
+
|
|
225
|
+
throw new Revert('encodeValue: Unsupported type');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ICodec } from '../interfaces/ICodec';
|
|
2
|
+
import { BytesWriter } from '../../buffer/BytesWriter';
|
|
3
|
+
import { encodePointerUnknownLength } from '../../math/abi';
|
|
4
|
+
import { Blockchain } from '../../env';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class StorageSet<T> {
|
|
8
|
+
private readonly pointer: u16;
|
|
9
|
+
private readonly parentKey: Uint8Array;
|
|
10
|
+
|
|
11
|
+
private elementCodec: ICodec<T>;
|
|
12
|
+
|
|
13
|
+
constructor(pointer: u16, parentKey: Uint8Array, elementCodec: ICodec<T>) {
|
|
14
|
+
this.pointer = pointer;
|
|
15
|
+
this.parentKey = parentKey;
|
|
16
|
+
this.elementCodec = elementCodec;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public add(value: T): void {
|
|
20
|
+
const storageKey = this.getStorageKey(value);
|
|
21
|
+
|
|
22
|
+
// A 1-byte array with a single 0x01 is enough to mark presence
|
|
23
|
+
const flag = new Uint8Array(1);
|
|
24
|
+
flag[31] = 1;
|
|
25
|
+
|
|
26
|
+
Blockchain.setStorageAt(storageKey, flag);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public has(value: T): bool {
|
|
30
|
+
const storageKey = this.getStorageKey(value);
|
|
31
|
+
return Blockchain.hasStorageAt(storageKey);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public delete(value: T): bool {
|
|
35
|
+
const storageKey = this.getStorageKey(value);
|
|
36
|
+
if (!Blockchain.hasStorageAt(storageKey)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Blockchain.setStorageAt(storageKey, new Uint8Array(32));
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@unsafe
|
|
45
|
+
public clear(): void {
|
|
46
|
+
throw new Error('clear() not implemented.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private getStorageKey(value: T): Uint8Array {
|
|
50
|
+
const encoded = this.elementCodec.encode(value);
|
|
51
|
+
const writer = new BytesWriter(this.parentKey.length + encoded.length);
|
|
52
|
+
writer.writeBytes(this.parentKey);
|
|
53
|
+
writer.writeBytes(encoded);
|
|
54
|
+
|
|
55
|
+
return encodePointerUnknownLength(this.pointer, writer.getBuffer());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -35,7 +35,7 @@ export class StoredAddressArray {
|
|
|
35
35
|
constructor(public pointer: u16, public subPointer: Uint8Array) {
|
|
36
36
|
assert(
|
|
37
37
|
subPointer.length <= 30,
|
|
38
|
-
`You must pass a 30 bytes sub-pointer. (AddressArray, got ${
|
|
38
|
+
`You must pass a 30 bytes sub-pointer. (AddressArray, got ${subPointer.length})`,
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
// Construct base pointer as a 32-byte array
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { Revert } from '../types/Revert';
|
|
2
|
-
import { ArrayBuffer } from 'arraybuffer';
|
|
3
|
-
|
|
4
|
-
export class FastUint8Array {
|
|
5
|
-
public length: i32;
|
|
6
|
-
private ptr: usize;
|
|
7
|
-
|
|
8
|
-
[key: number]: u8;
|
|
9
|
-
|
|
10
|
-
constructor(length: i32) {
|
|
11
|
-
if (length < 0) {
|
|
12
|
-
throw new Revert('Negative length in FastUint8Array constructor');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
this.length = length;
|
|
16
|
-
this.ptr = __alloc(<usize>length);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public static fromUint8Array(arr: Uint8Array): FastUint8Array {
|
|
20
|
-
const buffer = new FastUint8Array(arr.length);
|
|
21
|
-
for (let i: u32 = 0; i < arr.length; i++) {
|
|
22
|
-
buffer[i] = arr[i];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return buffer;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
toArrayBuffer(): ArrayBuffer {
|
|
29
|
-
const arr = new Uint8Array(this.length);
|
|
30
|
-
for (let i: u32 = 0; i < arr.length; i++) {
|
|
31
|
-
arr[i] = this[i];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return arr.buffer;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Fills the buffer with a specified byte value.
|
|
39
|
-
*/
|
|
40
|
-
fill(value: u8): void {
|
|
41
|
-
memory.fill(this.ptr, value, <usize>this.length);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Copies bytes from another FastUint8Array into this one, starting at `destOffset`.
|
|
46
|
-
*/
|
|
47
|
-
copyFrom(source: FastUint8Array, destOffset: i32 = 0): void {
|
|
48
|
-
if (destOffset < 0) {
|
|
49
|
-
throw new Revert(`Negative destOffset in copyFrom: ${destOffset}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let maxBytes = source.length;
|
|
53
|
-
const destRemaining = this.length - destOffset;
|
|
54
|
-
if (destRemaining < maxBytes) {
|
|
55
|
-
maxBytes = destRemaining;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (maxBytes <= 0) return;
|
|
59
|
-
|
|
60
|
-
memory.copy(
|
|
61
|
-
this.ptr + <usize>destOffset,
|
|
62
|
-
source.ptr,
|
|
63
|
-
<usize>maxBytes,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
free(): void {
|
|
68
|
-
if (this.ptr != 0) {
|
|
69
|
-
__free(this.ptr);
|
|
70
|
-
this.ptr = 0;
|
|
71
|
-
this.length = 0;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Safe read operator: buffer[i]
|
|
77
|
-
* Throws on out-of-bounds.
|
|
78
|
-
*/
|
|
79
|
-
@operator('[]')
|
|
80
|
-
private __get(index: i32): u8 {
|
|
81
|
-
if (<u32>index >= <u32>this.length) {
|
|
82
|
-
throw new Revert('Index out of range in __get');
|
|
83
|
-
}
|
|
84
|
-
return load<u8>(this.ptr + <usize>index);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Safe write operator: buffer[i] = value
|
|
89
|
-
* Throws on out-of-bounds.
|
|
90
|
-
*/
|
|
91
|
-
@operator('[]=')
|
|
92
|
-
private __set(index: i32, value: u8): void {
|
|
93
|
-
if (<u32>index >= <u32>this.length) {
|
|
94
|
-
throw new Revert('Index out of range in __set');
|
|
95
|
-
}
|
|
96
|
-
store<u8>(this.ptr + <usize>index, value);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Braces read operator: buffer{i}
|
|
101
|
-
* Now also throws on out-of-bounds for safety.
|
|
102
|
-
*/
|
|
103
|
-
@operator('{}')
|
|
104
|
-
private __uget(index: i32): u8 {
|
|
105
|
-
if (<u32>index >= <u32>this.length) {
|
|
106
|
-
throw new Revert('Index out of range in __uget');
|
|
107
|
-
}
|
|
108
|
-
return load<u8>(this.ptr + <usize>index);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Braces write operator: buffer{i} = value
|
|
113
|
-
* Now also throws on out-of-bounds.
|
|
114
|
-
*/
|
|
115
|
-
@operator('{}=')
|
|
116
|
-
private __uset(index: i32, value: u8): void {
|
|
117
|
-
if (<u32>index >= <u32>this.length) {
|
|
118
|
-
throw new Revert('Index out of range in __uset');
|
|
119
|
-
}
|
|
120
|
-
store<u8>(this.ptr + <usize>index, value);
|
|
121
|
-
}
|
|
122
|
-
}
|