Skip to content

Commit b64abcc

Browse files
committed
feat: new CairoUint256 cairo datatype model
1 parent a0952d4 commit b64abcc

File tree

10 files changed

+354
-88
lines changed

10 files changed

+354
-88
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/* eslint-disable no-new */
2+
import {
3+
CairoUint256,
4+
UINT_256_HIGH_MAX,
5+
UINT_256_HIGH_MIN,
6+
UINT_256_LOW_MAX,
7+
UINT_256_LOW_MIN,
8+
UINT_256_MAX,
9+
UINT_256_MIN,
10+
} from '../../../src/utils/cairoDataTypes/uint256';
11+
12+
describe('CairoUint256 class test', () => {
13+
test('constructor 1 should throw on < UINT_256_MIN', () => {
14+
expect(() => {
15+
new CairoUint256(UINT_256_MIN - 1n);
16+
}).toThrow('bigNumberish is smaller than UINT_256_MIN');
17+
});
18+
19+
test('constructor 1 should throw on > UINT_256_MAX', () => {
20+
expect(() => {
21+
new CairoUint256(UINT_256_MAX + 1n);
22+
}).toThrow('bigNumberish is bigger than UINT_256_MAX');
23+
});
24+
25+
test('constructor 2 (low, high)', () => {
26+
const u256 = new CairoUint256(1000, 1000);
27+
expect(u256.toApiRequest()).toEqual(['1000', '1000']);
28+
});
29+
30+
test('constructor 2 should throw out of bounds', () => {
31+
expect(() => {
32+
new CairoUint256(UINT_256_LOW_MIN - 1n, 1000);
33+
}).toThrow('low is our of range UINT_256_LOW_MIN - UINT_256_LOW_MAX');
34+
});
35+
36+
test('constructor 2 should throw out of bounds', () => {
37+
expect(() => {
38+
new CairoUint256(UINT_256_LOW_MAX + 1n, 1000);
39+
}).toThrow('low is our of range UINT_256_LOW_MIN - UINT_256_LOW_MAX');
40+
});
41+
42+
test('constructor 2 should throw out of bounds', () => {
43+
expect(() => {
44+
new CairoUint256(1000, UINT_256_HIGH_MIN - 1n);
45+
}).toThrow('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
46+
});
47+
48+
test('constructor 2 should throw out of bounds', () => {
49+
expect(() => {
50+
new CairoUint256(1000, UINT_256_HIGH_MAX + 1n);
51+
}).toThrow('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
52+
});
53+
54+
test('constructor 3 ({low, high})', () => {
55+
const u256 = new CairoUint256({ low: 1000, high: 1000 });
56+
expect(u256.toApiRequest()).toEqual(['1000', '1000']);
57+
});
58+
59+
test('constructor 3 should throw out of bounds', () => {
60+
expect(() => {
61+
new CairoUint256({ low: 1000, high: UINT_256_HIGH_MAX + 1n });
62+
}).toThrow('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
63+
});
64+
65+
test('validate should throw on < UINT_256_MIN', () => {
66+
expect(() => {
67+
CairoUint256.validate(UINT_256_MIN - 1n);
68+
}).toThrow('bigNumberish is smaller than UINT_256_MIN');
69+
});
70+
71+
test('validate should throw on > UINT_256_MAX', () => {
72+
expect(() => {
73+
CairoUint256.validate(UINT_256_MAX + 1n);
74+
}).toThrow('bigNumberish is bigger than UINT_256_MAX');
75+
});
76+
77+
test('validate should pass and return bigint', () => {
78+
const validate = CairoUint256.validate(UINT_256_MAX);
79+
expect(typeof validate).toBe('bigint');
80+
});
81+
82+
test('is should return true', () => {
83+
const is = CairoUint256.is(UINT_256_MIN);
84+
expect(is).toBe(true);
85+
});
86+
87+
test('is should return false', () => {
88+
const is = CairoUint256.is(UINT_256_MAX + 1n);
89+
expect(is).toBe(false);
90+
});
91+
92+
test('constructor 1 should support BigNumberish', () => {
93+
const case1 = new CairoUint256(10n);
94+
const case2 = new CairoUint256(10);
95+
const case3 = new CairoUint256('10');
96+
const case4 = new CairoUint256('0xA');
97+
98+
expect(case1).toEqual(case2);
99+
expect(case3).toEqual(case4);
100+
expect(case1).toEqual(case4);
101+
});
102+
103+
test('should convert UINT_256_MAX to Uint256 dec struct', () => {
104+
const u256 = new CairoUint256(UINT_256_MAX);
105+
const u256Hex = u256.toUint256DecimalString();
106+
expect(u256Hex).toMatchInlineSnapshot(`
107+
Object {
108+
"high": "340282366920938463463374607431768211455",
109+
"low": "340282366920938463463374607431768211455",
110+
}
111+
`);
112+
});
113+
114+
test('should convert UINT_256_MAX to Uint256 hex struct', () => {
115+
const u256 = new CairoUint256(UINT_256_MAX);
116+
const u256Decimal = u256.toUint256HexString();
117+
expect(u256Decimal).toMatchInlineSnapshot(`
118+
Object {
119+
"high": "0xffffffffffffffffffffffffffffffff",
120+
"low": "0xffffffffffffffffffffffffffffffff",
121+
}
122+
`);
123+
});
124+
125+
test('isAbiType should return true', () => {
126+
const isAbiType = CairoUint256.isAbiType('core::integer::u256');
127+
expect(isAbiType).toBe(true);
128+
});
129+
130+
test('should convert UINT_256_MAX to BN', () => {
131+
const u256 = new CairoUint256(UINT_256_MAX);
132+
expect(u256.toBigInt()).toEqual(UINT_256_MAX);
133+
});
134+
135+
test('should convert UINT_256_MAX to API Request', () => {
136+
const u256 = new CairoUint256(UINT_256_MAX);
137+
expect(u256.toApiRequest()).toEqual([
138+
'340282366920938463463374607431768211455',
139+
'340282366920938463463374607431768211455',
140+
]);
141+
});
142+
});

__tests__/utils/uint256.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import { cairo } from '../../src';
2-
import { UINT_128_MAX, UINT_256_MAX, bnToUint256, uint256ToBN } from '../../src/utils/uint256';
1+
import { cairo, uint256 as u256 } from '../../src';
2+
3+
const { bnToUint256, UINT_128_MAX, UINT_256_MAX, uint256ToBN } = u256;
34

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

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

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

122123
test('should throw if BN over uint256 range', () => {
123124
expect(() => bnToUint256(UINT_256_MAX + 1n)).toThrowErrorMatchingInlineSnapshot(
124-
`"Number is too large"`
125+
`"bigNumberish is bigger than UINT_256_MAX"`
125126
);
126127
});
127128
});

src/utils/cairoDataTypes/felt.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// TODO Convert to CairoFelt base on CairoUint256 and implement it in the codebase in the backward compatible manner
2+
3+
import { BigNumberish, isBigInt, isHex, isStringWholeNumber } from '../num';
4+
import { encodeShortString, isShortString, isText } from '../shortString';
5+
6+
/**
7+
* Create felt Cairo type (cairo type helper)
8+
* @returns format: felt-string
9+
*/
10+
export function CairoFelt(it: BigNumberish): string {
11+
// BN or number
12+
if (isBigInt(it) || (typeof it === 'number' && Number.isInteger(it))) {
13+
return it.toString();
14+
}
15+
// string text
16+
if (isText(it)) {
17+
if (!isShortString(it as string))
18+
throw new Error(
19+
`${it} is a long string > 31 chars, felt can store short strings, split it to array of short strings`
20+
);
21+
const encoded = encodeShortString(it as string);
22+
return BigInt(encoded).toString();
23+
}
24+
// hex string
25+
if (typeof it === 'string' && isHex(it)) {
26+
// toBN().toString
27+
return BigInt(it).toString();
28+
}
29+
// string number (already converted), or unhandled type
30+
if (typeof it === 'string' && isStringWholeNumber(it)) {
31+
return it;
32+
}
33+
// bool to felt
34+
if (typeof it === 'boolean') {
35+
return `${+it}`;
36+
}
37+
38+
throw new Error(`${it} can't be computed by felt()`);
39+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/* eslint-disable no-bitwise */
2+
/**
3+
* Singular class handling cairo u256 data type
4+
*/
5+
6+
import { BigNumberish, Uint256 } from '../../types';
7+
import { addHexPrefix } from '../encode';
8+
import { CairoFelt } from './felt';
9+
10+
export const UINT_128_MAX = (1n << 128n) - 1n;
11+
export const UINT_256_MAX = (1n << 256n) - 1n;
12+
export const UINT_256_MIN = 0n;
13+
export const UINT_256_LOW_MAX = 340282366920938463463374607431768211455n;
14+
export const UINT_256_HIGH_MAX = 340282366920938463463374607431768211455n;
15+
export const UINT_256_LOW_MIN = 0n;
16+
export const UINT_256_HIGH_MIN = 0n;
17+
18+
export class CairoUint256 {
19+
public low: bigint;
20+
21+
public high: bigint;
22+
23+
static abiSelector = 'core::integer::u256';
24+
25+
/**
26+
* Default constructor (Lib usage)
27+
* @param bigNumberish BigNumberish value representing uin256
28+
*/
29+
public constructor(bigNumberish: BigNumberish);
30+
/**
31+
* Direct props initialization (Api response)
32+
*/
33+
public constructor(low: BigNumberish, high: BigNumberish);
34+
/**
35+
* Initialization from Uint256 object
36+
*/
37+
public constructor(uint256: Uint256);
38+
39+
public constructor(...arr: any[]) {
40+
if (typeof arr === 'object' && arr.length === 1 && arr[0].low && arr[0].high) {
41+
const props = CairoUint256.validateProps(arr[0].low, arr[0].high);
42+
this.low = props.low;
43+
this.high = props.high;
44+
} else if (arr.length === 1) {
45+
const bigInt = CairoUint256.validate(arr[0]);
46+
this.low = bigInt & UINT_128_MAX;
47+
this.high = bigInt >> 128n;
48+
} else if (arr.length === 2) {
49+
const props = CairoUint256.validateProps(arr[0], arr[1]);
50+
this.low = props.low;
51+
this.high = props.high;
52+
} else {
53+
throw Error('Incorrect constructor parameters');
54+
}
55+
}
56+
57+
/**
58+
* Validate if BigNumberish can be represented as Unit256
59+
*/
60+
static validate(bigNumberish: BigNumberish) {
61+
const bigInt = BigInt(bigNumberish);
62+
if (bigInt < UINT_256_MIN) throw Error('bigNumberish is smaller than UINT_256_MIN');
63+
if (bigInt > UINT_256_MAX) throw new Error('bigNumberish is bigger than UINT_256_MAX');
64+
return bigInt;
65+
}
66+
67+
static validateProps(low: BigNumberish, high: BigNumberish) {
68+
const bigIntLow = BigInt(low);
69+
const bigIntHigh = BigInt(high);
70+
if (bigIntLow < UINT_256_LOW_MIN || bigIntLow > UINT_256_LOW_MAX) {
71+
throw new Error('low is our of range UINT_256_LOW_MIN - UINT_256_LOW_MAX');
72+
}
73+
if (bigIntHigh < UINT_256_HIGH_MIN || bigIntHigh > UINT_256_HIGH_MAX) {
74+
throw new Error('high is our of range UINT_256_HIGH_MIN - UINT_256_HIGH_MAX');
75+
}
76+
return { low: bigIntLow, high: bigIntHigh };
77+
}
78+
79+
/**
80+
* Check if BigNumberish can be represented as Unit256
81+
*/
82+
static is(bigNumberish: BigNumberish) {
83+
try {
84+
CairoUint256.validate(bigNumberish);
85+
} catch (error) {
86+
return false;
87+
}
88+
return true;
89+
}
90+
91+
/**
92+
* Check if provided abi type is this data type
93+
*/
94+
static isAbiType(abiType: string) {
95+
return abiType === CairoUint256.abiSelector;
96+
}
97+
98+
/**
99+
* Return bigint representation
100+
*/
101+
toBigInt() {
102+
return (this.high << 128n) + this.low;
103+
}
104+
105+
/**
106+
* Return Uint256 structure with HexString props
107+
* {low: HexString, high: HexString}
108+
*/
109+
toUint256HexString() {
110+
return {
111+
low: addHexPrefix(this.low.toString(16)),
112+
high: addHexPrefix(this.high.toString(16)),
113+
};
114+
}
115+
116+
/**
117+
* Return Uint256 structure with DecimalString props
118+
* {low: DecString, high: DecString}
119+
*/
120+
toUint256DecimalString() {
121+
return {
122+
low: this.low.toString(10),
123+
high: this.high.toString(10),
124+
};
125+
}
126+
127+
/**
128+
* Return api requests representation witch is felt array
129+
*/
130+
toApiRequest() {
131+
return [CairoFelt(this.low), CairoFelt(this.high)];
132+
}
133+
}

0 commit comments

Comments
 (0)