starknet 4.16.0 → 4.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/__tests__/account.test.ts +2 -0
- package/__tests__/contract.test.ts +14 -2
- package/__tests__/defaultProvider.test.ts +4 -2
- package/__tests__/fixtures.ts +1 -1
- package/__tests__/rpcProvider.test.ts +2 -6
- package/dist/index.d.ts +8 -45
- package/dist/index.global.js +4128 -4106
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +199 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +199 -177
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +8 -45
- package/index.global.js +4128 -4106
- package/index.global.js.map +1 -1
- package/index.js +199 -177
- package/index.js.map +1 -1
- package/index.mjs +199 -177
- package/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/contract/contractFactory.ts +13 -6
- package/src/contract/default.ts +11 -226
- package/src/provider/default.ts +2 -2
- package/src/provider/interface.ts +2 -2
- package/src/utils/calldata.ts +250 -0
- package/www/docs/API/account.md +50 -24
- package/www/docs/API/contract.md +20 -6
- package/www/docs/API/contractFactory.md +7 -3
- package/www/docs/API/provider.md +126 -44
- package/www/docs/API/signer.md +14 -4
- package/www/docs/API/utils.md +23 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starknet",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.17.0",
|
|
4
4
|
"description": "JavaScript library for StarkNet",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -21,13 +21,14 @@
|
|
|
21
21
|
"build:esm": "tsup --clean false --format esm --platform node",
|
|
22
22
|
"build:iife": "tsup --clean false --format iife --platform browser",
|
|
23
23
|
"build:dts": "tsup --clean false --dts-only",
|
|
24
|
-
"pretest": "npm run lint",
|
|
24
|
+
"pretest": "npm run lint && npm run ts:check",
|
|
25
25
|
"test": "jest -i",
|
|
26
26
|
"posttest": "npm run format",
|
|
27
27
|
"test:watch": "jest --watch",
|
|
28
28
|
"docs": "cd www && npm run start",
|
|
29
29
|
"format": "prettier --loglevel warn --write \"**/*.{ts,js,md,yml,json}\"",
|
|
30
|
-
"lint": "eslint . --cache --fix --ext .ts"
|
|
30
|
+
"lint": "eslint . --cache --fix --ext .ts",
|
|
31
|
+
"ts:check": "tsc --noEmit --resolveJsonModule --project tsconfig.eslint.json"
|
|
31
32
|
},
|
|
32
33
|
"keywords": [
|
|
33
34
|
"starknet",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert';
|
|
2
2
|
|
|
3
3
|
import { AccountInterface } from '../account';
|
|
4
|
-
import { Abi, CompiledContract,
|
|
4
|
+
import { Abi, CompiledContract, FunctionAbi } from '../types';
|
|
5
|
+
import { CheckCallData } from '../utils/calldata';
|
|
5
6
|
import { Contract } from './default';
|
|
6
7
|
|
|
7
8
|
export class ContractFactory {
|
|
@@ -13,6 +14,8 @@ export class ContractFactory {
|
|
|
13
14
|
|
|
14
15
|
account: AccountInterface;
|
|
15
16
|
|
|
17
|
+
private checkCalldata: CheckCallData;
|
|
18
|
+
|
|
16
19
|
constructor(
|
|
17
20
|
compiledContract: CompiledContract,
|
|
18
21
|
classHash: string,
|
|
@@ -23,19 +26,23 @@ export class ContractFactory {
|
|
|
23
26
|
this.compiledContract = compiledContract;
|
|
24
27
|
this.account = account;
|
|
25
28
|
this.classHash = classHash;
|
|
29
|
+
this.checkCalldata = new CheckCallData(abi);
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Deploys contract and returns new instance of the Contract
|
|
30
34
|
*
|
|
31
|
-
* @param
|
|
35
|
+
* @param args - Array of the constructor arguments for deployment
|
|
32
36
|
* @param addressSalt (optional) - Address Salt for deployment
|
|
33
37
|
* @returns deployed Contract
|
|
34
38
|
*/
|
|
35
|
-
public async deploy(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
public async deploy(args: Array<any> = [], addressSalt?: string | undefined): Promise<Contract> {
|
|
40
|
+
this.checkCalldata.validateMethodAndArgs('DEPLOY', 'constructor', args);
|
|
41
|
+
const { inputs } = this.abi.find((abi) => abi.type === 'constructor') as FunctionAbi;
|
|
42
|
+
|
|
43
|
+
// compile calldata
|
|
44
|
+
const constructorCalldata = this.checkCalldata.compileCalldata(args, inputs);
|
|
45
|
+
|
|
39
46
|
const {
|
|
40
47
|
deploy: { contract_address, transaction_hash },
|
|
41
48
|
} = await this.account.declareDeploy({
|
package/src/contract/default.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
AsyncContractFunction,
|
|
11
11
|
BlockTag,
|
|
12
12
|
Call,
|
|
13
|
-
Calldata,
|
|
14
13
|
ContractFunction,
|
|
15
14
|
FunctionAbi,
|
|
16
15
|
InvokeFunctionResponse,
|
|
@@ -19,7 +18,8 @@ import {
|
|
|
19
18
|
Result,
|
|
20
19
|
StructAbi,
|
|
21
20
|
} from '../types';
|
|
22
|
-
import {
|
|
21
|
+
import { CheckCallData } from '../utils/calldata';
|
|
22
|
+
import { BigNumberish, toBN } from '../utils/number';
|
|
23
23
|
import { CallOptions, ContractInterface } from './interface';
|
|
24
24
|
|
|
25
25
|
function parseFelt(candidate: string): BN {
|
|
@@ -121,6 +121,8 @@ export class Contract implements ContractInterface {
|
|
|
121
121
|
|
|
122
122
|
readonly [key: string]: AsyncContractFunction | any;
|
|
123
123
|
|
|
124
|
+
private checkCalldata: CheckCallData;
|
|
125
|
+
|
|
124
126
|
/**
|
|
125
127
|
* Contract class to handle contract methods
|
|
126
128
|
*
|
|
@@ -145,6 +147,7 @@ export class Contract implements ContractInterface {
|
|
|
145
147
|
}),
|
|
146
148
|
{}
|
|
147
149
|
);
|
|
150
|
+
this.checkCalldata = new CheckCallData(abi);
|
|
148
151
|
|
|
149
152
|
Object.defineProperty(this, 'functions', {
|
|
150
153
|
enumerable: true,
|
|
@@ -240,11 +243,11 @@ export class Contract implements ContractInterface {
|
|
|
240
243
|
assert(this.address !== null, 'contract is not connected to an address');
|
|
241
244
|
|
|
242
245
|
// validate method and args
|
|
243
|
-
this.validateMethodAndArgs('CALL', method, args);
|
|
246
|
+
this.checkCalldata.validateMethodAndArgs('CALL', method, args);
|
|
244
247
|
const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
|
|
245
248
|
|
|
246
249
|
// compile calldata
|
|
247
|
-
const calldata = this.compileCalldata(args, inputs);
|
|
250
|
+
const calldata = this.checkCalldata.compileCalldata(args, inputs);
|
|
248
251
|
return this.providerOrAccount
|
|
249
252
|
.callContract(
|
|
250
253
|
{
|
|
@@ -265,7 +268,7 @@ export class Contract implements ContractInterface {
|
|
|
265
268
|
// ensure contract is connected
|
|
266
269
|
assert(this.address !== null, 'contract is not connected to an address');
|
|
267
270
|
// validate method and args
|
|
268
|
-
this.validateMethodAndArgs('INVOKE', method, args);
|
|
271
|
+
this.checkCalldata.validateMethodAndArgs('INVOKE', method, args);
|
|
269
272
|
|
|
270
273
|
const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
|
|
271
274
|
const inputsLength = inputs.reduce((acc, input) => {
|
|
@@ -281,7 +284,7 @@ export class Contract implements ContractInterface {
|
|
|
281
284
|
);
|
|
282
285
|
}
|
|
283
286
|
// compile calldata
|
|
284
|
-
const calldata = this.compileCalldata(args, inputs);
|
|
287
|
+
const calldata = this.checkCalldata.compileCalldata(args, inputs);
|
|
285
288
|
|
|
286
289
|
const invocation = {
|
|
287
290
|
contractAddress: this.address,
|
|
@@ -318,7 +321,7 @@ export class Contract implements ContractInterface {
|
|
|
318
321
|
assert(this.address !== null, 'contract is not connected to an address');
|
|
319
322
|
|
|
320
323
|
// validate method and args
|
|
321
|
-
this.validateMethodAndArgs('INVOKE', method, args);
|
|
324
|
+
this.checkCalldata.validateMethodAndArgs('INVOKE', method, args);
|
|
322
325
|
const invocation = this.populateTransaction[method](...args);
|
|
323
326
|
if ('estimateInvokeFee' in this.providerOrAccount) {
|
|
324
327
|
return this.providerOrAccount.estimateInvokeFee(invocation);
|
|
@@ -331,167 +334,10 @@ export class Contract implements ContractInterface {
|
|
|
331
334
|
return {
|
|
332
335
|
contractAddress: this.address,
|
|
333
336
|
entrypoint: method,
|
|
334
|
-
calldata: this.compileCalldata(args, inputs),
|
|
337
|
+
calldata: this.checkCalldata.compileCalldata(args, inputs),
|
|
335
338
|
};
|
|
336
339
|
}
|
|
337
340
|
|
|
338
|
-
/**
|
|
339
|
-
* Deep parse of the object that has been passed to the method
|
|
340
|
-
*
|
|
341
|
-
* @param struct - struct that needs to be calculated
|
|
342
|
-
* @return {number} - number of members for the given struct
|
|
343
|
-
*/
|
|
344
|
-
private calculateStructMembers(struct: string): number {
|
|
345
|
-
return this.structs[struct].members.reduce((acc, member) => {
|
|
346
|
-
if (member.type === 'felt') {
|
|
347
|
-
return acc + 1;
|
|
348
|
-
}
|
|
349
|
-
return acc + this.calculateStructMembers(member.type);
|
|
350
|
-
}, 0);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Validates if all arguments that are passed to the method are corresponding to the ones in the abi
|
|
355
|
-
*
|
|
356
|
-
* @param type - type of the method
|
|
357
|
-
* @param method - name of the method
|
|
358
|
-
* @param args - arguments that are passed to the method
|
|
359
|
-
*/
|
|
360
|
-
protected validateMethodAndArgs(type: 'INVOKE' | 'CALL', method: string, args: Array<any> = []) {
|
|
361
|
-
// ensure provided method exists
|
|
362
|
-
const invocableFunctionNames = this.abi
|
|
363
|
-
.filter((abi) => {
|
|
364
|
-
if (abi.type !== 'function') return false;
|
|
365
|
-
const isView = abi.stateMutability === 'view';
|
|
366
|
-
return type === 'INVOKE' ? !isView : isView;
|
|
367
|
-
})
|
|
368
|
-
.map((abi) => abi.name);
|
|
369
|
-
assert(
|
|
370
|
-
invocableFunctionNames.includes(method),
|
|
371
|
-
`${type === 'INVOKE' ? 'invocable' : 'viewable'} method not found in abi`
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
// ensure args match abi type
|
|
375
|
-
const methodAbi = this.abi.find(
|
|
376
|
-
(abi) => abi.name === method && abi.type === 'function'
|
|
377
|
-
) as FunctionAbi;
|
|
378
|
-
let argPosition = 0;
|
|
379
|
-
methodAbi.inputs.forEach((input) => {
|
|
380
|
-
if (/_len$/.test(input.name)) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
if (input.type === 'felt') {
|
|
384
|
-
assert(
|
|
385
|
-
typeof args[argPosition] === 'string' ||
|
|
386
|
-
typeof args[argPosition] === 'number' ||
|
|
387
|
-
args[argPosition] instanceof BN,
|
|
388
|
-
`arg ${input.name} should be a felt (string, number, BigNumber)`
|
|
389
|
-
);
|
|
390
|
-
argPosition += 1;
|
|
391
|
-
} else if (input.type in this.structs && typeof args[argPosition] === 'object') {
|
|
392
|
-
if (Array.isArray(args[argPosition])) {
|
|
393
|
-
const structMembersLength = this.calculateStructMembers(input.type);
|
|
394
|
-
assert(
|
|
395
|
-
args[argPosition].length === structMembersLength,
|
|
396
|
-
`arg should be of length ${structMembersLength}`
|
|
397
|
-
);
|
|
398
|
-
} else {
|
|
399
|
-
this.structs[input.type].members.forEach(({ name }) => {
|
|
400
|
-
assert(
|
|
401
|
-
Object.keys(args[argPosition]).includes(name),
|
|
402
|
-
`arg should have a property ${name}`
|
|
403
|
-
);
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
argPosition += 1;
|
|
407
|
-
} else {
|
|
408
|
-
assert(Array.isArray(args[argPosition]), `arg ${input.name} should be an Array`);
|
|
409
|
-
if (input.type === 'felt*') {
|
|
410
|
-
args[argPosition].forEach((felt: BigNumberish) => {
|
|
411
|
-
assert(
|
|
412
|
-
typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
|
|
413
|
-
`arg ${input.name} should be an array of string, number or BigNumber`
|
|
414
|
-
);
|
|
415
|
-
});
|
|
416
|
-
argPosition += 1;
|
|
417
|
-
} else if (/\(felt/.test(input.type)) {
|
|
418
|
-
const tupleLength = input.type.split(',').length;
|
|
419
|
-
assert(
|
|
420
|
-
args[argPosition].length === tupleLength,
|
|
421
|
-
`arg ${input.name} should have ${tupleLength} elements in tuple`
|
|
422
|
-
);
|
|
423
|
-
args[argPosition].forEach((felt: BigNumberish) => {
|
|
424
|
-
assert(
|
|
425
|
-
typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
|
|
426
|
-
`arg ${input.name} should be an array of string, number or BigNumber`
|
|
427
|
-
);
|
|
428
|
-
});
|
|
429
|
-
argPosition += 1;
|
|
430
|
-
} else {
|
|
431
|
-
const arrayType = input.type.replace('*', '');
|
|
432
|
-
args[argPosition].forEach((struct: any) => {
|
|
433
|
-
this.structs[arrayType].members.forEach(({ name }) => {
|
|
434
|
-
if (Array.isArray(struct)) {
|
|
435
|
-
const structMembersLength = this.calculateStructMembers(arrayType);
|
|
436
|
-
assert(
|
|
437
|
-
struct.length === structMembersLength,
|
|
438
|
-
`arg should be of length ${structMembersLength}`
|
|
439
|
-
);
|
|
440
|
-
} else {
|
|
441
|
-
assert(
|
|
442
|
-
Object.keys(struct).includes(name),
|
|
443
|
-
`arg ${input.name} should be an array of ${arrayType}`
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
argPosition += 1;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Deep parse of the object that has been passed to the method
|
|
456
|
-
*
|
|
457
|
-
* @param element - element that needs to be parsed
|
|
458
|
-
* @param type - name of the method
|
|
459
|
-
* @return {string | string[]} - parsed arguments in format that contract is expecting
|
|
460
|
-
*/
|
|
461
|
-
|
|
462
|
-
protected parseCalldataValue(
|
|
463
|
-
element: ParsedStruct | BigNumberish | BigNumberish[],
|
|
464
|
-
type: string
|
|
465
|
-
): string | string[] {
|
|
466
|
-
if (element === undefined) {
|
|
467
|
-
throw Error('Missing element in calldata');
|
|
468
|
-
}
|
|
469
|
-
if (Array.isArray(element)) {
|
|
470
|
-
const structMemberNum = this.calculateStructMembers(type);
|
|
471
|
-
if (element.length !== structMemberNum) {
|
|
472
|
-
throw Error('Missing element in calldata');
|
|
473
|
-
}
|
|
474
|
-
return element.map((el) => toFelt(el));
|
|
475
|
-
}
|
|
476
|
-
// checking if the passed element is struct or element in struct
|
|
477
|
-
if (this.structs[type] && this.structs[type].members.length) {
|
|
478
|
-
// going through all the members of the struct and parsing the value
|
|
479
|
-
return this.structs[type].members.reduce((acc, member: AbiEntry) => {
|
|
480
|
-
// if the member of the struct is another struct this will return array of the felts if not it will be single felt
|
|
481
|
-
// TODO: refactor types so member name can be used as keyof ParsedStruct
|
|
482
|
-
/* @ts-ignore */
|
|
483
|
-
const parsedData = this.parseCalldataValue(element[member.name], member.type);
|
|
484
|
-
if (typeof parsedData === 'string') {
|
|
485
|
-
acc.push(parsedData);
|
|
486
|
-
} else {
|
|
487
|
-
acc.push(...parsedData);
|
|
488
|
-
}
|
|
489
|
-
return acc;
|
|
490
|
-
}, [] as string[]);
|
|
491
|
-
}
|
|
492
|
-
return toFelt(element as BigNumberish);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
341
|
/**
|
|
496
342
|
* Parse of the response elements that are converted to Object (Struct) by using the abi
|
|
497
343
|
*
|
|
@@ -514,67 +360,6 @@ export class Contract implements ContractInterface {
|
|
|
514
360
|
return parseFelt(responseIterator.next().value);
|
|
515
361
|
}
|
|
516
362
|
|
|
517
|
-
/**
|
|
518
|
-
* Parse one field of the calldata by using input field from the abi for that method
|
|
519
|
-
*
|
|
520
|
-
* @param args - value of the field
|
|
521
|
-
* @param input - input(field) information from the abi that will be used to parse the data
|
|
522
|
-
* @return {string | string[]} - parsed arguments in format that contract is expecting
|
|
523
|
-
*/
|
|
524
|
-
protected parseCalldataField(argsIterator: Iterator<any>, input: AbiEntry): string | string[] {
|
|
525
|
-
const { name, type } = input;
|
|
526
|
-
const { value } = argsIterator.next();
|
|
527
|
-
|
|
528
|
-
const parsedCalldata: string[] = [];
|
|
529
|
-
switch (true) {
|
|
530
|
-
case /\*/.test(type):
|
|
531
|
-
if (Array.isArray(value)) {
|
|
532
|
-
parsedCalldata.push(toFelt(value.length));
|
|
533
|
-
return (value as (BigNumberish | ParsedStruct)[]).reduce((acc, el) => {
|
|
534
|
-
if (/felt/.test(type)) {
|
|
535
|
-
acc.push(toFelt(el as BigNumberish));
|
|
536
|
-
} else {
|
|
537
|
-
acc.push(...this.parseCalldataValue(el, type.replace('*', '')));
|
|
538
|
-
}
|
|
539
|
-
return acc;
|
|
540
|
-
}, parsedCalldata);
|
|
541
|
-
}
|
|
542
|
-
throw Error(`Expected ${name} to be array`);
|
|
543
|
-
case type in this.structs:
|
|
544
|
-
return this.parseCalldataValue(value as ParsedStruct | BigNumberish[], type);
|
|
545
|
-
case /\(felt/.test(type):
|
|
546
|
-
if (Array.isArray(value)) {
|
|
547
|
-
return value.map((el) => toFelt(el as BigNumberish));
|
|
548
|
-
}
|
|
549
|
-
throw Error(`Expected ${name} to be array`);
|
|
550
|
-
default:
|
|
551
|
-
return toFelt(value as BigNumberish);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Parse the calldata by using input fields from the abi for that method
|
|
557
|
-
*
|
|
558
|
-
* @param args - arguments passed the the method
|
|
559
|
-
* @param inputs - list of inputs(fields) that are in the abi
|
|
560
|
-
* @return {Calldata} - parsed arguments in format that contract is expecting
|
|
561
|
-
*/
|
|
562
|
-
protected compileCalldata(args: Array<any>, inputs: AbiEntry[]): Calldata {
|
|
563
|
-
const argsIterator = args[Symbol.iterator]();
|
|
564
|
-
return inputs.reduce((acc, input) => {
|
|
565
|
-
if (/_len$/.test(input.name)) {
|
|
566
|
-
return acc;
|
|
567
|
-
}
|
|
568
|
-
const parsedData = this.parseCalldataField(argsIterator, input);
|
|
569
|
-
if (Array.isArray(parsedData)) {
|
|
570
|
-
acc.push(...parsedData);
|
|
571
|
-
} else {
|
|
572
|
-
acc.push(parsedData);
|
|
573
|
-
}
|
|
574
|
-
return acc;
|
|
575
|
-
}, [] as Calldata);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
363
|
/**
|
|
579
364
|
* Parse elements of the response and structuring them into one field by using output property from the abi for that method
|
|
580
365
|
*
|
package/src/provider/default.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class Provider implements ProviderInterface {
|
|
|
67
67
|
|
|
68
68
|
public async getClassAt(
|
|
69
69
|
contractAddress: string,
|
|
70
|
-
blockIdentifier
|
|
70
|
+
blockIdentifier?: BlockIdentifier
|
|
71
71
|
): Promise<ContractClass> {
|
|
72
72
|
return this.provider.getClassAt(contractAddress, blockIdentifier);
|
|
73
73
|
}
|
|
@@ -113,7 +113,7 @@ export class Provider implements ProviderInterface {
|
|
|
113
113
|
public async getStorageAt(
|
|
114
114
|
contractAddress: string,
|
|
115
115
|
key: BigNumberish,
|
|
116
|
-
blockIdentifier
|
|
116
|
+
blockIdentifier?: BlockIdentifier
|
|
117
117
|
): Promise<BigNumberish> {
|
|
118
118
|
return this.provider.getStorageAt(contractAddress, key, blockIdentifier);
|
|
119
119
|
}
|
|
@@ -150,7 +150,7 @@ export abstract class ProviderInterface {
|
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
152
|
* Invokes a function on starknet
|
|
153
|
-
* @deprecated This method wont be supported as soon as fees are mandatory
|
|
153
|
+
* @deprecated This method wont be supported as soon as fees are mandatory. Should not be used outside of Account class
|
|
154
154
|
*
|
|
155
155
|
* @param invocation the invocation object containing:
|
|
156
156
|
* - contractAddress - the address of the contract
|
|
@@ -187,7 +187,7 @@ export abstract class ProviderInterface {
|
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* Estimates the fee for a given INVOKE transaction
|
|
190
|
-
* @deprecated Please use getInvokeEstimateFee or getDeclareEstimateFee instead
|
|
190
|
+
* @deprecated Please use getInvokeEstimateFee or getDeclareEstimateFee instead. Should not be used outside of Account class
|
|
191
191
|
*
|
|
192
192
|
* @param invocation the invocation object containing:
|
|
193
193
|
* - contractAddress - the address of the contract
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import BN from 'bn.js';
|
|
2
|
+
import assert from 'minimalistic-assert';
|
|
3
|
+
|
|
4
|
+
import { Abi, AbiEntry, Calldata, FunctionAbi, ParsedStruct, StructAbi } from '../types';
|
|
5
|
+
import { BigNumberish, toFelt } from './number';
|
|
6
|
+
|
|
7
|
+
export class CheckCallData {
|
|
8
|
+
abi: Abi;
|
|
9
|
+
|
|
10
|
+
protected readonly structs: { [name: string]: StructAbi };
|
|
11
|
+
|
|
12
|
+
constructor(abi: Abi) {
|
|
13
|
+
this.abi = abi;
|
|
14
|
+
this.structs = abi
|
|
15
|
+
.filter((abiEntry) => abiEntry.type === 'struct')
|
|
16
|
+
.reduce(
|
|
17
|
+
(acc, abiEntry) => ({
|
|
18
|
+
...acc,
|
|
19
|
+
[abiEntry.name]: abiEntry,
|
|
20
|
+
}),
|
|
21
|
+
{}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse the calldata by using input fields from the abi for that method
|
|
27
|
+
*
|
|
28
|
+
* @param args - arguments passed the the method
|
|
29
|
+
* @param inputs - list of inputs(fields) that are in the abi
|
|
30
|
+
* @return {Calldata} - parsed arguments in format that contract is expecting
|
|
31
|
+
*/
|
|
32
|
+
public compileCalldata(args: Array<any>, inputs: AbiEntry[]): Calldata {
|
|
33
|
+
const argsIterator = args[Symbol.iterator]();
|
|
34
|
+
return inputs.reduce((acc, input) => {
|
|
35
|
+
if (/_len$/.test(input.name)) {
|
|
36
|
+
return acc;
|
|
37
|
+
}
|
|
38
|
+
const parsedData = this.parseCalldataField(argsIterator, input);
|
|
39
|
+
if (Array.isArray(parsedData)) {
|
|
40
|
+
acc.push(...parsedData);
|
|
41
|
+
} else {
|
|
42
|
+
acc.push(parsedData);
|
|
43
|
+
}
|
|
44
|
+
return acc;
|
|
45
|
+
}, [] as Calldata);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validates if all arguments that are passed to the method are corresponding to the ones in the abi
|
|
50
|
+
*
|
|
51
|
+
* @param type - type of the method
|
|
52
|
+
* @param method - name of the method
|
|
53
|
+
* @param args - arguments that are passed to the method
|
|
54
|
+
*/
|
|
55
|
+
public validateMethodAndArgs(
|
|
56
|
+
type: 'INVOKE' | 'CALL' | 'DEPLOY',
|
|
57
|
+
method: string,
|
|
58
|
+
args: Array<any> = []
|
|
59
|
+
) {
|
|
60
|
+
// ensure provided method exists
|
|
61
|
+
if (type !== 'DEPLOY') {
|
|
62
|
+
const invocableFunctionNames = this.abi
|
|
63
|
+
.filter((abi) => {
|
|
64
|
+
if (abi.type !== 'function') return false;
|
|
65
|
+
const isView = abi.stateMutability === 'view';
|
|
66
|
+
return type === 'INVOKE' ? !isView : isView;
|
|
67
|
+
})
|
|
68
|
+
.map((abi) => abi.name);
|
|
69
|
+
assert(
|
|
70
|
+
invocableFunctionNames.includes(method),
|
|
71
|
+
`${type === 'INVOKE' ? 'invocable' : 'viewable'} method not found in abi`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ensure args match abi type
|
|
76
|
+
const methodAbi = this.abi.find((abi) =>
|
|
77
|
+
type === 'DEPLOY'
|
|
78
|
+
? abi.name === method && abi.type === method
|
|
79
|
+
: abi.name === method && abi.type === 'function'
|
|
80
|
+
) as FunctionAbi;
|
|
81
|
+
let argPosition = 0;
|
|
82
|
+
methodAbi.inputs.forEach((input) => {
|
|
83
|
+
if (/_len$/.test(input.name)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (input.type === 'felt') {
|
|
87
|
+
assert(
|
|
88
|
+
typeof args[argPosition] === 'string' ||
|
|
89
|
+
typeof args[argPosition] === 'number' ||
|
|
90
|
+
args[argPosition] instanceof BN,
|
|
91
|
+
`arg ${input.name} should be a felt (string, number, BigNumber)`
|
|
92
|
+
);
|
|
93
|
+
argPosition += 1;
|
|
94
|
+
} else if (input.type in this.structs && typeof args[argPosition] === 'object') {
|
|
95
|
+
if (Array.isArray(args[argPosition])) {
|
|
96
|
+
const structMembersLength = this.calculateStructMembers(input.type);
|
|
97
|
+
assert(
|
|
98
|
+
args[argPosition].length === structMembersLength,
|
|
99
|
+
`arg should be of length ${structMembersLength}`
|
|
100
|
+
);
|
|
101
|
+
} else {
|
|
102
|
+
this.structs[input.type].members.forEach(({ name }) => {
|
|
103
|
+
assert(
|
|
104
|
+
Object.keys(args[argPosition]).includes(name),
|
|
105
|
+
`arg should have a property ${name}`
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
argPosition += 1;
|
|
110
|
+
} else {
|
|
111
|
+
assert(Array.isArray(args[argPosition]), `arg ${input.name} should be an Array`);
|
|
112
|
+
if (input.type === 'felt*') {
|
|
113
|
+
args[argPosition].forEach((felt: BigNumberish) => {
|
|
114
|
+
assert(
|
|
115
|
+
typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
|
|
116
|
+
`arg ${input.name} should be an array of string, number or BigNumber`
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
argPosition += 1;
|
|
120
|
+
} else if (/\(felt/.test(input.type)) {
|
|
121
|
+
const tupleLength = input.type.split(',').length;
|
|
122
|
+
assert(
|
|
123
|
+
args[argPosition].length === tupleLength,
|
|
124
|
+
`arg ${input.name} should have ${tupleLength} elements in tuple`
|
|
125
|
+
);
|
|
126
|
+
args[argPosition].forEach((felt: BigNumberish) => {
|
|
127
|
+
assert(
|
|
128
|
+
typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
|
|
129
|
+
`arg ${input.name} should be an array of string, number or BigNumber`
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
argPosition += 1;
|
|
133
|
+
} else {
|
|
134
|
+
const arrayType = input.type.replace('*', '');
|
|
135
|
+
args[argPosition].forEach((struct: any) => {
|
|
136
|
+
this.structs[arrayType].members.forEach(({ name }) => {
|
|
137
|
+
if (Array.isArray(struct)) {
|
|
138
|
+
const structMembersLength = this.calculateStructMembers(arrayType);
|
|
139
|
+
assert(
|
|
140
|
+
struct.length === structMembersLength,
|
|
141
|
+
`arg should be of length ${structMembersLength}`
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
assert(
|
|
145
|
+
Object.keys(struct).includes(name),
|
|
146
|
+
`arg ${input.name} should be an array of ${arrayType}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
argPosition += 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Parse one field of the calldata by using input field from the abi for that method
|
|
159
|
+
*
|
|
160
|
+
* @param args - value of the field
|
|
161
|
+
* @param input - input(field) information from the abi that will be used to parse the data
|
|
162
|
+
* @return {string | string[]} - parsed arguments in format that contract is expecting
|
|
163
|
+
*/
|
|
164
|
+
protected parseCalldataField(argsIterator: Iterator<any>, input: AbiEntry): string | string[] {
|
|
165
|
+
const { name, type } = input;
|
|
166
|
+
const { value } = argsIterator.next();
|
|
167
|
+
|
|
168
|
+
const parsedCalldata: string[] = [];
|
|
169
|
+
switch (true) {
|
|
170
|
+
case /\*/.test(type):
|
|
171
|
+
if (Array.isArray(value)) {
|
|
172
|
+
parsedCalldata.push(toFelt(value.length));
|
|
173
|
+
return (value as (BigNumberish | ParsedStruct)[]).reduce((acc, el) => {
|
|
174
|
+
if (/felt/.test(type)) {
|
|
175
|
+
acc.push(toFelt(el as BigNumberish));
|
|
176
|
+
} else {
|
|
177
|
+
acc.push(...this.parseCalldataValue(el, type.replace('*', '')));
|
|
178
|
+
}
|
|
179
|
+
return acc;
|
|
180
|
+
}, parsedCalldata);
|
|
181
|
+
}
|
|
182
|
+
throw Error(`Expected ${name} to be array`);
|
|
183
|
+
case type in this.structs:
|
|
184
|
+
return this.parseCalldataValue(value as ParsedStruct | BigNumberish[], type);
|
|
185
|
+
case /\(felt/.test(type):
|
|
186
|
+
if (Array.isArray(value)) {
|
|
187
|
+
return value.map((el) => toFelt(el as BigNumberish));
|
|
188
|
+
}
|
|
189
|
+
throw Error(`Expected ${name} to be array`);
|
|
190
|
+
default:
|
|
191
|
+
return toFelt(value as BigNumberish);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Deep parse of the object that has been passed to the method
|
|
197
|
+
*
|
|
198
|
+
* @param element - element that needs to be parsed
|
|
199
|
+
* @param type - name of the method
|
|
200
|
+
* @return {string | string[]} - parsed arguments in format that contract is expecting
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
protected parseCalldataValue(
|
|
204
|
+
element: ParsedStruct | BigNumberish | BigNumberish[],
|
|
205
|
+
type: string
|
|
206
|
+
): string | string[] {
|
|
207
|
+
if (element === undefined) {
|
|
208
|
+
throw Error('Missing element in calldata');
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(element)) {
|
|
211
|
+
const structMemberNum = this.calculateStructMembers(type);
|
|
212
|
+
if (element.length !== structMemberNum) {
|
|
213
|
+
throw Error('Missing element in calldata');
|
|
214
|
+
}
|
|
215
|
+
return element.map((el) => toFelt(el));
|
|
216
|
+
}
|
|
217
|
+
// checking if the passed element is struct or element in struct
|
|
218
|
+
if (this.structs[type] && this.structs[type].members.length) {
|
|
219
|
+
// going through all the members of the struct and parsing the value
|
|
220
|
+
return this.structs[type].members.reduce((acc, member: AbiEntry) => {
|
|
221
|
+
// if the member of the struct is another struct this will return array of the felts if not it will be single felt
|
|
222
|
+
// TODO: refactor types so member name can be used as keyof ParsedStruct
|
|
223
|
+
/* @ts-ignore */
|
|
224
|
+
const parsedData = this.parseCalldataValue(element[member.name], member.type);
|
|
225
|
+
if (typeof parsedData === 'string') {
|
|
226
|
+
acc.push(parsedData);
|
|
227
|
+
} else {
|
|
228
|
+
acc.push(...parsedData);
|
|
229
|
+
}
|
|
230
|
+
return acc;
|
|
231
|
+
}, [] as string[]);
|
|
232
|
+
}
|
|
233
|
+
return toFelt(element as BigNumberish);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Deep parse of the object that has been passed to the method
|
|
238
|
+
*
|
|
239
|
+
* @param struct - struct that needs to be calculated
|
|
240
|
+
* @return {number} - number of members for the given struct
|
|
241
|
+
*/
|
|
242
|
+
private calculateStructMembers(struct: string): number {
|
|
243
|
+
return this.structs[struct].members.reduce((acc, member) => {
|
|
244
|
+
if (member.type === 'felt') {
|
|
245
|
+
return acc + 1;
|
|
246
|
+
}
|
|
247
|
+
return acc + this.calculateStructMembers(member.type);
|
|
248
|
+
}, 0);
|
|
249
|
+
}
|
|
250
|
+
}
|