UNPKG

@btc-vision/btc-runtime

Version:

Bitcoin Smart Contract Runtime

134 lines (107 loc) 4.48 kB
import { ICodec } from '../interfaces/ICodec'; import { PointerManager } from '../PointerManager'; import { Blockchain } from '../../env'; import { bigEndianAdd } from '../../math/bytes'; /** * A generic codec to store an arbitrary-length byte array ("payload") in chunked 32-byte slots. * Layout: * - chunk 0 (pointer+0): * first 4 bytes = length (u32 big-endian), * next 28 bytes = partial data * - chunk i>0 (pointer + i): 32 bytes of subsequent data */ export class IVariableBytesCodec implements ICodec<Uint8Array> { public encode(value: Uint8Array): Uint8Array { const length = value.length; // Number of bytes that fit in "the first chunk" = 28 (since 4 bytes used by length) const firstChunkDataLen = length < 28 ? length : 28; let remaining = length - firstChunkDataLen; // If remaining > 0, each chunk is 32 bytes. // So total chunks needed = 1 (for first chunk) + ceil(remaining / 32). const additionalChunks: u32 = remaining == 0 ? 0 : ((remaining + 32 - 1) / 32); const totalChunks: u32 = 1 + additionalChunks; // 1) Allocate `totalChunks` from PointerManager const pointerBytes = PointerManager.allocateSlots(totalChunks); // 2) Write chunk 0: length + up to 28 bytes const chunk0 = new Uint8Array(32); // store length in big-endian (4 bytes) chunk0[0] = <u8>((length >> 24) & 0xff); chunk0[1] = <u8>((length >> 16) & 0xff); chunk0[2] = <u8>((length >> 8) & 0xff); chunk0[3] = <u8>(length & 0xff); for (let i = 0; i < firstChunkDataLen; i++) { chunk0[4 + i] = value[i]; } // store chunk0 Blockchain.setStorageAt(pointerBytes, chunk0); // 3) Write subsequent chunks let offset: u32 = firstChunkDataLen; for (let i: u64 = 1; i < u64(totalChunks); i++) { const chunk = new Uint8Array(32); const chunkSize: u32 = remaining < 32 ? remaining : 32; for (let j: u32 = 0; j < chunkSize; j++) { chunk[j] = value[offset + j]; } offset += chunkSize; remaining -= chunkSize; // compute pointer + i in big-endian const chunkPointer = bigEndianAdd(pointerBytes, i); Blockchain.setStorageAt(chunkPointer, chunk); } // 4) Return the pointer as the "encoded" data (32 bytes). return pointerBytes; } public decode(buffer: Uint8Array): Uint8Array { // If buffer is 0 or all zero => means no data if (buffer.length == 0 || isAllZero(buffer)) { return new Uint8Array(0); } const pointer = buffer; // the pointer (32 bytes) // chunk0 const chunk0 = Blockchain.getStorageAt(pointer); if (chunk0.length == 0) { // No data stored => empty return new Uint8Array(32); } // read length from first 4 bytes const b0 = <u32>chunk0[0]; const b1 = <u32>chunk0[1]; const b2 = <u32>chunk0[2]; const b3 = <u32>chunk0[3]; const length = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; if (length == 0) { return new Uint8Array(0); } // read the data const out = new Uint8Array(length); const firstChunkLen = length < 28 ? length : 28; // copy from chunk0 for (let i: u32 = 0; i < firstChunkLen; i++) { out[i] = chunk0[4 + i]; } let offset = firstChunkLen; let remaining = length - firstChunkLen; // read subsequent chunks let chunkIndex: u64 = 1; while (remaining > 0) { const chunkPointer = bigEndianAdd(pointer, chunkIndex); const chunkData = Blockchain.getStorageAt(chunkPointer); const chunkSize: u32 = remaining < 32 ? remaining : 32; for (let j: u32 = 0; j < chunkSize; j++) { out[offset + j] = chunkData[j]; } offset += chunkSize; remaining -= chunkSize; chunkIndex++; } return out; } } function isAllZero(arr: Uint8Array): bool { for (let i = 0; i < arr.length; i++) { if (arr[i] != 0) return false; } return true; } export const idOfVariableBytes = idof<IVariableBytesCodec>(); export const VariableBytesCodec = new IVariableBytesCodec();