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

feat: Implement Calldata Decoding to JSC Types #1018

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
dist/
www/
src/
144 changes: 144 additions & 0 deletions __tests__/utils/calldataDecode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { DecodeConfig } from '../../src/types';

import {
// Account,
BigNumberish,
CairoUint256,
// CairoCustomEnum,
// CairoOption,
// CairoOptionVariant,
// CairoResult,
// CairoResultVariant,
// CairoUint256,
// CairoUint512,
CallData,
Calldata,
// CompiledSierra,
// Contract,
// DeclareDeployUDCResponse,
RawArgsArray,
RawArgsObject,
// byteArray,
cairo,
// ec,
// hash,
// num,
// selector,
// shortString,
// stark,
// types,
// type Uint512,
} from '../../src';

import {
// compiledC1v2,
// compiledHelloSierra,
compiledComplexSierra,
} from '../config/fixtures';

const {
// uint256,
tuple,
// isCairo1Abi
} = cairo;

describe('Cairo 1', () => {
test('should correctly compile and decompile complex data structures', async () => {
type Order2 = {
p1: BigNumberish;
p2: BigNumberish[];
};

const myOrder2bis: Order2 = {
// wrong order
p2: ['abcd', '56ec'],
p1: 'smolstring',
};

const secondUint256: CairoUint256 = new CairoUint256(40000n);
const tempUint256 = { low: 23456n, high: 1n } as CairoUint256;
const thirdUint256: CairoUint256 = new CairoUint256(tempUint256);
const myFalseUint256: CairoUint256 = thirdUint256; // wrong order

const myRawArgsObject: RawArgsObject = {
// wrong order
active: true,
symbol: 'NIT',
initial_supply: myFalseUint256,
recipient: '0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a',
decimals: 18,
tupoftup: tuple(tuple(34, 94), myFalseUint256),
card: myOrder2bis,
longText: 'Bug is back, for ever, here and everywhere',
array1: [100, 101, 102],
array2: [
[200, 201],
[202, 203],
[204, 205],
],
array3: [myOrder2bis, myOrder2bis],
array4: [myFalseUint256, myFalseUint256],
tuple1: tuple(secondUint256, myOrder2bis, [54, 55, 174], 59),
name: 'niceToken',
array5: [tuple(251, 40000), tuple(252, 40001)],
};

const myRawArgsArray: RawArgsArray = [
'niceToken',
'NIT',
18,
thirdUint256,
{ p1: '17', p2: ['234', '467456745457', '0x56ec'] },
'0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a',
true,
{ '0': { '0': 34, '1': 94 }, '1': thirdUint256 },
'Bug is back, for ever, here and everywhere',
['100', '101', '102'],
[
[200, 201],
[202, 203],
[204, 205],
],
[
{ p1: '17', p2: ['234', '467456745457n', '0x56ec'] },
{ p1: '17', p2: ['234', '467456745457n', '0x56ec'] },
],
[thirdUint256, thirdUint256],
{
'0': secondUint256,
'1': { p1: '17', p2: ['234', '467456745457n', '0x56ec'] },
'2': [54, 55, 56],
'3': 59,
},
[
{ '0': 251, '1': 40000 },
{ '0': 252, '1': 40001 },
],
];

const config: DecodeConfig = {
felt: String,
'core::felt252': String,
'core::integer::u8': Number,
'core::integer::u16': Number,
'core::integer::u64': Number,
'core::integer::u128': BigInt,
'core::starknet::contract_address::ContractAddress': String,
longText: String,
};

const cd: CallData = new CallData(compiledComplexSierra.abi);
const compiledDataFromObject: Calldata = cd.compile('constructor', myRawArgsObject);
const compiledDataFromArray: Calldata = cd.compile('constructor', myRawArgsArray);
const decompiledDataFromObject = cd.decompile('constructor', compiledDataFromObject, config);
const decompiledDataFromArray = cd.decompile(
'constructor',
compiledDataFromArray,
config,
true
);

expect(decompiledDataFromObject).toEqual(myRawArgsObject);
expect(decompiledDataFromArray).toEqual(myRawArgsArray);
});
});
7 changes: 7 additions & 0 deletions src/types/lib/contract/abi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Uint256 } from '..';

/** ABI */
export type Abi = ReadonlyArray<FunctionAbi | EventAbi | StructAbi | any>;

Expand Down Expand Up @@ -56,3 +58,8 @@ export type LegacyEvent = {
data: EventEntry[];
keys: EventEntry[];
};

type JSCDataType = StringConstructor | NumberConstructor | BigIntConstructor | BooleanConstructor;
export type DecodeConfig = {
[typeName: string]: JSCDataType;
};
20 changes: 20 additions & 0 deletions src/utils/cairoDataTypes/uint256.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,24 @@ export class CairoUint256 {
toApiRequest() {
return [CairoFelt(this.low), CairoFelt(this.high)];
}

/**
* Construct CairoUint256 from calldata
* @param calldata Array of two strings representing the low and high parts of a uint256.
*/
static fromCalldata(calldata: [string, string]): CairoUint256 {
if (calldata.length !== 2) {
AryanGodara marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(
'Calldata must contain exactly two elements for low and high parts of uint256.'
);
}

// Validate each part to ensure they are within the acceptable range.
const [low, high] = calldata;
const validatedLow = CairoUint256.validateProps(low, UINT_256_LOW_MIN.toString());
const validatedHigh = CairoUint256.validateProps(high, UINT_256_HIGH_MIN.toString());

// Construct a new instance based on the validated low and high parts.
return new CairoUint256(validatedLow.low, validatedHigh.high);
}
}
15 changes: 15 additions & 0 deletions src/utils/calldata/cairo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ export const isTypeResult = (type: string) => type.startsWith('core::result::Res
* @returns - Returns true if the value is a valid Uint type, otherwise false.
*/
export const isTypeUint = (type: string) => Object.values(Uint).includes(type as Uint);
/**
* Retrieves the Uint enum type for the given type string.
*
* @param {string} type - The type string to check against Uint types.
* @returns {(Uint | null)} - The corresponding Uint enum value or null if not found.
*/
export const getUintType = (type: string): string | undefined => {
for (const value of Object.values(Uint)) {
if (value === type) {
return value;
}
}

return undefined;
};
// Legacy Export
/**
* Checks if the given type is `uint256`.
Expand Down
Loading
Loading