From 5931453f48deb6dc87fb87550bdd9e5dbd07add3 Mon Sep 17 00:00:00 2001 From: capt-nemo429 Date: Sun, 2 Apr 2023 22:01:11 -0300 Subject: [PATCH] feat(core): add `estimateBoxSize` method --- .../serializer/sigma/boxSerializer.spec.ts | 52 ++++++++++++++++- .../src/serializer/sigma/boxSerializer.ts | 56 ++++++++++++++++++- packages/core/src/serializer/vlq.spec.ts | 18 +++++- packages/core/src/serializer/vlq.ts | 19 ++++++- 4 files changed, 138 insertions(+), 7 deletions(-) diff --git a/packages/core/src/serializer/sigma/boxSerializer.spec.ts b/packages/core/src/serializer/sigma/boxSerializer.spec.ts index 5cbeca1c..00e6fa69 100644 --- a/packages/core/src/serializer/sigma/boxSerializer.spec.ts +++ b/packages/core/src/serializer/sigma/boxSerializer.spec.ts @@ -1,5 +1,7 @@ -import { Box } from "@fleet-sdk/common"; -import { serializeBox } from "./boxSerializer"; +import { Box, ensureBigInt, hexSize } from "@fleet-sdk/common"; +import { OutputBuilder, SAFE_MIN_BOX_VALUE } from "../../builder"; +import { manyTokensBoxesMock, regularBoxesMock, validBoxesMock } from "../../tests/mocks/mockBoxes"; +import { estimateBoxSize, serializeBox } from "./boxSerializer"; describe("Serialize ErgoBox", () => { const testVectors = [ @@ -252,4 +254,50 @@ describe("Serialize ErgoBox", () => { } as unknown as Box); }).toThrow(); }); + + it("Should estimate the box size in bytes", () => { + for (const tv of testVectors) { + expect(estimateBoxSize(tv.json)).toBe(hexSize(tv.serialized)); + } + + for (const box of regularBoxesMock) { + expect(estimateBoxSize(box)).toBe(serializeBox(box).length); + } + + for (const box of manyTokensBoxesMock) { + expect(estimateBoxSize(box)).toBe(serializeBox(box).length); + } + + for (const box of validBoxesMock) { + expect(estimateBoxSize(box)).toBe(serializeBox(box).length); + } + }); + + it("Should estimate the box size in bytes with custom value", () => { + for (const tv of testVectors) { + expect(estimateBoxSize(tv.json, ensureBigInt(tv.json.value) * 4n)).toBeGreaterThan( + hexSize(tv.serialized) + ); + } + }); + + it("Should estimate output builder size in bytes", () => { + for (const tv of testVectors) { + const output = new OutputBuilder(tv.json.value, tv.json.address) + .addTokens(tv.json.assets) + .setAdditionalRegisters(tv.json.additionalRegisters) + .setCreationHeight(tv.json.creationHeight); + + expect(estimateBoxSize(output)).toBeGreaterThanOrEqual(hexSize(tv.serialized)); + } + }); + + it("Should fail if creation height is undefined", () => { + const output = new OutputBuilder( + SAFE_MIN_BOX_VALUE, + "9hY16vzHmmfyVBwKeFGHvb2bMFsG94A1u7To1QWtUokACyFVENQ" + ); + + expect(() => estimateBoxSize(output)).toThrow(); + }); }); diff --git a/packages/core/src/serializer/sigma/boxSerializer.ts b/packages/core/src/serializer/sigma/boxSerializer.ts index ad1e2e73..65c5d257 100644 --- a/packages/core/src/serializer/sigma/boxSerializer.ts +++ b/packages/core/src/serializer/sigma/boxSerializer.ts @@ -2,12 +2,16 @@ import { Amount, Box, BoxCandidate, + hexSize, + isUndefined, NonMandatoryRegisters, some, TokenAmount } from "@fleet-sdk/common"; import { ensureBigInt, isDefined, isEmpty } from "@fleet-sdk/common"; +import { OutputBuilder } from "../../builder"; import { ErgoBox } from "../../models/ergoBox"; +import { estimateVLQSize } from "../vlq"; import { SigmaWriter } from "./sigmaWriter"; export function serializeBox(box: Box | ErgoBox): SigmaWriter; @@ -43,7 +47,9 @@ export function serializeBox( } } -function isBox(box: Box | ErgoBox | BoxCandidate): box is Box { +function isBox( + box: Box | ErgoBox | BoxCandidate | OutputBuilder +): box is Box { const castedBox = box as Box; return isDefined(castedBox.transactionId) && isDefined(castedBox.index); @@ -92,3 +98,51 @@ function writeRegisters(writer: SigmaWriter, registers: NonMandatoryRegisters): } } } + +const MAX_UINT16_VALUE = 65535; +const TRANSACTION_ID_BYTE_SIZE = 32; + +/** + * Estimates the byte size a box. + * @returns byte size of the box. + */ +export function estimateBoxSize( + box: Box | BoxCandidate | OutputBuilder, + withValue?: Amount +): number { + if (isUndefined(box.creationHeight)) { + throw new Error("Box size estimation error: creation height is undefined."); + } + + let size = 0; + + if (isDefined(withValue)) { + size += estimateVLQSize(withValue); + } else { + size += estimateVLQSize(box.value); + } + + size += hexSize(box.ergoTree); + size += estimateVLQSize(box.creationHeight); + + size += estimateVLQSize(box.assets.length); + size += box.assets.reduce( + (acc: number, curr) => (acc += hexSize(curr.tokenId) + estimateVLQSize(curr.amount)), + 0 + ); + + let registersLength = 0; + for (const key in box.additionalRegisters) { + const register = box.additionalRegisters[key as keyof NonMandatoryRegisters]; + if (register) { + size += hexSize(register); + registersLength++; + } + } + size += estimateVLQSize(registersLength); + + size += TRANSACTION_ID_BYTE_SIZE; + size += estimateVLQSize(isBox(box) ? box.index : MAX_UINT16_VALUE); + + return size; +} diff --git a/packages/core/src/serializer/vlq.spec.ts b/packages/core/src/serializer/vlq.spec.ts index d3612e61..44adf6ce 100644 --- a/packages/core/src/serializer/vlq.spec.ts +++ b/packages/core/src/serializer/vlq.spec.ts @@ -1,8 +1,8 @@ import { SigmaReader } from "./sigma/sigmaReader"; import { SigmaWriter } from "./sigma/sigmaWriter"; -import { readBigVLQ, readVLQ, writeBigVLQ, writeVLQ } from "./vlq"; +import { estimateVLQSize, readBigVLQ, readVLQ, writeBigVLQ, writeVLQ } from "./vlq"; -describe("32-bit VLQ encoding/decoding", () => { +describe("VLQ encoding/decoding", () => { const testVectors = [ { uint: 0, bytes: Uint8Array.from([0x00]) }, { uint: 126, bytes: Uint8Array.from([0x7e]) }, @@ -51,9 +51,15 @@ describe("32-bit VLQ encoding/decoding", () => { expect(readVLQ(new SigmaReader(toVLQBytes(n)))).toBe(n); }); }); + + it("Should estimate the byte size of numbers", () => { + for (const tv of testVectors) { + expect(estimateVLQSize(tv.uint)).toBe(tv.bytes.length); + } + }); }); -describe("64-bit VLQ encoding/decoding", () => { +describe("Big VLQ encoding/decoding", () => { function toBigVLQBytes(value: bigint) { return writeBigVLQ(new SigmaWriter(100), value).toBytes(); } @@ -102,4 +108,10 @@ describe("64-bit VLQ encoding/decoding", () => { expect(readBigVLQ(new SigmaReader(toBigVLQBytes(n)))).toBe(n); }); }); + + it("Should estimate the byte size of numbers", () => { + for (const tv of testVectors) { + expect(estimateVLQSize(tv.uint)).toBe(tv.bytes.length); + } + }); }); diff --git a/packages/core/src/serializer/vlq.ts b/packages/core/src/serializer/vlq.ts index 1b6b001a..5b414b41 100644 --- a/packages/core/src/serializer/vlq.ts +++ b/packages/core/src/serializer/vlq.ts @@ -1,4 +1,4 @@ -import { _0n, _127n, _128n, _7n } from "@fleet-sdk/common"; +import { _0n, _127n, _128n, _7n, ensureBigInt } from "@fleet-sdk/common"; import { SigmaReader } from "./sigma/sigmaReader"; import { SigmaWriter } from "./sigma/sigmaWriter"; @@ -113,3 +113,20 @@ export function readBigVLQ(reader: SigmaReader): bigint { return value; } + +/** + * Estimates the byte size of a given unsigned integer. + * @param value: the value to be evaluated. + * @returns the byte size of the value. + */ +export function estimateVLQSize(value: number | bigint | string): number { + value = ensureBigInt(value); + let size = 0; + + do { + size++; + value /= _128n; + } while (value > _0n); + + return size; +}