Skip to content

Commit

Permalink
feat: new CairoUint256 cairo datatype model
Browse files Browse the repository at this point in the history
  • Loading branch information
tabaktoni committed Feb 2, 2024
1 parent a0952d4 commit b64abcc
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 88 deletions.
142 changes: 142 additions & 0 deletions __tests__/utils/CairoTypes/uint256.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* eslint-disable no-new */
import {
CairoUint256,
UINT_256_HIGH_MAX,
UINT_256_HIGH_MIN,
UINT_256_LOW_MAX,
UINT_256_LOW_MIN,
UINT_256_MAX,
UINT_256_MIN,
} from '../../../src/utils/cairoDataTypes/uint256';

describe('CairoUint256 class test', () => {
test('constructor 1 should throw on < UINT_256_MIN', () => {
expect(() => {
new CairoUint256(UINT_256_MIN - 1n);
}).toThrow('bigNumberish is smaller than UINT_256_MIN');
});

test('constructor 1 should throw on > UINT_256_MAX', () => {
expect(() => {
new CairoUint256(UINT_256_MAX + 1n);
}).toThrow('bigNumberish is bigger than UINT_256_MAX');
});

test('constructor 2 (low, high)', () => {
const u256 = new CairoUint256(1000, 1000);
expect(u256.toApiRequest()).toEqual(['1000', '1000']);
});

test('constructor 2 should throw out of bounds', () => {
expect(() => {
new CairoUint256(UINT_256_LOW_MIN - 1n, 1000);
}).toThrow('low is our of range UINT_256_LOW_MIN - UINT_256_LOW_MAX');
});

test('constructor 2 should throw out of bounds', () => {
expect(() => {
new CairoUint256(UINT_256_LOW_MAX + 1n, 1000);
}).toThrow('low is our of range UINT_256_LOW_MIN - UINT_256_LOW_MAX');
});

test('constructor 2 should throw out of bounds', () => {
expect(() => {
new CairoUint256(1000, UINT_256_HIGH_MIN - 1n);
}).toThrow('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
});

test('constructor 2 should throw out of bounds', () => {
expect(() => {
new CairoUint256(1000, UINT_256_HIGH_MAX + 1n);
}).toThrow('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
});

test('constructor 3 ({low, high})', () => {
const u256 = new CairoUint256({ low: 1000, high: 1000 });
expect(u256.toApiRequest()).toEqual(['1000', '1000']);
});

test('constructor 3 should throw out of bounds', () => {
expect(() => {
new CairoUint256({ low: 1000, high: UINT_256_HIGH_MAX + 1n });
}).toThrow('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
});

test('validate should throw on < UINT_256_MIN', () => {
expect(() => {
CairoUint256.validate(UINT_256_MIN - 1n);
}).toThrow('bigNumberish is smaller than UINT_256_MIN');
});

test('validate should throw on > UINT_256_MAX', () => {
expect(() => {
CairoUint256.validate(UINT_256_MAX + 1n);
}).toThrow('bigNumberish is bigger than UINT_256_MAX');
});

test('validate should pass and return bigint', () => {
const validate = CairoUint256.validate(UINT_256_MAX);
expect(typeof validate).toBe('bigint');
});

test('is should return true', () => {
const is = CairoUint256.is(UINT_256_MIN);
expect(is).toBe(true);
});

test('is should return false', () => {
const is = CairoUint256.is(UINT_256_MAX + 1n);
expect(is).toBe(false);
});

test('constructor 1 should support BigNumberish', () => {
const case1 = new CairoUint256(10n);
const case2 = new CairoUint256(10);
const case3 = new CairoUint256('10');
const case4 = new CairoUint256('0xA');

expect(case1).toEqual(case2);
expect(case3).toEqual(case4);
expect(case1).toEqual(case4);
});

test('should convert UINT_256_MAX to Uint256 dec struct', () => {
const u256 = new CairoUint256(UINT_256_MAX);
const u256Hex = u256.toUint256DecimalString();
expect(u256Hex).toMatchInlineSnapshot(`
Object {
"high": "340282366920938463463374607431768211455",
"low": "340282366920938463463374607431768211455",
}
`);
});

test('should convert UINT_256_MAX to Uint256 hex struct', () => {
const u256 = new CairoUint256(UINT_256_MAX);
const u256Decimal = u256.toUint256HexString();
expect(u256Decimal).toMatchInlineSnapshot(`
Object {
"high": "0xffffffffffffffffffffffffffffffff",
"low": "0xffffffffffffffffffffffffffffffff",
}
`);
});

test('isAbiType should return true', () => {
const isAbiType = CairoUint256.isAbiType('core::integer::u256');
expect(isAbiType).toBe(true);
});

test('should convert UINT_256_MAX to BN', () => {
const u256 = new CairoUint256(UINT_256_MAX);
expect(u256.toBigInt()).toEqual(UINT_256_MAX);
});

test('should convert UINT_256_MAX to API Request', () => {
const u256 = new CairoUint256(UINT_256_MAX);
expect(u256.toApiRequest()).toEqual([
'340282366920938463463374607431768211455',
'340282366920938463463374607431768211455',
]);
});
});
13 changes: 7 additions & 6 deletions __tests__/utils/uint256.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { cairo } from '../../src';
import { UINT_128_MAX, UINT_256_MAX, bnToUint256, uint256ToBN } from '../../src/utils/uint256';
import { cairo, uint256 as u256 } from '../../src';

const { bnToUint256, UINT_128_MAX, UINT_256_MAX, uint256ToBN } = u256;

describe('cairo uint256', () => {
test('bnToUint256 should not convert -1 from BN to uint256 hex-string struct', () => {
expect(() => {
bnToUint256(-1n);
}).toThrow('uint256 must be positive number');
u256.bnToUint256(-1n);
}).toThrow('bigNumberish is smaller than UINT_256_MIN');
});

test('uint256 should not convert -1 to uint256 dec struct', () => {
expect(() => {
cairo.uint256(-1n);
}).toThrow('uint256 must be positive number');
}).toThrow('bigNumberish is smaller than UINT_256_MIN');
});

test('uint256 should convert 1000 to uint256 dec struct', () => {
Expand Down Expand Up @@ -121,7 +122,7 @@ describe('cairo uint256', () => {

test('should throw if BN over uint256 range', () => {
expect(() => bnToUint256(UINT_256_MAX + 1n)).toThrowErrorMatchingInlineSnapshot(
`"Number is too large"`
`"bigNumberish is bigger than UINT_256_MAX"`
);
});
});
39 changes: 39 additions & 0 deletions src/utils/cairoDataTypes/felt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// TODO Convert to CairoFelt base on CairoUint256 and implement it in the codebase in the backward compatible manner

import { BigNumberish, isBigInt, isHex, isStringWholeNumber } from '../num';
import { encodeShortString, isShortString, isText } from '../shortString';

/**
* Create felt Cairo type (cairo type helper)
* @returns format: felt-string
*/
export function CairoFelt(it: BigNumberish): string {
// BN or number
if (isBigInt(it) || (typeof it === 'number' && Number.isInteger(it))) {
return it.toString();
}
// string text
if (isText(it)) {
if (!isShortString(it as string))
throw new Error(
`${it} is a long string > 31 chars, felt can store short strings, split it to array of short strings`
);
const encoded = encodeShortString(it as string);
return BigInt(encoded).toString();
}
// hex string
if (typeof it === 'string' && isHex(it)) {
// toBN().toString
return BigInt(it).toString();
}
// string number (already converted), or unhandled type
if (typeof it === 'string' && isStringWholeNumber(it)) {
return it;
}
// bool to felt
if (typeof it === 'boolean') {
return `${+it}`;
}

throw new Error(`${it} can't be computed by felt()`);
}
133 changes: 133 additions & 0 deletions src/utils/cairoDataTypes/uint256.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable no-bitwise */
/**
* Singular class handling cairo u256 data type
*/

import { BigNumberish, Uint256 } from '../../types';
import { addHexPrefix } from '../encode';
import { CairoFelt } from './felt';

export const UINT_128_MAX = (1n << 128n) - 1n;
export const UINT_256_MAX = (1n << 256n) - 1n;
export const UINT_256_MIN = 0n;
export const UINT_256_LOW_MAX = 340282366920938463463374607431768211455n;
export const UINT_256_HIGH_MAX = 340282366920938463463374607431768211455n;
export const UINT_256_LOW_MIN = 0n;
export const UINT_256_HIGH_MIN = 0n;

export class CairoUint256 {
public low: bigint;

public high: bigint;

static abiSelector = 'core::integer::u256';

/**
* Default constructor (Lib usage)
* @param bigNumberish BigNumberish value representing uin256
*/
public constructor(bigNumberish: BigNumberish);
/**
* Direct props initialization (Api response)
*/
public constructor(low: BigNumberish, high: BigNumberish);
/**
* Initialization from Uint256 object
*/
public constructor(uint256: Uint256);

public constructor(...arr: any[]) {
if (typeof arr === 'object' && arr.length === 1 && arr[0].low && arr[0].high) {
const props = CairoUint256.validateProps(arr[0].low, arr[0].high);
this.low = props.low;
this.high = props.high;
} else if (arr.length === 1) {
const bigInt = CairoUint256.validate(arr[0]);
this.low = bigInt & UINT_128_MAX;
this.high = bigInt >> 128n;
} else if (arr.length === 2) {
const props = CairoUint256.validateProps(arr[0], arr[1]);
this.low = props.low;
this.high = props.high;
} else {
throw Error('Incorrect constructor parameters');
}
}

/**
* Validate if BigNumberish can be represented as Unit256
*/
static validate(bigNumberish: BigNumberish) {
const bigInt = BigInt(bigNumberish);
if (bigInt < UINT_256_MIN) throw Error('bigNumberish is smaller than UINT_256_MIN');
if (bigInt > UINT_256_MAX) throw new Error('bigNumberish is bigger than UINT_256_MAX');
return bigInt;
}

static validateProps(low: BigNumberish, high: BigNumberish) {
const bigIntLow = BigInt(low);
const bigIntHigh = BigInt(high);
if (bigIntLow < UINT_256_LOW_MIN || bigIntLow > UINT_256_LOW_MAX) {
throw new Error('low is our of range UINT_256_LOW_MIN - UINT_256_LOW_MAX');
}
if (bigIntHigh < UINT_256_HIGH_MIN || bigIntHigh > UINT_256_HIGH_MAX) {
throw new Error('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
}
return { low: bigIntLow, high: bigIntHigh };
}

/**
* Check if BigNumberish can be represented as Unit256
*/
static is(bigNumberish: BigNumberish) {
try {
CairoUint256.validate(bigNumberish);
} catch (error) {
return false;
}
return true;
}

/**
* Check if provided abi type is this data type
*/
static isAbiType(abiType: string) {
return abiType === CairoUint256.abiSelector;
}

/**
* Return bigint representation
*/
toBigInt() {
return (this.high << 128n) + this.low;
}

/**
* Return Uint256 structure with HexString props
* {low: HexString, high: HexString}
*/
toUint256HexString() {
return {
low: addHexPrefix(this.low.toString(16)),
high: addHexPrefix(this.high.toString(16)),
};
}

/**
* Return Uint256 structure with DecimalString props
* {low: DecString, high: DecString}
*/
toUint256DecimalString() {
return {
low: this.low.toString(10),
high: this.high.toString(10),
};
}

/**
* Return api requests representation witch is felt array
*/
toApiRequest() {
return [CairoFelt(this.low), CairoFelt(this.high)];
}
}
Loading

0 comments on commit b64abcc

Please sign in to comment.