Skip to content

Commit 9fdf54f

Browse files
authored
feat: isClassDeclared prepareInvocations, fix cairo0 test (#1211)
* feat: fix cairo0 test, feat provider.isClassDeclared, feat provider.createBulkInvocations * chore: rename ContractIdentifier to ContractClassIdentifier * chore: contractClassIdentifier fix * chore: prepareInvocations, LibraryError
1 parent e435903 commit 9fdf54f

File tree

4 files changed

+188
-46
lines changed

4 files changed

+188
-46
lines changed

__tests__/account.test.ts

Lines changed: 107 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,10 @@ describe('deploy and test Wallet', () => {
120120
const calldata = { publicKey: pubKey };
121121

122122
// declare account
123-
const declareAccount = await account.declare({
123+
const declareAccount = await account.declareIfNot({
124124
contract: compiledOpenZeppelinAccount,
125125
});
126126
const accountClassHash = declareAccount.class_hash;
127-
await account.waitForTransaction(declareAccount.transaction_hash);
128127

129128
// fund new account
130129
const tobeAccountAddress = hash.calculateContractAddressFromHash(
@@ -193,6 +192,9 @@ describe('deploy and test Wallet', () => {
193192
});
194193

195194
describe('simulate transaction - single transaction S0.11.2', () => {
195+
test('simulate empty invocations', async () => {
196+
await expect(account.simulateTransaction([])).rejects.toThrow(TypeError);
197+
});
196198
test('simulate INVOKE Cairo 0', async () => {
197199
const res = await account.simulateTransaction([
198200
{
@@ -245,37 +247,30 @@ describe('deploy and test Wallet', () => {
245247

246248
describeIfDevnet('declare tests only on devnet', () => {
247249
test('simulate DECLARE - Cairo 0 Contract', async () => {
248-
const res = await account.simulateTransaction([
250+
const invocation = await provider.prepareInvocations([
249251
{
250252
type: TransactionType.DECLARE,
251253
contract: compiledErc20,
252254
},
253255
]);
254-
expect(res).toMatchSchemaRef('SimulateTransactionResponse');
256+
if (invocation.length) {
257+
const res = await account.simulateTransaction(invocation);
258+
expect(res).toMatchSchemaRef('SimulateTransactionResponse');
259+
}
255260
});
256261
});
257262

258263
test('simulate DECLARE - Cairo 1 Contract - test if not already declared', async () => {
259-
const declareContractPayload = extractContractHashes({
260-
contract: compiledHelloSierra,
261-
casm: compiledHelloSierraCasm,
262-
});
263-
let skip = false;
264-
try {
265-
await account.getClassByHash(declareContractPayload.classHash);
266-
skip = true;
267-
} catch (error) {
268-
/* empty */
269-
}
264+
const invocation = await provider.prepareInvocations([
265+
{
266+
type: TransactionType.DECLARE,
267+
contract: compiledHelloSierra,
268+
casm: compiledHelloSierraCasm,
269+
},
270+
]);
270271

271-
if (!skip) {
272-
const res = await account.simulateTransaction([
273-
{
274-
type: TransactionType.DECLARE,
275-
contract: compiledHelloSierra,
276-
casm: compiledHelloSierraCasm,
277-
},
278-
]);
272+
if (invocation.length) {
273+
const res = await account.simulateTransaction(invocation);
279274
expect(res).toMatchSchemaRef('SimulateTransactionResponse');
280275
}
281276
});
@@ -625,6 +620,10 @@ describe('deploy and test Wallet', () => {
625620
expect(result).toMatchSchemaRef('EstimateFee');
626621
});
627622

623+
test('estimate fee bulk on empty invocations', async () => {
624+
await expect(account.estimateFeeBulk([])).rejects.toThrow(TypeError);
625+
});
626+
628627
test('estimate fee bulk invoke functions', async () => {
629628
// TODO @dhruvkelawala check expectation for feeTransactionVersion
630629
// const innerInvokeEstFeeSpy = jest.spyOn(account.signer, 'signTransaction');
@@ -696,22 +695,80 @@ describe('deploy and test Wallet', () => {
696695
});
697696

698697
describeIfDevnet('declare tests only on devnet', () => {
699-
test('declare, deploy & multi invoke functions', async () => {
700-
const res = await account.estimateFeeBulk([
701-
/* {
702-
// Cairo 1.1.0, if declared estimate error with can't redeclare same contract
703-
type: TransactionType.DECLARE,
704-
contract: compiledHelloSierra,
705-
casm: compiledHelloSierraCasm,
706-
}, */
698+
test('Manual: declare, deploy & multi invoke functions', async () => {
699+
/*
700+
* For Cairo0 and Cairo1 contracts re-declaration of the class throw an errors
701+
* as soo We first need to test is class is already declared
702+
*/
703+
const isDeclaredCairo0 = await account.isClassDeclared({
704+
classHash: '0x54328a1075b8820eb43caf0caa233923148c983742402dcfc38541dd843d01a',
705+
});
706+
707+
const hashes = extractContractHashes({
708+
contract: compiledHelloSierra,
709+
casm: compiledHelloSierraCasm,
710+
});
711+
712+
const isDeclaredCairo1 = await account.isClassDeclared({ classHash: hashes.classHash });
713+
714+
const invocations = [
707715
{
708-
// Cairo 0
709-
type: TransactionType.DECLARE,
716+
type: TransactionType.INVOKE,
717+
payload: [
718+
{
719+
contractAddress: erc20Address,
720+
entrypoint: 'approve',
721+
calldata: {
722+
address: erc20Address,
723+
amount: uint256(10),
724+
},
725+
},
726+
{
727+
contractAddress: erc20Address,
728+
entrypoint: 'transfer',
729+
calldata: [erc20.address, '10', '0'],
730+
},
731+
],
732+
},
733+
{
734+
type: TransactionType.DEPLOY,
710735
payload: {
711-
contract: compiledErc20,
712736
classHash: '0x54328a1075b8820eb43caf0caa233923148c983742402dcfc38541dd843d01a',
737+
constructorCalldata: ['Token', 'ERC20', account.address],
713738
},
714739
},
740+
...(!isDeclaredCairo0
741+
? [
742+
{
743+
// Cairo 0
744+
type: TransactionType.DECLARE,
745+
payload: {
746+
contract: compiledErc20,
747+
classHash: '0x54328a1075b8820eb43caf0caa233923148c983742402dcfc38541dd843d01a',
748+
},
749+
},
750+
]
751+
: []),
752+
...(!isDeclaredCairo1
753+
? [
754+
{
755+
// Cairo 1.1.0, if declared estimate error with can't redeclare same contract
756+
type: TransactionType.DECLARE,
757+
contract: compiledHelloSierra,
758+
casm: compiledHelloSierraCasm,
759+
},
760+
]
761+
: []),
762+
];
763+
764+
const res = await account.estimateFeeBulk(invocations);
765+
res.forEach((value) => {
766+
expect(value).toMatchSchemaRef('EstimateFee');
767+
});
768+
});
769+
770+
test('prepareInvocations: unordered declare, deploy & multi invoke', async () => {
771+
const invocations = await provider.prepareInvocations([
715772
{
716773
type: TransactionType.DEPLOY,
717774
payload: {
@@ -737,8 +794,23 @@ describe('deploy and test Wallet', () => {
737794
},
738795
],
739796
},
797+
{
798+
// Cairo 0
799+
type: TransactionType.DECLARE,
800+
payload: {
801+
contract: compiledErc20,
802+
classHash: '0x54328a1075b8820eb43caf0caa233923148c983742402dcfc38541dd843d01a',
803+
},
804+
},
805+
{
806+
// Cairo 1.1.0, if declared estimate error with can't redeclare same contract
807+
type: TransactionType.DECLARE,
808+
contract: compiledHelloSierra,
809+
casm: compiledHelloSierraCasm,
810+
},
740811
]);
741-
expect(res).toHaveLength(3);
812+
813+
const res = await account.estimateFeeBulk(invocations);
742814
res.forEach((value) => {
743815
expect(value).toMatchSchemaRef('EstimateFee');
744816
});

src/account/default.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// eslint-disable-next-line @typescript-eslint/no-unused-vars
22
import type { SPEC } from 'starknet-types-07';
3+
34
import {
45
OutsideExecutionCallerAny,
56
SNIP9_V1_INTERFACE_ID,
@@ -53,16 +54,16 @@ import {
5354
type OutsideExecutionOptions,
5455
type OutsideTransaction,
5556
} from '../types/outsideExecution';
56-
import {
57-
buildExecuteFromOutsideCallData,
58-
getOutsideCall,
59-
getTypedData,
60-
} from '../utils/outsideExecution';
6157
import { CallData } from '../utils/calldata';
6258
import { extractContractHashes, isSierra } from '../utils/contract';
6359
import { parseUDCEvent } from '../utils/events';
6460
import { calculateContractAddressFromHash } from '../utils/hash';
6561
import { isHex, toBigInt, toCairoBool, toHex } from '../utils/num';
62+
import {
63+
buildExecuteFromOutsideCallData,
64+
getOutsideCall,
65+
getTypedData,
66+
} from '../utils/outsideExecution';
6667
import { parseContract } from '../utils/provider';
6768
import { isString } from '../utils/shortString';
6869
import { supportsInterface } from '../utils/src5';
@@ -279,6 +280,7 @@ export class Account extends Provider implements AccountInterface {
279280
invocations: Invocations,
280281
details: UniversalDetails = {}
281282
): Promise<EstimateFeeBulk> {
283+
if (!invocations.length) throw TypeError('Invocations should be non-empty array');
282284
const { nonce, blockIdentifier, version, skipValidate } = details;
283285
const accountInvocations = await this.accountInvocationsFactory(invocations, {
284286
...v3Details(details),
@@ -304,6 +306,7 @@ export class Account extends Provider implements AccountInterface {
304306
invocations: Invocations,
305307
details: SimulateTransactionDetails = {}
306308
): Promise<SimulateTransactionResponse> {
309+
if (!invocations.length) throw TypeError('Invocations should be non-empty array');
307310
const { nonce, blockIdentifier, skipValidate = true, skipExecute, version } = details;
308311
const accountInvocations = await this.accountInvocationsFactory(invocations, {
309312
...v3Details(details),

src/provider/rpc.ts

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { SPEC } from 'starknet-types-07';
2+
23
import { RPC06, RPC07, RpcChannel } from '../channel';
34
import {
45
AccountInvocations,
@@ -8,12 +9,14 @@ import {
89
BlockTag,
910
Call,
1011
ContractClassResponse,
12+
ContractClassIdentifier,
1113
ContractVersion,
1214
DeclareContractTransaction,
1315
DeployAccountContractTransaction,
1416
GetBlockResponse,
1517
GetTxReceiptResponseWithoutHelper,
1618
Invocation,
19+
Invocations,
1720
InvocationsDetailsWithNonce,
1821
PendingBlock,
1922
PendingStateUpdate,
@@ -25,24 +28,24 @@ import {
2528
getContractVersionOptions,
2629
getEstimateFeeBulkOptions,
2730
getSimulateTransactionOptions,
28-
waitForTransactionOptions,
2931
type Signature,
3032
type TypedData,
33+
waitForTransactionOptions,
3134
} from '../types';
3235
import type { TransactionWithHash } from '../types/provider/spec';
3336
import assert from '../utils/assert';
37+
import { CallData } from '../utils/calldata';
3438
import { getAbiContractVersion } from '../utils/calldata/cairo';
35-
import { isSierra } from '../utils/contract';
39+
import { extractContractHashes, isSierra } from '../utils/contract';
40+
import { solidityUint256PackedKeccak256 } from '../utils/hash';
3641
import { isBigNumberish, toBigInt, toHex } from '../utils/num';
3742
import { wait } from '../utils/provider';
3843
import { RPCResponseParser } from '../utils/responseParser/rpc';
44+
import { formatSignature } from '../utils/stark';
3945
import { GetTransactionReceiptResponse, ReceiptTx } from '../utils/transactionReceipt';
46+
import { getMessageHash, validateTypedData } from '../utils/typedData';
4047
import { LibraryError } from './errors';
4148
import { ProviderInterface } from './interface';
42-
import { solidityUint256PackedKeccak256 } from '../utils/hash';
43-
import { CallData } from '../utils/calldata';
44-
import { formatSignature } from '../utils/stark';
45-
import { getMessageHash, validateTypedData } from '../utils/typedData';
4649

4750
export class RpcProvider implements ProviderInterface {
4851
public responseParser: RPCResponseParser;
@@ -570,4 +573,62 @@ export class RpcProvider implements ProviderInterface {
570573

571574
throw Error(`Signature verification Error: ${error}`);
572575
}
576+
577+
/**
578+
* Test if class is already declared from ContractClassIdentifier
579+
* Helper method using getClass
580+
* @param ContractClassIdentifier
581+
* @param blockIdentifier
582+
*/
583+
public async isClassDeclared(
584+
contractClassIdentifier: ContractClassIdentifier,
585+
blockIdentifier?: BlockIdentifier
586+
) {
587+
let classHash: string;
588+
if (!contractClassIdentifier.classHash && 'contract' in contractClassIdentifier) {
589+
const hashes = extractContractHashes(contractClassIdentifier);
590+
classHash = hashes.classHash;
591+
} else if (contractClassIdentifier.classHash) {
592+
classHash = contractClassIdentifier.classHash;
593+
} else {
594+
throw Error('contractClassIdentifier type not satisfied');
595+
}
596+
597+
try {
598+
const result = await this.getClass(classHash, blockIdentifier);
599+
return result instanceof Object;
600+
} catch (error) {
601+
if (error instanceof LibraryError) {
602+
return false;
603+
}
604+
throw error;
605+
}
606+
}
607+
608+
/**
609+
* Build bulk invocations with auto-detect declared class
610+
* 1. Test if class is declared if not declare it preventing already declared class error and not declared class errors
611+
* 2. Order declarations first
612+
* @param invocations
613+
*/
614+
public async prepareInvocations(invocations: Invocations) {
615+
const bulk: Invocations = [];
616+
// Build new ordered array
617+
// eslint-disable-next-line no-restricted-syntax
618+
for (const invocation of invocations) {
619+
if (invocation.type === TransactionType.DECLARE) {
620+
// Test if already declared
621+
// eslint-disable-next-line no-await-in-loop
622+
const isDeclared = await this.isClassDeclared(
623+
'payload' in invocation ? invocation.payload : invocation
624+
);
625+
if (!isDeclared) {
626+
bulk.unshift(invocation);
627+
}
628+
} else {
629+
bulk.push(invocation);
630+
}
631+
}
632+
return bulk;
633+
}
573634
}

src/types/lib/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { StarknetChainId } from '../../constants';
33
import { weierstrass } from '../../utils/ec';
44
import { EDataAvailabilityMode, ResourceBounds } from '../api';
55
import { CairoEnum } from '../cairoEnum';
6+
import { ValuesType } from '../helpers/valuesType';
67
import { CompiledContract, CompiledSierraCasm, ContractClass } from './contract';
78

89
export type WeierstrassSignatureType = weierstrass.SignatureType;
@@ -100,6 +101,11 @@ export type DeclareContractPayload = {
100101
compiledClassHash?: string;
101102
};
102103

104+
/**
105+
* DeclareContractPayload with classHash or contract defined
106+
*/
107+
export type ContractClassIdentifier = DeclareContractPayload | { classHash: string };
108+
103109
export type CompleteDeclareContractPayload = {
104110
contract: CompiledContract | string;
105111
classHash: string;

0 commit comments

Comments
 (0)