Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Big number support #95

Merged
merged 5 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
50 changes: 45 additions & 5 deletions libs/as-sdk-integration-tests/assembly/bytes.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -111,14 +118,47 @@ export class TestBytesToNumber extends OracleProgram {

export class TestNumberToBytes extends OracleProgram {
execution(): void {
const num1 = Bytes.fromNumber<u64>(133798);
const num2 = Bytes.fromNumber<u64>(133798, true);
const num1 = Bytes.fromNumber<u64>(u64.MAX_VALUE);
const num2 = Bytes.fromNumber<i64>(i64.MIN_VALUE, true);

// Convert both back
const result1 = num1.toNumber();
const result2 = num2.toNumber(true);
const result1 = num1.toNumber<u64>();
const result2 = num2.toNumber<i64>(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>(u128.Max);
const num2 = Bytes.fromNumber<u128>(u128.from(987654321), true);
const num3 = Bytes.fromNumber<u256>(u256.Max);
const num4 = Bytes.fromNumber<u256>(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));
}
}
6 changes: 6 additions & 0 deletions libs/as-sdk-integration-tests/assembly/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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") {
Expand Down
28 changes: 27 additions & 1 deletion libs/as-sdk-integration-tests/src/bytes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
});
});
Expand Down
92 changes: 79 additions & 13 deletions libs/as-sdk/assembly/bytes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { u128, u256 } from "as-bignum/assembly";
import { JSON } from "json-as";
import { decodeHex, encodeHex } from "./hex";

Expand Down Expand Up @@ -317,7 +318,7 @@ export class Bytes {

/**
* Creates a new Bytes instance from a number <T>
* <T> supports any number type (i8, u8, i16, u16, i32, u32, i64 and u64)
* <T> supports any number type (i8, u8, i16, u16, i32, u32, i64, u64, u128, and u256)
*
* @example
* ```
Expand All @@ -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<T = u64>(number: T, bigEndian: bool = false): Bytes {
static fromNumber<T>(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<T>();
const buffer = new ArrayBuffer(sizeOfNumber as i32);
const bufferPtr = changetype<usize>(buffer);
Expand All @@ -348,7 +356,8 @@ export class Bytes {

/**
* Convert the Bytes instance to the number <T>
* 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
* ```
Expand All @@ -365,17 +374,9 @@ export class Bytes {
*
* @param bigEndian if the Bytes instance should be read as big-endian (defaults to little-endian)
*/
toNumber<T = u64>(bigEndian: bool = false): T {
toNumber<T>(bigEndian: bool = false): T {
const sizeOfNumber = sizeof<T>();

// 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<T>();
throw new Error(
`Type ${typeName} has a byte length of ${sizeOfNumber}, but the Bytes instance has a length of ${this.length}`,
);
}
this.validateNumberByteLength<T>(sizeOfNumber);

let typedArray = this.value.slice();

Expand All @@ -387,4 +388,69 @@ export class Bytes {
const bufPtr = changetype<usize>(typedArray.buffer);
return load<T>(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<u128>();
this.validateNumberByteLength<u128>(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<u256>();
this.validateNumberByteLength<u256>(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<T>(sizeOfNumber: usize): void {
if ((this.length as usize) !== sizeOfNumber) {
const typeName = nameof<T>();
throw new Error(
`Type ${typeName} has a byte length of ${sizeOfNumber}, but the Bytes instance has a length of ${this.length}`,
);
}
}
}
6 changes: 4 additions & 2 deletions libs/as-sdk/assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
3 changes: 2 additions & 1 deletion libs/as-sdk/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
Expand Down
2 changes: 1 addition & 1 deletion libs/dev-tools/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
14 changes: 11 additions & 3 deletions libs/dev-tools/src/lib/testing/create-mock-reveal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ 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();
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,
Expand All @@ -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);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading