From 78e9f874e0d812a9d1c896846e5ec6c95a6cffca Mon Sep 17 00:00:00 2001 From: Philippe ROSTAN <81040730+PhilippeR26@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:49:21 +0200 Subject: [PATCH] feat: add isCairo1 utility methods --- __tests__/cairo1.test.ts | 8 ++++++++ __tests__/contract.test.ts | 9 ++++++++- src/contract/default.ts | 6 +++++- src/contract/interface.ts | 11 +++++++++++ src/utils/calldata/cairo.ts | 26 +++++++++++++++++++++++++- www/docs/guides/define_call_message.md | 8 ++++++++ 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/__tests__/cairo1.test.ts b/__tests__/cairo1.test.ts index 353d44b2c..40e249932 100644 --- a/__tests__/cairo1.test.ts +++ b/__tests__/cairo1.test.ts @@ -16,6 +16,7 @@ import { shortString, stark, } from '../src'; +import { isCairo1Abi } from '../src/utils/calldata/cairo'; import { starknetKeccak } from '../src/utils/selector'; import { compiledC1Account, @@ -71,6 +72,13 @@ describeIfDevnet('Cairo 1 Devnet', () => { expect(classResponse).toMatchSchemaRef('SierraContractClass'); }); + test('isCairo1', async () => { + const isContractCairo1 = cairo1Contract.isCairo1(); + expect(isContractCairo1).toBe(true); + const isAbiCairo1 = isCairo1Abi(cairo1Contract.abi); + expect(isAbiCairo1).toBe(true); + }); + test('Cairo 1 Contract Interaction - skip invoke validation & call parsing', async () => { const tx = await cairo1Contract.increase_balance( CallData.compile({ diff --git a/__tests__/contract.test.ts b/__tests__/contract.test.ts index a07770000..03cff4ae5 100644 --- a/__tests__/contract.test.ts +++ b/__tests__/contract.test.ts @@ -1,6 +1,6 @@ import { BigNumberish, Contract, ContractFactory, RawArgs, json, stark } from '../src'; import { CallData } from '../src/utils/calldata'; -import { felt, tuple, uint256 } from '../src/utils/calldata/cairo'; +import { felt, isCairo1Abi, tuple, uint256 } from '../src/utils/calldata/cairo'; import { getSelectorFromName } from '../src/utils/hash'; import { hexToDecimalString, toBigInt } from '../src/utils/num'; import { encodeShortString } from '../src/utils/shortString'; @@ -50,6 +50,13 @@ describe('contract module', () => { ); }); + test('isCairo1', async () => { + const isContractCairo1: boolean = erc20Contract.isCairo1(); + expect(isContractCairo1).toBe(false); + const isAbiCairo1: boolean = isCairo1Abi(erc20Contract.abi); + expect(isAbiCairo1).toBe(false); + }); + test('populate transaction for initial balance of that account', async () => { const res = await erc20Contract.populateTransaction.balanceOf(wallet); expect(res).toHaveProperty('contractAddress'); diff --git a/src/contract/default.ts b/src/contract/default.ts index ed339cefa..69a33a493 100644 --- a/src/contract/default.ts +++ b/src/contract/default.ts @@ -19,7 +19,7 @@ import { StructAbi, } from '../types'; import assert from '../utils/assert'; -import { CallData } from '../utils/calldata'; +import { CallData, cairo } from '../utils/calldata'; import { ContractInterface } from './interface'; export const splitArgsAndOptions = (args: ArgsOrCalldataWithOptions) => { @@ -320,4 +320,8 @@ export class Contract implements ContractInterface { calldata, }; } + + public isCairo1(): boolean { + return cairo.isCairo1Abi(this.abi); + } } diff --git a/src/contract/interface.ts b/src/contract/interface.ts index b7d599d47..e6b72466d 100644 --- a/src/contract/interface.ts +++ b/src/contract/interface.ts @@ -106,4 +106,15 @@ export abstract class ContractInterface { * @returns Invocation object */ public abstract populate(method: string, args?: ArgsOrCalldata): Invocation; + + /** + * tells if the contract comes from a Cairo 1 contract + * + * @returns TRUE if the contract comes from a Cairo1 contract + * @example + * ```typescript + * const isCairo1: boolean = myContract.isCairo1(); + * ``` + */ + public abstract isCairo1(): boolean; } diff --git a/src/utils/calldata/cairo.ts b/src/utils/calldata/cairo.ts index 204649265..342d31a92 100644 --- a/src/utils/calldata/cairo.ts +++ b/src/utils/calldata/cairo.ts @@ -1,4 +1,4 @@ -import { AbiStructs, BigNumberish, Uint256 } from '../../types'; +import { Abi, AbiStructs, BigNumberish, Uint256 } from '../../types'; import { isBigInt, isHex, isStringWholeNumber } from '../num'; import { encodeShortString, isShortString, isText } from '../shortString'; import { UINT_128_MAX, isUint256 } from '../uint256'; @@ -33,6 +33,30 @@ export const getArrayType = (type: string) => { return type.replace('*', ''); }; +/** + * tells if an ABI comes from a Cairo 1 contract + * + * @param abi representing the interface of a Cairo contract + * @returns TRUE if it is an ABI from a Cairo1 contract + * @example + * ```typescript + * const isCairo1: boolean = isCairo1Abi(myAbi: Abi); + * ``` + */ +export function isCairo1Abi(abi: Abi): boolean { + const firstFunction = abi.find((entry) => entry.type === 'function'); + if (!firstFunction) { + throw new Error(`Error in ABI. No function in ABI.`); + } + if (firstFunction.inputs.length) { + return isCairo1Type(firstFunction.inputs[0].type); + } + if (firstFunction.outputs.length) { + return isCairo1Type(firstFunction.outputs[0].type); + } + throw new Error(`Error in ABI. No input/output in function ${firstFunction.name}`); +} + /** * named tuple are described as js object {} * struct types are described as js object {} diff --git a/www/docs/guides/define_call_message.md b/www/docs/guides/define_call_message.md index c6112a9c6..3df451528 100644 --- a/www/docs/guides/define_call_message.md +++ b/www/docs/guides/define_call_message.md @@ -436,6 +436,14 @@ const amount = myContract.call(...); | Struct | ` func get_v() -> MyStruct` | MyStruct = { account: bigint, amount: bigint} | `const res: MyStruct = myContract.call(...` | | complex array | `func get_v() -> Array` | MyStruct[] | `const res: MyStruct[] = myContract.call(...` | +If you don't know if your Contract object is interacting with a Cairo 0 or a Cairo 1 contract, you have these methods: + +```typescript +import { cairo } from "starknet"; +const isCairo1: boolean = myContract.isCairo1(); +const isAbiCairo1: boolean = cairo.isCairo1Abi(myAbi); +``` + ## Parse configuration ### parseRequest