Skip to content

Commit

Permalink
Constructor detection via casm file (#354)
Browse files Browse the repository at this point in the history
* [skip ci]
-------------------------------------------------------------
Co-authored-by: FabijanC <fabijan.corak@gmail.com>
  • Loading branch information
taco-paco committed May 4, 2023
1 parent 2b4d271 commit 02c7681
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 48 deletions.
43 changes: 34 additions & 9 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
HEXADECIMAL_REGEX,
CHECK_STATUS_TIMEOUT
} from "../constants";
import { adaptLog, copyWithBigint, formatSpaces, isEntryAContructor, sleep, warn } from "../utils";
import { adaptLog, copyWithBigint, findConstructor, formatSpaces, sleep, warn } from "../utils";
import { adaptInputUtil, adaptOutputUtil } from "../adapt";
import { HardhatRuntimeEnvironment, Wallet } from "hardhat/types";
import { hash } from "starknet";
Expand Down Expand Up @@ -368,16 +368,41 @@ export class StarknetContractFactory {
this.metadataPath = config.metadataPath;
this.casmPath = config.casmPath;

// find constructor
for (const abiEntryName in this.abi) {
const abiEntry = this.abi[abiEntryName];
if (isEntryAContructor(abiEntry, config.hre.config.paths, this.casmPath)) {
this.constructorAbi = <starknet.CairoFunction>abiEntry;
break;
}
}
const constructorPredicate = this.resolveContructorPredicate();
this.constructorAbi = findConstructor(this.abi, constructorPredicate);
}

private resolveContructorPredicate(): (abiEntry: starknet.AbiEntry) => boolean {
if (!this.isCairo1()) {
return (abiEntry: starknet.AbiEntry): boolean => {
return abiEntry.type === "constructor";
};
}

const casmJson = JSON.parse(fs.readFileSync(this.casmPath, "utf-8"));
if (casmJson?.compiler_version.split(".")[0] !== "1") {
const msg = ".CASM json has to contain compiler_version '1.*.*'";
throw new StarknetPluginError(msg);
}

if (!casmJson?.entry_points_by_type?.CONSTRUCTOR) {
const msg = "Invalid .CASM structure: No CONSTRUCTOR in entry_points_by_type";
throw new StarknetPluginError(msg);
}

// Can be removed after new cairo release.
if (casmJson.entry_points_by_type.CONSTRUCTOR.length > 1) {
const msg = "There can be at most 1 constructor.";
throw new StarknetPluginError(msg);
}

// Can be simplified once starkware fixes multiple constructor issue.
// Precomputed selector can be used if only 'constructor' name allowed
const selector = casmJson.entry_points_by_type.CONSTRUCTOR[0].selector;
return (abiEntry: starknet.AbiEntry): boolean => {
return hash.getSelectorFromName(abiEntry.name) === selector;
};
}
/**
* Declare a contract class.
* @param options optional arguments to class declaration
Expand Down
51 changes: 12 additions & 39 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import { getContractFactoryUtil } from "./extend-utils";
import { compressProgram } from "starknet/utils/stark";
import { CompiledContract } from "starknet";
import JsonBigint from "json-bigint";
import { AbiEntry } from "./starknet-types";
import { Abi, AbiEntry } from "./starknet-types";
import * as starknet from "./starknet-types";

const globPromise = promisify(glob);
/**
Expand Down Expand Up @@ -368,44 +369,16 @@ export function estimatedFeeToMaxFee(amount?: bigint, overhead = 0.5) {
return (amount * BigInt(overhead)) / BigInt(100);
}

/**
* Checks if abi entry is a constructor or not
* @param entryType Abi entry to get name and type of
* @param paths Starknet project paths config
* @param casmPath Source artifact of a cairo1 contract
* @returns boolean
*/
export function isEntryAContructor(
entryType: AbiEntry,
paths: ProjectPathsConfig,
casmPath?: string
): boolean {
if (entryType.type === "constructor") return true;

if (casmPath) {
const { root, starknetArtifacts } = paths;
const dirPath = casmPath.replace(starknetArtifacts, "");
const sourcePath = path.dirname(path.join(root, dirPath));
// Check if path exists
if (!fs.existsSync(sourcePath)) return false;
// Check if contract contains constructor with the name
const file = fs.readFileSync(sourcePath).toString();
const lines = file
.split("\n")
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("//"));

for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes("#[constructor]")) {
// Check if next line is contains entry type name
const nextLine = lines[i + 1];
const pattern = new RegExp(`\\bfn\\s+${entryType.name}\\b`);
if (nextLine && pattern.test(nextLine)) {
return true;
}
}
export function findConstructor(
abi: Abi,
predicate: (entry: AbiEntry) => boolean
): starknet.CairoFunction {
for (const abiEntryName in abi) {
const abiEntry = abi[abiEntryName];
if (predicate(abiEntry)) {
return <starknet.CairoFunction>abiEntry;
}
}
return false;

return undefined;
}

0 comments on commit 02c7681

Please sign in to comment.