Repository: 0xs34n/starknet.js
Pull request #660 (2023-06-23 13:00:18)

0.11.2/redeclare ts
## Motivation and Resolution
Fix https://github.com/0xs34n/starknet.js/issues/588

## Usage related changes
Because of cairo0 back compatibility, we left formatting to be compressed program in response class, so it needs to be decompressed for declare and also typecast to legacy.
An example of redeclare cairo0 without a helper will be:
![Screenshot 2023-06-23 at 14 13 47](https://github.com/0xs34n/starknet.js/assets/923054/45734378-0ea1-4212-a4dc-ff480a1fdd15)

Redeclare cairo0 contract helper:
``contractClassResponseToLegacyCompiledContract();``

Example Cairo0 redeclare:
https://github.com/0xs34n/starknet.js/blob/a5b628fb0248a5c8caddc985cf93b6d53a69bf04/__tests__/account.test.ts#L75
Example Cairo1 redeclare:
https://github.com/0xs34n/starknet.js/blob/a5b628fb0248a5c8caddc985cf93b6d53a69bf04/__tests__/cairo1.test.ts#L60

- Fix ContractClassResponse type
- Extend isSierra() response type to help TS evaluate the resulting type
- [export utils: provider, selector, contract, events](https://github.com/0xs34n/starknet.js/pull/660/commits/00b7bdf756c516a1905ce1ecc9036472800989af)

## Checklist:

- [x] Performed a self-review of the code
- [x] Rebased to the last commit of the target branch (or merged it into my branch)
- [x] Linked the issues which this PR resolves
- [x] Documented the changes in code (API docs will be generated automatically)
- [x] Updated the tests
- [x] All tests are passing



Patch:
diff --git a/src/index.ts b/src/index.ts
index c5713c28..b1ededfb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -26,9 +26,13 @@ export * as shortString from './utils/shortString';
 export * as typedData from './utils/typedData';
 export * as ec from './utils/ec';
 export * as starknetId from './utils/starknetId';
+export * as provider from './utils/provider';
+export * as selector from './utils/selector';
 export * from './utils/address';
 export * from './utils/url';
 export * from './utils/calldata';
+export * from './utils/contract';
+export * from './utils/events';
 
 /**
  * Deprecated
diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts
index 1b9a9a61..1b8a7104 100644
--- a/src/provider/rpc.ts
+++ b/src/provider/rpc.ts
@@ -337,14 +337,13 @@ export class RpcProvider implements ProviderInterface {
     details: InvocationsDetailsWithNonce
   ): Promise<DeclareContractResponse> {
     if (!isSierra(contract)) {
-      const legacyContract = contract as LegacyContractClass;
       return this.fetchEndpoint('starknet_addDeclareTransaction', {
         declare_transaction: {
           type: RPC.TransactionType.DECLARE,
           contract_class: {
-            program: legacyContract.program,
-            entry_points_by_type: legacyContract.entry_points_by_type,
-            abi: legacyContract.abi,
+            program: contract.program,
+            entry_points_by_type: contract.entry_points_by_type,
+            abi: contract.abi,
           },
           version: toHex(transactionVersion),
           max_fee: toHex(details.maxFee || 0),
@@ -354,15 +353,14 @@ export class RpcProvider implements ProviderInterface {
         },
       });
     }
-    const sierraContract = contract as SierraContractClass;
     return this.fetchEndpoint('starknet_addDeclareTransaction', {
       declare_transaction: {
         type: RPC.TransactionType.DECLARE,
         contract_class: {
-          sierra_program: decompressProgram(sierraContract.sierra_program),
-          contract_class_version: sierraContract.contract_class_version,
-          entry_points_by_type: sierraContract.entry_points_by_type,
-          abi: sierraContract.abi,
+          sierra_program: decompressProgram(contract.sierra_program),
+          contract_class_version: contract.contract_class_version,
+          entry_points_by_type: contract.entry_points_by_type,
+          abi: contract.abi,
         },
         compiled_class_hash: compiledClassHash || '',
         version: toHex(transactionVersion_2),
diff --git a/src/types/lib/contract/index.ts b/src/types/lib/contract/index.ts
index dc603e5c..810d6550 100644
--- a/src/types/lib/contract/index.ts
+++ b/src/types/lib/contract/index.ts
@@ -7,10 +7,15 @@ import { CompiledSierra, SierraContractClass } from './sierra';
  * CompressedCompiledContract
  */
 export type ContractClass = LegacyContractClass | SierraContractClass;
+
 /**
  * format produced after compile .cairo to .json
  */
 export type CompiledContract = LegacyCompiledContract | CompiledSierra;
+
+/**
+ * Compressed or decompressed Cairo0 or Cairo1 Contract
+ */
 export type CairoContract = ContractClass | CompiledContract;
 
 // Basic elements
diff --git a/src/types/lib/contract/sierra.ts b/src/types/lib/contract/sierra.ts
index f44dc339..2973f9a8 100644
--- a/src/types/lib/contract/sierra.ts
+++ b/src/types/lib/contract/sierra.ts
@@ -18,7 +18,7 @@ export type CairoAssembly = {
  */
 export type CompiledSierra = {
   sierra_program: ByteCode;
-  sierra_program_debug_info: SierraProgramDebugInfo;
+  sierra_program_debug_info?: SierraProgramDebugInfo;
   contract_class_version: string;
   entry_points_by_type: SierraEntryPointsByType;
   abi: Abi;
diff --git a/src/types/provider/response.ts b/src/types/provider/response.ts
index d9102d67..f41cb300 100644
--- a/src/types/provider/response.ts
+++ b/src/types/provider/response.ts
@@ -6,13 +6,13 @@
 import { RPC } from '../api/rpc';
 import { Sequencer } from '../api/sequencer';
 import {
-  Abi,
   AllowArray,
   ByteCode,
   Call,
-  ContractClass,
+  CompiledSierra,
   DeclareContractPayload,
   DeployAccountContractPayload,
+  LegacyContractClass,
   RawCalldata,
   Signature,
   Status,
@@ -172,4 +172,12 @@ export interface StateUpdateResponse {
   };
 }
 
-export type ContractClassResponse = Omit<ContractClass, 'abi'> & { abi?: Abi };
+/**
+ * Standardized type
+ * Cairo0 program compressed and Cairo1 sierra_program decompressed
+ * abi Abi
+ * CompiledSierra without '.sierra_program_debug_info'
+ */
+export type ContractClassResponse =
+  | LegacyContractClass
+  | Omit<CompiledSierra, 'sierra_program_debug_info'>;
diff --git a/src/utils/contract.ts b/src/utils/contract.ts
index 21ef2391..cfb4fdb5 100644
--- a/src/utils/contract.ts
+++ b/src/utils/contract.ts
@@ -1,9 +1,19 @@
-import { CairoContract } from '../types/lib/contract/index';
+import { ContractClassResponse } from '../types';
+import {
+  CairoContract,
+  CompiledSierra,
+  LegacyCompiledContract,
+  LegacyContractClass,
+  SierraContractClass,
+} from '../types/lib/contract/index';
 import { CompleteDeclareContractPayload, DeclareContractPayload } from '../types/lib/index';
 import { computeCompiledClassHash, computeContractClassHash } from './hash';
 import { parse } from './json';
+import { decompressProgram } from './stark';
 
-export function isSierra(contract: CairoContract | string) {
+export function isSierra(
+  contract: CairoContract | string
+): contract is SierraContractClass | CompiledSierra {
   const compiledContract = typeof contract === 'string' ? parse(contract) : contract;
   return 'sierra_program' in compiledContract;
 }
@@ -29,3 +39,16 @@ export function extractContractHashes(
 
   return response;
 }
+
+/**
+ * Helper to redeclare response Cairo0 contract
+ * @param ccr ContractClassResponse
+ * @returns LegacyCompiledContract
+ */
+export function contractClassResponseToLegacyCompiledContract(ccr: ContractClassResponse) {
+  if (isSierra(ccr)) {
+    throw Error('ContractClassResponse need to be LegacyContractClass (cairo0 response class)');
+  }
+  const contract = ccr as LegacyContractClass;
+  return { ...contract, program: decompressProgram(contract.program) } as LegacyCompiledContract;
+}
diff --git a/src/utils/provider.ts b/src/utils/provider.ts
index b1fc7474..2da5e3f3 100644
--- a/src/utils/provider.ts
+++ b/src/utils/provider.ts
@@ -33,7 +33,6 @@ export function parseContract(contract: CompiledContract | string): ContractClas
   if (!isSierra(contract)) {
     return {
       ...parsedContract,
-      // TODO: Why do we gzip program object?
       ...('program' in parsedContract && { program: compressProgram(parsedContract.program) }),
     } as LegacyContractClass;
   }


Test patch:
diff --git a/__tests__/account.test.ts b/__tests__/account.test.ts
index 29a76bab..90a99222 100644
--- a/__tests__/account.test.ts
+++ b/__tests__/account.test.ts
@@ -1,5 +1,3 @@
-import { Signature } from 'micro-starknet';
-
 import typedDataExample from '../__mocks__/typedDataExample.json';
 import {
   Account,
@@ -8,17 +6,16 @@ import {
   Provider,
   TransactionStatus,
   TransactionType,
+  cairo,
+  contractClassResponseToLegacyCompiledContract,
   ec,
+  extractContractHashes,
   hash,
+  num,
+  parseUDCEvent,
+  shortString,
   stark,
 } from '../src';
-import { uint256 } from '../src/utils/calldata/cairo';
-import { extractContractHashes } from '../src/utils/contract';
-import { parseUDCEvent } from '../src/utils/events';
-import { calculateContractAddressFromHash, feeTransactionVersion } from '../src/utils/hash';
-import { cleanHex, hexToDecimalString, toBigInt, toHex } from '../src/utils/num';
-import { encodeShortString } from '../src/utils/shortString';
-import { randomAddress } from '../src/utils/stark';
 import {
   compiledErc20,
   compiledHelloSierra,
@@ -34,18 +31,25 @@ import {
 } from './fixtures';
 import { initializeMatcher } from './schema';
 
+const { cleanHex, hexToDecimalString, toBigInt, toHex } = num;
+const { encodeShortString } = shortString;
+const { randomAddress } = stark;
+const { uint256 } = cairo;
+const { Signature } = ec.starkCurve;
+
 describe('deploy and test Wallet', () => {
   const provider = new Provider(getTestProvider());
   const account = getTestAccount(provider);
   let erc20: Contract;
   let erc20Address: string;
   let dapp: Contract;
+  let dd: DeclareDeployUDCResponse;
 
   beforeAll(async () => {
     initializeMatcher(expect);
     expect(account).toBeInstanceOf(Account);
 
-    const declareDeploy = await account.declareAndDeploy({
+    dd = await account.declareAndDeploy({
       contract: compiledErc20,
       constructorCalldata: [
         encodeShortString('Token'),
@@ -54,7 +58,7 @@ describe('deploy and test Wallet', () => {
       ],
     });
 
-    erc20Address = declareDeploy.deploy.contract_address;
+    erc20Address = dd.deploy.contract_address;
     erc20 = new Contract(compiledErc20.abi, erc20Address, provider);
 
     const { balance } = await erc20.balanceOf(account.address);
@@ -68,6 +72,19 @@ describe('deploy and test Wallet', () => {
     dapp = new Contract(compiledTestDapp.abi, dappResponse.deploy.contract_address!, provider);
   });
 
+  xtest('validate TS for redeclare - skip testing', async () => {
+    const cc0 = await account.getClassAt(dd.deploy.address);
+    const cc0_1 = await account.getClassByHash(toHex(dd.declare.class_hash));
+
+    await account.declare({
+      contract: contractClassResponseToLegacyCompiledContract(cc0),
+    });
+
+    await account.declare({
+      contract: contractClassResponseToLegacyCompiledContract(cc0_1),
+    });
+  });
+
   test('estimateInvokeFee Cairo 0', async () => {
     const innerInvokeEstFeeSpy = jest.spyOn(account.signer, 'signTransaction');
     const result = await account.estimateInvokeFee({
@@ -77,7 +94,7 @@ describe('deploy and test Wallet', () => {
     });
 
     expect(result).toMatchSchemaRef('EstimateFee');
-    expect(innerInvokeEstFeeSpy.mock.calls[0][1].version).toBe(feeTransactionVersion);
+    expect(innerInvokeEstFeeSpy.mock.calls[0][1].version).toBe(hash.feeTransactionVersion);
     innerInvokeEstFeeSpy.mockClear();
   });
 
@@ -287,7 +304,7 @@ describe('deploy and test Wallet', () => {
       await provider.waitForTransaction(declareAccount.transaction_hash);
       const privateKey = stark.randomAddress();
       const starkKeyPub = ec.starkCurve.getStarkKey(privateKey);
-      const precalculatedAddress = calculateContractAddressFromHash(
+      const precalculatedAddress = hash.calculateContractAddressFromHash(
         starkKeyPub,
         accountClassHash,
         { publicKey: starkKeyPub },
@@ -595,7 +612,7 @@ describe('deploy and test Wallet', () => {
 
       const privateKey = stark.randomAddress();
       starkKeyPub = ec.starkCurve.getStarkKey(privateKey);
-      precalculatedAddress = calculateContractAddressFromHash(
+      precalculatedAddress = hash.calculateContractAddressFromHash(
         starkKeyPub,
         accountClassHash,
         { publicKey: starkKeyPub },
diff --git a/__tests__/cairo1.test.ts b/__tests__/cairo1.test.ts
index 40e24993..78206b2c 100644
--- a/__tests__/cairo1.test.ts
+++ b/__tests__/cairo1.test.ts
@@ -4,6 +4,7 @@ import {
   BigNumberish,
   CallData,
   Calldata,
+  CompiledSierra,
   Contract,
   DeclareDeployUDCResponse,
   RawArgsArray,
@@ -13,11 +14,10 @@ import {
   ec,
   hash,
   num,
+  selector,
   shortString,
   stark,
 } from '../src';
-import { isCairo1Abi } from '../src/utils/calldata/cairo';
-import { starknetKeccak } from '../src/utils/selector';
 import {
   compiledC1Account,
   compiledC1AccountCasm,
@@ -32,6 +32,10 @@ import {
 } from './fixtures';
 import { initializeMatcher } from './schema';
 
+const { uint256, tuple, isCairo1Abi } = cairo;
+const { toHex } = num;
+const { starknetKeccak } = selector;
+
 describeIfDevnet('Cairo 1 Devnet', () => {
   describe('API &  Contract interactions', () => {
     const provider = getTestProvider();
@@ -55,6 +59,21 @@ describeIfDevnet('Cairo 1 Devnet', () => {
       expect(cairo1Contract).toBeInstanceOf(Contract);
     });
 
+    xtest('validate TS for redeclare - skip testing', async () => {
+      const cc0 = await account.getClassAt(dd.deploy.address);
+      const cc0_1 = await account.getClassByHash(toHex(dd.declare.class_hash));
+
+      await account.declare({
+        contract: cc0 as CompiledSierra,
+        casm: compiledHelloSierraCasm,
+      });
+
+      await account.declare({
+        contract: cc0_1 as CompiledSierra,
+        casm: compiledHelloSierraCasm,
+      });
+    });
+
     test('deployContract Cairo1', async () => {
       const deploy = await account.deployContract({
         classHash: dd.deploy.classHash,
@@ -123,7 +142,7 @@ describeIfDevnet('Cairo 1 Devnet', () => {
       expect(result).toBe(2n ** 256n - 1n);
 
       // defined as struct
-      const result1 = await cairo1Contract.test_u256(cairo.uint256(2n ** 256n - 2n));
+      const result1 = await cairo1Contract.test_u256(uint256(2n ** 256n - 2n));
       expect(result1).toBe(2n ** 256n - 1n);
     });
 
@@ -200,7 +219,7 @@ describeIfDevnet('Cairo 1 Devnet', () => {
     });
 
     test('Cairo 1 Contract Interaction - echo flat un-named un-nested tuple', async () => {
-      const status = await cairo1Contract.echo_un_tuple(cairo.tuple(77, 123));
+      const status = await cairo1Contract.echo_un_tuple(tuple(77, 123));
       expect(Object.values(status)).toEqual([77n, 123n]);
     });
 
@@ -214,10 +233,10 @@ describeIfDevnet('Cairo 1 Devnet', () => {
 
       // uint256 defined as struct
       const status11 = await cairo1Contract.echo_array_u256([
-        cairo.uint256(123),
-        cairo.uint256(55),
-        cairo.uint256(77),
-        cairo.uint256(255),
+        uint256(123),
+        uint256(55),
+        uint256(77),
+        uint256(255),
       ]);
       expect(status11).toEqual([123n, 55n, 77n, 255n]);
 
@@ -305,7 +324,7 @@ describeIfDevnet('Cairo 1 Devnet', () => {
         1: true,
       });
 
-      const res1 = await cairo1Contract.tuple_echo(cairo.tuple([1, 2, 3], [4, 5, 6]));
+      const res1 = await cairo1Contract.tuple_echo(tuple([1, 2, 3], [4, 5, 6]));
       expect(res1).toEqual({
         0: [1n, 2n, 3n],
         1: [4n, 5n, 6n],
@@ -331,7 +350,7 @@ describeIfDevnet('Cairo 1 Devnet', () => {
         initial_supply: myFalseUint256,
         recipient: '0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a',
         decimals: 18,
-        tupoftup: cairo.tuple(cairo.tuple(34, '0x5e'), myFalseUint256),
+        tupoftup: tuple(tuple(34, '0x5e'), myFalseUint256),
         card: myOrder2bis,
         longText: 'Bug is back, for ever, here and everywhere',
         array1: [100, 101, 102],
@@ -342,9 +361,9 @@ describeIfDevnet('Cairo 1 Devnet', () => {
         ],
         array3: [myOrder2bis, myOrder2bis],
         array4: [myFalseUint256, myFalseUint256],
-        tuple1: cairo.tuple(40000n, myOrder2bis, [54, 55n, '0xae'], 'texte'),
+        tuple1: tuple(40000n, myOrder2bis, [54, 55n, '0xae'], 'texte'),
         name: 'niceToken',
-        array5: [cairo.tuple(251, 40000n), cairo.tuple(252, 40001n)],
+        array5: [tuple(251, 40000n), tuple(252, 40001n)],
       };
       const myRawArgsArray: RawArgsArray = [
         'niceToken',
@@ -503,7 +522,7 @@ describeIfDevnet('Cairo 1 Devnet', () => {
         entrypoint: 'transfer',
         calldata: {
           recipient: toBeAccountAddress,
-          amount: cairo.uint256(1_000_000_000_000_000),
+          amount: uint256(1_000_000_000_000_000),
         },
       });
       await account.waitForTransaction(transaction_hash);
@@ -575,7 +594,7 @@ describeIfSequencerTestnet2('Cairo1 Testnet2', () => {
     });
 
     test('Cairo 1 - uint256 struct', async () => {
-      const myUint256 = cairo.uint256(2n ** 256n - 2n);
+      const myUint256 = uint256(2n ** 256n - 2n);
       const result = await cairo1Contract.test_u256(myUint256);
       expect(result).toBe(2n ** 256n - 1n);
     });