diff --git a/bun.lockb b/bun.lockb index 4efa3dc..90c6fc4 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/libs/as-sdk-integration-tests/assembly/bytes.ts b/libs/as-sdk-integration-tests/assembly/bytes.ts index 8f246c7..77b5955 100644 --- a/libs/as-sdk-integration-tests/assembly/bytes.ts +++ b/libs/as-sdk-integration-tests/assembly/bytes.ts @@ -1,4 +1,11 @@ -import { Bytes, Console, OracleProgram, Process } from "../../as-sdk/assembly"; +import { + Bytes, + Console, + OracleProgram, + Process, + u128, + u256, +} from "../../as-sdk/assembly"; export class TestBytesSliceNoArguments extends OracleProgram { execution(): void { @@ -111,14 +118,47 @@ export class TestBytesToNumber extends OracleProgram { export class TestNumberToBytes extends OracleProgram { execution(): void { - const num1 = Bytes.fromNumber(133798); - const num2 = Bytes.fromNumber(133798, true); + const num1 = Bytes.fromNumber(u64.MAX_VALUE); + const num2 = Bytes.fromNumber(i64.MIN_VALUE, true); // Convert both back - const result1 = num1.toNumber(); - const result2 = num2.toNumber(true); + const result1 = num1.toNumber(); + const result2 = num2.toNumber(true); const result = `${num1.toHexString()}:${num2.toHexString()}:${result1}:${result2}`; Process.success(Bytes.fromUtf8String(result)); } } + +export class TestBigNumberToBytes extends OracleProgram { + execution(): void { + const num1 = Bytes.fromNumber(u128.Max); + const num2 = Bytes.fromNumber(u128.from(987654321), true); + const num3 = Bytes.fromNumber(u256.Max); + const num4 = Bytes.fromNumber(u256.from(123456789), true); + + // Convert everything back + const result1 = num1.toU128(); + const result2 = num2.toU128(true); + const result3 = num3.toU256(); + const result4 = num4.toU256(true); + + const result = `${num1.toHexString()}:${result1.toString()},${num2.toHexString()}:${result2},${num3.toHexString()}:${result3},${num4.toHexString()}:${result4}`; + Process.success(Bytes.fromUtf8String(result)); + } +} + +export class TestBytesToBigNumber extends OracleProgram { + execution(): void { + const input = Bytes.fromHexString("0x0000000000000000000000003ade68b1"); + const number = input.toU128(); + + const inputU256 = Bytes.fromHexString( + "0x00000000000000000000000000000000000000000000000000000000075bcd15", + ); + const numberU256 = inputU256.toU256(true); + + const result = `${number.toString()}:${numberU256.toString()}`; + Process.success(Bytes.fromUtf8String(result)); + } +} diff --git a/libs/as-sdk-integration-tests/assembly/index.ts b/libs/as-sdk-integration-tests/assembly/index.ts index 93b24c9..b456a0c 100644 --- a/libs/as-sdk-integration-tests/assembly/index.ts +++ b/libs/as-sdk-integration-tests/assembly/index.ts @@ -1,11 +1,13 @@ import { Bytes, Process } from "../../as-sdk/assembly/index"; import { + TestBigNumberToBytes, TestBytesConcat, TestBytesJSON, TestBytesSliceNoArguments, TestBytesSliceOnlyStart, TestBytesSliceStartEnd, TestBytesStaticConcat, + TestBytesToBigNumber, TestBytesToNumber, TestNumberToBytes, } from "./bytes"; @@ -87,6 +89,10 @@ if (args === "testHttpRejection") { new TestBytesToNumber().run(); } else if (args === "testNumberToBytes") { new TestNumberToBytes().run(); +} else if (args === "testBytesToBigNumber") { + new TestBytesToBigNumber().run(); +} else if (args === "testBigNumberToBytes") { + new TestBigNumberToBytes().run(); } else if (args === "testLogNull") { new TestLogNull().run(); } else if (args === "testLogFloat") { diff --git a/libs/as-sdk-integration-tests/src/bytes.test.ts b/libs/as-sdk-integration-tests/src/bytes.test.ts index c841881..1067018 100644 --- a/libs/as-sdk-integration-tests/src/bytes.test.ts +++ b/libs/as-sdk-integration-tests/src/bytes.test.ts @@ -101,7 +101,33 @@ describe("bytes", () => { ); expect(result.resultAsString).toBe( - "a60a020000000000:0000000000020aa6:133798:133798", + "ffffffffffffffff:8000000000000000:18446744073709551615:-9223372036854775808", + ); + }); + }); + + describe("fromBigNumber", () => { + it("should convert a big number to Bytes", async () => { + const result = await executeDrWasm( + wasmBinary, + Buffer.from("testBigNumberToBytes"), + ); + + expect(result.resultAsString).toBe( + "ffffffffffffffffffffffffffffffff:340282366920938463463374607431768211455,0000000000000000000000003ade68b1:987654321,ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff:115792089237316195423570985008687907853269984665640564039457584007913129639935,00000000000000000000000000000000000000000000000000000000075bcd15:123456789", + ); + }); + }); + + describe("toBigNumber", () => { + it("should be able to parse Bytes as a big number", async () => { + const result = await executeDrWasm( + wasmBinary, + Buffer.from("testBytesToBigNumber"), + ); + + expect(result.resultAsString).toBe( + "235817861417383168075506718003194494976:123456789", ); }); }); diff --git a/libs/as-sdk/assembly/bytes.ts b/libs/as-sdk/assembly/bytes.ts index 3c50a6e..2e26d6d 100644 --- a/libs/as-sdk/assembly/bytes.ts +++ b/libs/as-sdk/assembly/bytes.ts @@ -1,3 +1,4 @@ +import { u128, u256 } from "as-bignum/assembly"; import { JSON } from "json-as"; import { decodeHex, encodeHex } from "./hex"; @@ -317,7 +318,7 @@ export class Bytes { /** * Creates a new Bytes instance from a number - * supports any number type (i8, u8, i16, u16, i32, u32, i64 and u64) + * supports any number type (i8, u8, i16, u16, i32, u32, i64, u64, u128, and u256) * * @example * ``` @@ -331,7 +332,14 @@ export class Bytes { * @param number the number you want to convert to a Bytes instance * @param bigEndian if you want to store the number as big-endian. Defaults to little-endian */ - static fromNumber(number: T, bigEndian: bool = false): Bytes { + static fromNumber(number: T, bigEndian: bool = false): Bytes { + if (number instanceof u128 || number instanceof u256) { + const bytes = number.toBytes(bigEndian); + const byteArray = new Uint8Array(bytes.length); + byteArray.set(bytes); + return Bytes.fromByteArray(byteArray); + } + const sizeOfNumber = sizeof(); const buffer = new ArrayBuffer(sizeOfNumber as i32); const bufferPtr = changetype(buffer); @@ -348,7 +356,8 @@ export class Bytes { /** * Convert the Bytes instance to the number - * T supports any number type (i8, u8, i16, u16, i32, u32, i64 and u64) + * T supports the following number types: (i8, u8, i16, u16, i32, u32, i64 and u64) + * for u128 and u256 see {@linkcode Bytes.toU128} and {@linkcode Bytes.toU256} * * @example * ``` @@ -365,17 +374,9 @@ export class Bytes { * * @param bigEndian if the Bytes instance should be read as big-endian (defaults to little-endian) */ - toNumber(bigEndian: bool = false): T { + toNumber(bigEndian: bool = false): T { const sizeOfNumber = sizeof(); - - // Make sure the byte amount is exactly the amount required for an integer - // Otherwise we could interpret the bytes wrong - if ((this.length as usize) !== sizeOfNumber) { - const typeName = nameof(); - throw new Error( - `Type ${typeName} has a byte length of ${sizeOfNumber}, but the Bytes instance has a length of ${this.length}`, - ); - } + this.validateNumberByteLength(sizeOfNumber); let typedArray = this.value.slice(); @@ -387,4 +388,69 @@ export class Bytes { const bufPtr = changetype(typedArray.buffer); return load(bufPtr); } + + /** + * Convert the Bytes instance to a u128 number + * for smaller number types see {@linkcode Bytes.toNumber} + * for u256 see {@linkcode Bytes.toU256} + * + * @example + * ``` + * const littleEndianBytes = Bytes.fromHexString("0x0000000000000000000000003ade68b1"); + * const num1 = littleEndianBytes.toU128(); + * + * Console.log(num1); // Outputs: 235817861417383168075506718003194494976 + * + * const bigEndianBytes = Bytes.fromHexString("0x0000000000000000000000003ade68b1"); + * const number = bigEndianBytes.toU128(true); + * + * Console.log(number); // Outputs: 987654321 + * ``` + * + * @param bigEndian if the Bytes instance should be read as big-endian (defaults to little-endian) + */ + toU128(bigEndian: bool = false): u128 { + const sizeOfNumber = offsetof(); + this.validateNumberByteLength(sizeOfNumber); + + return u128.fromBytes(this.value, bigEndian); + } + + /** + * Convert the Bytes instance to a u256 number + * for smaller number types see {@linkcode Bytes.toNumber} + * for u128 see {@linkcode Bytes.toU128} + * + * @example + * ``` + * const littleEndianBytes = Bytes.fromHexString("0x00000000000000000000000000000000000000000000000000000000075bcd15"); + * const num1 = littleEndianBytes.toU256(); + * + * Console.log(num1); // Outputs: 9861401716165347554763518477098801055286775394839307868237211366843748450304 + * + * const bigEndianBytes = Bytes.fromHexString("0x00000000000000000000000000000000000000000000000000000000075bcd15"); + * const number = bigEndianBytes.toU256(true); + * + * Console.log(number); // Outputs: 123456789 + * ``` + * + * @param bigEndian if the Bytes instance should be read as big-endian (defaults to little-endian) + */ + toU256(bigEndian: bool = false): u256 { + const sizeOfNumber = offsetof(); + this.validateNumberByteLength(sizeOfNumber); + + return u256.fromBytes(this.value, bigEndian); + } + + // Make sure the byte amount is exactly the amount required for an integer + // Otherwise we could interpret the bytes wrong + private validateNumberByteLength(sizeOfNumber: usize): void { + if ((this.length as usize) !== sizeOfNumber) { + const typeName = nameof(); + throw new Error( + `Type ${typeName} has a byte length of ${sizeOfNumber}, but the Bytes instance has a length of ${this.length}`, + ); + } + } } diff --git a/libs/as-sdk/assembly/index.ts b/libs/as-sdk/assembly/index.ts index 0e4d4c4..401b701 100644 --- a/libs/as-sdk/assembly/index.ts +++ b/libs/as-sdk/assembly/index.ts @@ -31,8 +31,6 @@ export { export { proxyHttpFetch, generateProxyHttpSigningMessage } from "./proxy-http"; -// Export library so consumers don't need to reimport it themselves -export { JSON } from "json-as/assembly"; export { PromiseStatus } from "./promise"; export { Process, Tally }; export { RevealResult } from "./tally"; @@ -41,3 +39,7 @@ export { Bytes } from "./bytes"; export { decodeHex, encodeHex } from "./hex"; export { OracleProgram } from "./oracle-program"; export { secp256k1Verify, keccak256 } from "./crypto"; + +// Export library so consumers don't need to reimport it themselves +export { JSON } from "json-as/assembly"; +export { u128, u256 } from "as-bignum/assembly"; diff --git a/libs/as-sdk/package.json b/libs/as-sdk/package.json index ceca0be..f42c6d7 100644 --- a/libs/as-sdk/package.json +++ b/libs/as-sdk/package.json @@ -1,10 +1,11 @@ { "name": "@seda-protocol/as-sdk", "type": "module", - "version": "0.0.14", + "version": "0.0.15", "types": "./assembly/index.ts", "dependencies": { "@assemblyscript/wasi-shim": "git+https://github.com/sedaprotocol/wasi-shim.git", + "as-bignum": "^0.3.1", "json-as": "^0.9.6", "visitor-as": "^0.11.4" } diff --git a/libs/dev-tools/package.json b/libs/dev-tools/package.json index 64753ff..ce800ef 100644 --- a/libs/dev-tools/package.json +++ b/libs/dev-tools/package.json @@ -1,7 +1,7 @@ { "name": "@seda-protocol/dev-tools", "type": "module", - "version": "0.0.9", + "version": "0.0.10", "scripts": { "build:cli": "bun build src/cli/index.ts --target node --outdir bin", "build": "bun run build:cli && bun run build:lib", diff --git a/libs/dev-tools/src/lib/testing/create-mock-reveal.ts b/libs/dev-tools/src/lib/testing/create-mock-reveal.ts index e4dfc50..39e54a6 100644 --- a/libs/dev-tools/src/lib/testing/create-mock-reveal.ts +++ b/libs/dev-tools/src/lib/testing/create-mock-reveal.ts @@ -9,9 +9,9 @@ export type RevealInput = { */ gasUsed: number; /** - * JSON string containing the output of the data request WASM binary. + * The output as determind by the `Process.succes()` call in the execution phase. */ - result: string; + result: string | Buffer; }; const encoder = new TextEncoder(); @@ -19,7 +19,7 @@ const MOCK_SALT = "seda_sdk"; const MOCK_SALT_BYTES = [...encoder.encode(MOCK_SALT)]; export function createMockReveal(input: RevealInput) { - const resultBytes = encoder.encode(input.result); + const resultBytes = inputToBytes(input.result); return { salt: MOCK_SALT_BYTES, @@ -32,3 +32,11 @@ export function createMockReveal(input: RevealInput) { export function createMockReveals(inputs: RevealInput[]) { return inputs.map((input) => createMockReveal(input)); } + +function inputToBytes(input: string | Buffer): Uint8Array { + if (typeof input === "string") { + return encoder.encode(input); + } + + return Uint8Array.from(input); +} diff --git a/package.json b/package.json index 9f71d8a..3ad2ef3 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "dotenv": "^16.3.1", "figlet": "^1.6.0", "json-as": "0.9.6", + "as-bignum": "^0.3.1", "node-fetch": "^3.3.2", "node-gzip": "^1.1.2", "ora": "^7.0.1",