Skip to content

Commit 2dcc853

Browse files
committed
Support benchmark registry schema and dev UI ABI
1 parent 206e559 commit 2dcc853

6 files changed

Lines changed: 463 additions & 48 deletions

File tree

packages/cli/src/index.ts

Lines changed: 128 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,29 +1150,79 @@ function loadRepoSchema(relPath: string): unknown {
11501150
return JSON.parse(fs.readFileSync(found, 'utf-8'));
11511151
}
11521152

1153-
function compileSolidity(sourcePath: string, contents: string, contractName: string): { abi: unknown; bytecode: string; deployedBytecode: string } {
1154-
const input = {
1155-
language: 'Solidity',
1156-
sources: {
1157-
[sourcePath]: { content: contents }
1158-
},
1159-
settings: {
1160-
optimizer: { enabled: true, runs: 200 },
1161-
outputSelection: {
1162-
'*': {
1163-
'*': ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object']
1153+
type CompileProfile = 'default' | 'large-app' | 'auto';
1154+
const MAX_EVM_RUNTIME_CODE_BYTES = 24576;
1155+
1156+
function shouldRetryWithViaIR(errors: any[]): boolean {
1157+
const rendered = errors
1158+
.map((e: any) => String(e?.formattedMessage || e?.message || ''))
1159+
.join('\n');
1160+
return /Stack too deep|YulException/i.test(rendered);
1161+
}
1162+
1163+
function deployedCodeBytes(output: any, sourcePath: string, contractName: string): number | null {
1164+
const object = output?.contracts?.[sourcePath]?.[contractName]?.evm?.deployedBytecode?.object;
1165+
if (typeof object !== 'string' || object.length === 0) return null;
1166+
return object.length / 2;
1167+
}
1168+
1169+
function compileSolidity(sourcePath: string, contents: string, contractName: string, options: { profile?: CompileProfile } = {}): { abi: unknown; bytecode: string; deployedBytecode: string; viaIR: boolean } {
1170+
const profile = options.profile ?? 'auto';
1171+
const compileOnce = (viaIR: boolean) => {
1172+
const input = {
1173+
language: 'Solidity',
1174+
sources: {
1175+
[sourcePath]: { content: contents }
1176+
},
1177+
settings: {
1178+
optimizer: { enabled: true, runs: 200 },
1179+
viaIR,
1180+
outputSelection: {
1181+
'*': {
1182+
'*': ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object']
1183+
}
11641184
}
11651185
}
1166-
}
1186+
};
1187+
1188+
const output = JSON.parse(solc.compile(JSON.stringify(input)));
1189+
const errors = (output.errors || []).filter((e: any) => e.severity === 'error');
1190+
return { output, errors, viaIR };
11671191
};
11681192

1169-
const output = JSON.parse(solc.compile(JSON.stringify(input)));
1170-
const errors = (output.errors || []).filter((e: any) => e.severity === 'error');
1171-
if (errors.length > 0) {
1172-
const msg = errors.map((e: any) => e.formattedMessage || e.message).join('\n');
1193+
const attempts =
1194+
profile === 'default'
1195+
? [compileOnce(false)]
1196+
: profile === 'large-app'
1197+
? [compileOnce(true)]
1198+
: (() => {
1199+
const first = compileOnce(false);
1200+
const firstBytes = first.errors.length === 0 ? deployedCodeBytes(first.output, sourcePath, contractName) : null;
1201+
if (first.errors.length > 0 && shouldRetryWithViaIR(first.errors)) {
1202+
return [first, compileOnce(true)];
1203+
}
1204+
if (first.errors.length === 0 && firstBytes !== null && firstBytes > MAX_EVM_RUNTIME_CODE_BYTES) {
1205+
return [first, compileOnce(true)];
1206+
}
1207+
return [first];
1208+
})();
1209+
1210+
const successful = (() => {
1211+
if (attempts.length === 2 && attempts[0]!.errors.length === 0) {
1212+
const fallback = attempts[1]!;
1213+
if (fallback.errors.length === 0) {
1214+
return fallback;
1215+
}
1216+
}
1217+
return attempts.find((attempt) => attempt.errors.length === 0);
1218+
})();
1219+
if (!successful) {
1220+
const last = attempts[attempts.length - 1]!;
1221+
const msg = last.errors.map((e: any) => e.formattedMessage || e.message).join('\n');
11731222
throw new Error(`Solidity compile failed:\n${msg}`);
11741223
}
11751224

1225+
const output = successful.output;
11761226
const compiled = output.contracts?.[sourcePath]?.[contractName];
11771227
if (!compiled) {
11781228
throw new Error(`Solidity compile output missing ${contractName} in ${sourcePath}`);
@@ -1181,7 +1231,22 @@ function compileSolidity(sourcePath: string, contents: string, contractName: str
11811231
const abi = compiled.abi;
11821232
const bytecode = `0x${compiled.evm.bytecode.object}`;
11831233
const deployedBytecode = `0x${compiled.evm.deployedBytecode.object}`;
1184-
return { abi, bytecode, deployedBytecode };
1234+
return { abi, bytecode, deployedBytecode, viaIR: successful.viaIR };
1235+
}
1236+
1237+
function normalizeCompileProfile(value?: string): CompileProfile {
1238+
const normalized = String(value ?? 'auto').trim().toLowerCase();
1239+
if (normalized === 'default' || normalized === 'large-app' || normalized === 'auto') {
1240+
return normalized;
1241+
}
1242+
throw new Error(`Invalid compiler profile "${value}". Supported: auto, default, large-app`);
1243+
}
1244+
1245+
function compileProfileForLog(profile: CompileProfile, viaIR: boolean): string {
1246+
if (profile === 'auto') {
1247+
return viaIR ? 'auto(viaIR)' : 'auto';
1248+
}
1249+
return viaIR ? `${profile}(viaIR)` : profile;
11851250
}
11861251

11871252
function titleFromSlug(slug: string): string {
@@ -1911,7 +1976,15 @@ function startUiSiteServer(args: {
19111976
function buildFromSchema(
19121977
schema: ThsSchema,
19131978
outDir: string,
1914-
opts: { ui: boolean; quiet?: boolean; schemaPathForHints?: string; txMode?: string; relayBaseUrl?: string; targetChainId?: number }
1979+
opts: {
1980+
ui: boolean;
1981+
quiet?: boolean;
1982+
schemaPathForHints?: string;
1983+
txMode?: string;
1984+
relayBaseUrl?: string;
1985+
targetChainId?: number;
1986+
compileProfile?: CompileProfile;
1987+
}
19151988
): { outDir: string; uiBundleDir: string | null; uiSiteDir: string | null } {
19161989
const resolvedOutDir = path.resolve(outDir);
19171990
ensureDir(resolvedOutDir);
@@ -1923,12 +1996,14 @@ function buildFromSchema(
19231996

19241997
// 2) Compile (solc-js)
19251998
const sourceRelPath = appSol.path.replace(/\\\\/g, '/');
1926-
const compiled = compileSolidity(sourceRelPath, appSol.contents, 'App');
1999+
const compileProfile = normalizeCompileProfile(opts.compileProfile);
2000+
const compiled = compileSolidity(sourceRelPath, appSol.contents, 'App', { profile: compileProfile });
19272001
const compiledArtifact = {
19282002
contractName: 'App',
19292003
abi: compiled.abi,
19302004
bytecode: compiled.bytecode,
1931-
deployedBytecode: compiled.deployedBytecode
2005+
deployedBytecode: compiled.deployedBytecode,
2006+
compilerProfile: compileProfileForLog(compileProfile, compiled.viaIR)
19322007
};
19332008
const compiledJson = JSON.stringify(compiledArtifact, null, 2);
19342009
const compiledOutPath = path.join(resolvedOutDir, 'compiled', 'App.json');
@@ -2026,7 +2101,8 @@ function buildFromSchema(
20262101
generatorVersion: '0.0.0',
20272102
toolchain: {
20282103
node: process.version.replace(/^v/, ''),
2029-
solc: solc.version()
2104+
solc: solc.version(),
2105+
compilerProfile: compileProfileForLog(compileProfile, compiled.viaIR)
20302106
},
20312107
release: {
20322108
releaseId: `rel_local_${Date.now()}`,
@@ -2743,8 +2819,9 @@ program
27432819
.argument('<schema>', 'Path to THS schema JSON file')
27442820
.option('--out <dir>', 'Output directory', 'artifacts')
27452821
.option('--no-ui', 'Do not generate UI output')
2822+
.option('--compiler-profile <profile>', 'Compiler profile (auto|default|large-app)', 'auto')
27462823
.option('--with-tests', 'Emit generated app test scaffold', false)
2747-
.action((schemaPath: string, opts: { out: string; ui: boolean; withTests: boolean }) => {
2824+
.action((schemaPath: string, opts: { out: string; ui: boolean; compilerProfile?: string; withTests: boolean }) => {
27482825
const input = readJsonFile(schemaPath);
27492826
const structural = validateThsStructural(input);
27502827
if (!structural.ok) {
@@ -2768,6 +2845,21 @@ program
27682845
ensureDir(contractsDir);
27692846
fs.writeFileSync(path.join(outDir, appSol.path), appSol.contents);
27702847

2848+
const sourceRelPath = appSol.path.replace(/\\\\/g, '/');
2849+
const compileProfile = normalizeCompileProfile(opts.compilerProfile);
2850+
const compiled = compileSolidity(sourceRelPath, appSol.contents, 'App', { profile: compileProfile });
2851+
const compiledArtifact = {
2852+
contractName: 'App',
2853+
abi: compiled.abi,
2854+
bytecode: compiled.bytecode,
2855+
deployedBytecode: compiled.deployedBytecode,
2856+
compilerProfile: compileProfileForLog(compileProfile, compiled.viaIR)
2857+
};
2858+
const compiledJson = JSON.stringify(compiledArtifact, null, 2);
2859+
const compiledOutPath = path.join(outDir, 'compiled', 'App.json');
2860+
ensureDir(path.dirname(compiledOutPath));
2861+
fs.writeFileSync(compiledOutPath, compiledJson);
2862+
27712863
// Also persist an immutable copy of the schema input alongside the artifacts.
27722864
ensureDir(outDir);
27732865
fs.writeFileSync(path.join(outDir, 'schema.json'), JSON.stringify(schema, null, 2));
@@ -2781,6 +2873,10 @@ program
27812873
ensureDir(path.dirname(thsTsPath));
27822874
fs.writeFileSync(thsTsPath, renderThsTs(schema));
27832875

2876+
const compiledPublicPath = path.join(uiDir, 'public', 'compiled', 'App.json');
2877+
ensureDir(path.dirname(compiledPublicPath));
2878+
fs.writeFileSync(compiledPublicPath, compiledJson);
2879+
27842880
if (opts.withTests) {
27852881
addGeneratedUiTestScaffold(uiDir, templateDir);
27862882
console.log(`Wrote ui/tests/ (generated app test scaffold)`);
@@ -2789,6 +2885,7 @@ program
27892885
console.log(`Wrote ui/ (Next.js static export template)`);
27902886
}
27912887

2888+
console.log(`Wrote compiled/App.json`);
27922889
console.log(`Wrote ${appSol.path}`);
27932890
});
27942891

@@ -2797,14 +2894,16 @@ program
27972894
.argument('<schema>', 'Path to THS schema JSON file')
27982895
.option('--out <dir>', 'Output directory', 'artifacts')
27992896
.option('--no-ui', 'Do not generate/build UI bundle')
2897+
.option('--compiler-profile <profile>', 'Compiler profile (auto|default|large-app)', 'auto')
28002898
.option('--tx-mode <mode>', 'Transaction mode (auto|userPays|sponsored)', 'auto')
28012899
.option('--relay-base-url <url>', 'Relay base URL for sponsored mode', '/__tokenhost/relay')
2802-
.action((schemaPath: string, opts: { out: string; ui: boolean; txMode?: string; relayBaseUrl?: string }) => {
2900+
.action((schemaPath: string, opts: { out: string; ui: boolean; compilerProfile?: string; txMode?: string; relayBaseUrl?: string }) => {
28032901
try {
28042902
const schema = loadThsSchemaOrThrow(schemaPath);
28052903
buildFromSchema(schema, opts.out, {
28062904
ui: opts.ui,
28072905
schemaPathForHints: schemaPath,
2906+
compileProfile: normalizeCompileProfile(opts.compilerProfile),
28082907
txMode: opts.txMode,
28092908
relayBaseUrl: opts.relayBaseUrl
28102909
});
@@ -2835,6 +2934,7 @@ program
28352934
.option('--port <n>', 'Preview port', '3000')
28362935
.option('--interactive', 'Prompt for missing values', false)
28372936
.option('--dry-run', 'Print what would run and exit', false)
2937+
.option('--compiler-profile <profile>', 'Compiler profile (auto|default|large-app)', 'auto')
28382938
.option('--tx-mode <mode>', 'Transaction mode (auto|userPays|sponsored)', 'auto')
28392939
.option('--relay-base-url <url>', 'Relay base URL for sponsored mode', '/__tokenhost/relay')
28402940
.option('--no-start-anvil', 'Do not start anvil automatically (anvil chain only)')
@@ -2856,6 +2956,7 @@ program
28562956
port: string;
28572957
interactive: boolean;
28582958
dryRun: boolean;
2959+
compilerProfile?: string;
28592960
txMode?: string;
28602961
relayBaseUrl?: string;
28612962
startAnvil: boolean;
@@ -2939,11 +3040,13 @@ program
29393040
const { chainName, chain } = resolveKnownChain(opts.chain);
29403041
const rpcUrl = resolveRpcUrl(chainName, chain, opts.rpc);
29413042
const resolvedTxMode = resolveTxMode(opts.txMode, chain.id);
3043+
const compileProfile = normalizeCompileProfile(opts.compilerProfile);
29423044

29433045
if (opts.dryRun) {
29443046
console.log('Plan:');
29453047
console.log(` - validate: ${resolvedSchemaPath}`);
29463048
console.log(` - build: ${outDir}`);
3049+
console.log(` - compile: ${compileProfile}`);
29473050
if (chainName === 'anvil') {
29483051
console.log(` - anvil: ${opts.startAnvil ? `ensure running at ${rpcUrl}` : `SKIP (rpc=${rpcUrl})`}`);
29493052
}
@@ -2969,6 +3072,7 @@ program
29693072
console.log(`Out: ${path.relative(process.cwd(), outDir)}`);
29703073
console.log(`Chain: ${chainName} (${rpcUrl})`);
29713074
console.log(`Tx: ${resolvedTxMode}`);
3075+
console.log(`Compile:${compileProfile}`);
29723076
if (opts.preview) console.log(`UI: ${previewUrl}`);
29733077
console.log('');
29743078

@@ -2987,6 +3091,7 @@ program
29873091
ui: true,
29883092
quiet: true,
29893093
schemaPathForHints: resolvedSchemaPath,
3094+
compileProfile,
29903095
txMode: opts.txMode,
29913096
relayBaseUrl: opts.relayBaseUrl,
29923097
targetChainId: chain.id

0 commit comments

Comments
 (0)