diff --git a/.gitignore b/.gitignore index 993ca77..6d840fa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ logs *.log .env + npm-debug.log* yarn-debug.log* yarn-error.log* @@ -9,11 +10,16 @@ pnpm-debug.log* lerna-debug.log* codegen/*.ts +codegen/*.json codegen/abi/* codegen/pvm/* codegen/evm/* *.sha256.txt +# Benchmark artifacts +stats.db +benchmark/.ipynb_checkpoints/ + node_modules dist dist-ssr diff --git a/benchmark/contracts.ts b/benchmark/contracts.ts new file mode 100755 index 0000000..2e8efd4 --- /dev/null +++ b/benchmark/contracts.ts @@ -0,0 +1,125 @@ +#!/usr/bin/env -S deno run --env-file --allow-all +import { deploy as deployContract, env } from '../tools/lib/index.ts' +import { abis } from '../codegen/abis.ts' +import { logger } from '../utils/logger.ts' +import { + Artifacts, + build, + deleteChainData, + deploy, + execute, + ink, + rust, + solidity, +} from './lib.ts' +import { parseArgs } from '@std/cli' +import { parseEther } from 'viem' + +export const contracts: Artifacts = [ + { + id: 'Fibonacci', + srcs: [ + ink('fibonacci'), + rust('fibonacci'), + rust('fibonacci_u128'), + rust('fibonacci_u256'), + ...solidity('fibonacci.sol', 'Fibonacci'), + ], + deploy: (id, name, bytecode) => { + return deployContract({ + name: { id, name }, + bytecode, + args: [], + }) + }, + calls: [ + { + name: 'fib_10', + exec: async (address) => { + return await env.wallet.writeContract({ + address, + abi: abis.Fibonacci, + functionName: 'fibonacci', + args: [10], + }) + }, + }, + ], + }, + { + id: 'SimpleToken', + srcs: [ + ink('simple_token'), + rust('simple_token_no_alloc'), + ...solidity('simple_token.sol', 'SimpleToken'), + ], + deploy: (id, name, bytecode) => { + return deployContract({ + name: { id, name }, + bytecode, + args: [], + }) + }, + calls: [ + { + name: 'mint', + exec: (address) => { + return env.wallet.writeContract({ + address, + abi: abis.SimpleToken, + functionName: 'mint', + args: [ + env.wallet.account.address, + 10_000_000_000_000_000_000_000_000n, + ], + }) + }, + }, + { + name: 'transfer', + exec: async (address) => { + // fund destination first + await env.wallet.sendTransaction({ + to: '0x3d26c9637dFaB74141bA3C466224C0DBFDfF4A63', + value: parseEther('1'), + }) + + return env.wallet.writeContract({ + address, + abi: abis.SimpleToken, + functionName: 'transfer', + args: [ + '0x3d26c9637dFaB74141bA3C466224C0DBFDfF4A63', + 10_000_000_000_000_000_000_000_000n, + ], + }) + }, + }, + ], + }, +] + +const cli = parseArgs(Deno.args, { + boolean: ['build', 'execute', 'report', 'clean'], +}) + +if (cli.build) { + logger.info(`Building contracts...`) + await build(contracts) +} +if (cli.execute) { + logger.info(`Deleting existing data for chain: ${env.chain.name}`) + deleteChainData(env.chain.name) + + logger.info(`Deploying contracts...`) + await deploy(contracts) + + logger.info(`Executing contracts...`) + await execute(contracts) +} + +if (cli.report) { + logger.info(`Generating reports...`) + const { report } = await import('./reports.ts') + await report(contracts) +} diff --git a/benchmark/lib.ts b/benchmark/lib.ts new file mode 100644 index 0000000..2129f00 --- /dev/null +++ b/benchmark/lib.ts @@ -0,0 +1,341 @@ +import { Hex, TransactionReceipt } from 'viem' +import { env } from '../tools/lib/index.ts' +import { Abis } from '../codegen/abis.ts' +import { readBytecode } from '../utils/index.ts' +import { compile } from '../utils/build.ts' +import { join } from '@std/path' +import { logger } from '../utils/logger.ts' +import { Buffer } from 'node:buffer' +import { DatabaseSync } from 'node:sqlite' + +export const db = new DatabaseSync('stats.db') +db.exec( + ` +CREATE TABLE IF NOT EXISTS transactions ( + hash BLOB NOT NULL, + chain_name TEXT NOT NULL, + contract_id TEXT NOT NULL, + contract_name TEXT NOT NULL, + transaction_name TEXT NOT NULL, + gas_used INTEGER NOT NULL, + status INTEGER NOT NULL, + post_dispatch_ref_time INTEGER, + post_dispatch_pov INTEGER, + weight_consumed_ref_time INTEGER, + weight_consumed_proof_size INTEGER, + base_call_weight_ref_time INTEGER, + base_call_weight_proof_size INTEGER, + + PRIMARY KEY (hash, chain_name) +); + +CREATE TABLE IF NOT EXISTS transaction_steps ( + hash BLOB NOT NULL, + chain_name TEXT NOT NULL, + op TEXT NOT NULL, + gas_cost INTEGER NOT NULL, + weight_cost_ref_time INTEGER, + weight_cost_proof_size INTEGER, + PRIMARY KEY (hash, chain_name, op), + FOREIGN KEY (hash, chain_name) REFERENCES transactions(hash, chain_name) +); +`, +) + +export interface ContractInfo { + supportEvm(): boolean + getName(): string + getBytecode(): Hex + build(): Promise +} + +export function ink(name: string): ContractInfo { + return { + supportEvm() { + return false + }, + getName() { + return `${name}_ink` + }, + getBytecode() { + return readBytecode(`./ink/${name}/target/ink/${name}.polkavm`) + }, + async build() { + const cwd = join(import.meta.dirname!, '..', 'ink', name) + const cmd = new Deno.Command('cargo', { + args: ['contract', 'build', '--release'], + cwd, + stdout: 'inherit', + stderr: 'inherit', + }) + const result = await cmd.output() + if (!result.success) { + throw new Error(`Failed to build ink contract: ${name}`) + } + }, + } +} + +export function solidity(fileName: string, name: string): ContractInfo[] { + const bytecodes = { pvm: 'polkavm', evm: 'bin' } as const + let buildRun = false + + return Object.entries(bytecodes).map(([type, ext]) => ({ + supportEvm() { + return type == 'evm' + }, + getName() { + return `${name}_${type}` + }, + getBytecode() { + return readBytecode(`./codegen/${type}/${name}.${ext}`) + }, + async build() { + if (buildRun) return + buildRun = true + + const rootDir = join(import.meta.dirname!, '..') + const contractsDir = join(rootDir, 'contracts') + const sourceFilePath = join(contractsDir, fileName) + const sourceContent = Deno.readTextFileSync(sourceFilePath) + + // Compile with resolc for PVM + await compile({ + fileName, + sourceContent, + rootDir, + compiler: 'resolc', + }) + + // Compile with solc for EVM + await compile({ + fileName, + sourceContent, + rootDir, + compiler: 'solc', + generateAbi: true, + }) + }, + })) +} + +export function rust(name: string): ContractInfo { + return { + supportEvm() { + return false + }, + getName() { + return `${name}_rust` + }, + getBytecode() { + return readBytecode(`./rust/contracts/${name}.polkavm`) + }, + async build() { + const cwd = join(import.meta.dirname!, '..', 'rust', 'contracts') + const cmd = new Deno.Command('cargo', { + args: ['pvm-contract', 'build', '-b', name], + cwd, + stdout: 'inherit', + stderr: 'inherit', + }) + const result = await cmd.output() + if (!result.success) { + throw new Error(`Failed to build rust contract: ${name}`) + } + }, + } +} + +export type Artifacts = Array<{ + id: string + srcs: ContractInfo[] + deploy: ( + id: keyof Abis, + name: string, + bytecode: Hex, + ) => Promise + calls: Array<{ + name: string + exec: (address: Hex) => Promise + }> +}> + +export function deleteChainData(chainName: string) { + db.exec('BEGIN') + try { + db.prepare( + `DELETE FROM transaction_steps WHERE chain_name = ?`, + ).run(chainName) + + db.prepare( + `DELETE FROM transactions WHERE chain_name = ?`, + ).run(chainName) + + db.exec('COMMIT') + logger.debug(`Deleted all data for chain: ${chainName}`) + } catch (error) { + db.exec('ROLLBACK') + throw error + } +} + +export async function build(contracts: Artifacts) { + for (const artifact of contracts) { + for (const src of artifact.srcs) { + logger.debug(`Building ${src.getName()}...`) + await src.build() + } + } +} + +export async function deploy(contracts: Artifacts) { + for (const artifact of contracts) { + const srcs = env.chain.name == 'Geth' + ? artifact.srcs.filter((src) => src.supportEvm()) + : artifact.srcs + + for (const src of srcs) { + const contract = src.getName() + logger.debug(`Deploying ${contract}...`) + + const receipt = await artifact.deploy( + artifact.id as keyof Abis, + contract, + src.getBytecode(), + ) + + await updateStats( + artifact.id, + contract, + 'deploy', + receipt.transactionHash, + ) + } + } +} + +export async function execute(contracts: Artifacts) { + const addresses = await import('../codegen/addresses.ts') as Record< + string, + Hex + > + + for (const artifact of contracts) { + const srcs = env.chain.name == 'Geth' + ? artifact.srcs.filter((src) => src.supportEvm()) + : artifact.srcs + for (const src of srcs) { + for (const call of artifact.calls) { + const contract = src.getName() + logger.debug(`Executing ${call.name} on ${contract}...`) + const hash = await call.exec(addresses[contract]) + await updateStats(artifact.id, contract, call.name, hash) + } + } + } +} + +async function updateStats( + contractId: string, + contract: string, + action: string, + hash: Hex, +) { + const chainName = env.chain.name + + const receipt = await env.wallet.waitForTransactionReceipt({ + hash, + }) + + const weight = env.chain.name !== 'Geth' + ? await env.debugClient.postDispatchWeight(hash) + : undefined + + const trace = await env.debugClient.traceTransaction( + hash, + 'opcodeTracer', + { disableStack: true }, + ) + + const hashBytes = Buffer.from(hash.slice(2), 'hex') + const statusValue = receipt.status === 'success' ? 1 : 0 + const typedTrace = trace as { + structLogs?: Array<{ + op: string + gas: number + gasCost: number + weightCost?: { ref_time: number; proof_size: number } + }> + weightConsumed?: { ref_time: number; proof_size: number } + baseCallWeight?: { ref_time: number; proof_size: number } + } + const structLogs = typedTrace.structLogs ?? [] + const weightConsumed = typedTrace.weightConsumed + const baseCallWeight = typedTrace.baseCallWeight + + db.exec('BEGIN') + try { + db.prepare( + ` +INSERT OR REPLACE INTO transactions ( + hash, + chain_name, + contract_id, + contract_name, + transaction_name, + gas_used, + status, + post_dispatch_ref_time, + post_dispatch_pov, + weight_consumed_ref_time, + weight_consumed_proof_size, + base_call_weight_ref_time, + base_call_weight_proof_size +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +`, + ).run( + hashBytes, + chainName, + contractId, + contract, + action, + receipt.gasUsed.toString(), + statusValue, + weight ? weight.ref_time.toString() : null, + weight ? weight.proof_size.toString() : null, + weightConsumed ? weightConsumed.ref_time.toString() : null, + weightConsumed ? weightConsumed.proof_size.toString() : null, + baseCallWeight ? baseCallWeight.ref_time.toString() : null, + baseCallWeight ? baseCallWeight.proof_size.toString() : null, + ) + + const insertStep = db.prepare( + ` +INSERT OR REPLACE INTO transaction_steps ( + hash, + chain_name, + op, + gas_cost, + weight_cost_ref_time, + weight_cost_proof_size +) VALUES (?, ?, ?, ?, ?, ?) +`, + ) + + for (const log of structLogs) { + insertStep.run( + hashBytes, + chainName, + log.op, + log.gasCost, + log.weightCost ? log.weightCost.ref_time : null, + log.weightCost ? log.weightCost.proof_size : null, + ) + } + + db.exec('COMMIT') + } catch (error) { + db.exec('ROLLBACK') + throw error + } +} diff --git a/benchmark/reports.ts b/benchmark/reports.ts new file mode 100644 index 0000000..791dd91 --- /dev/null +++ b/benchmark/reports.ts @@ -0,0 +1,361 @@ +import { Artifacts, db } from './lib.ts' +import { logger } from '../utils/logger.ts' +import { ensureDir } from '@std/fs' +import { join } from '@std/path' +import { tablemark } from 'tablemark' +import { sumOf } from '@std/collections' + +const REPORTS_DIR = join(import.meta.dirname!, 'reports') + +function table(data: Record[]) { + return tablemark(data, { align: undefined, headerCase: 'preserve' }) +} + +export async function report(contracts: Artifacts) { + await ensureDir(REPORTS_DIR) + await generateOpcodeAnalysis() + await generateContractComparison() + await generateBytecodeComparison(contracts) + logger.info(`Reports saved to ${REPORTS_DIR}`) +} + +async function generateOpcodeAnalysis() { + let markdown = `# Opcode Analysis\n\n` + markdown += `Generated on: ${new Date().toISOString().split('T')[0]}\n\n` + + const allData = db.prepare(` + SELECT + t.chain_name, + t.contract_id, + t.contract_name, + t.transaction_name, + t.gas_used, + t.weight_consumed_ref_time, + t.weight_consumed_proof_size, + t.base_call_weight_ref_time, + t.base_call_weight_proof_size, + s.op, + SUM(s.gas_cost) as total_gas_cost, + COUNT(*) as count, + SUM(s.weight_cost_ref_time) as total_weight_cost_ref_time, + SUM(s.weight_cost_proof_size) as total_weight_cost_proof_size + FROM transactions AS t + JOIN + transaction_steps AS s ON s.hash = t.hash AND s.chain_name = t.chain_name + GROUP BY + t.hash, t.chain_name, s.op + ORDER BY + t.chain_name, t.contract_id, t.contract_name, t.transaction_name, total_gas_cost DESC + `).all() as Array<{ + chain_name: string + contract_id: string + contract_name: string + transaction_name: string + gas_used: number + weight_consumed_ref_time: number | null + weight_consumed_proof_size: number | null + base_call_weight_ref_time: number | null + base_call_weight_proof_size: number | null + op: string | null + total_gas_cost: number | null + count: number + total_weight_cost_ref_time: number | null + total_weight_cost_proof_size: number | null + }> + + const byChain = Object.groupBy(allData, (row) => row.chain_name) + + for (const [chainName, chainRows] of Object.entries(byChain)) { + if (!chainRows) continue + + markdown += `## Chain: ${chainName}\n\n` + + const byTransaction = Object.groupBy( + chainRows, + (row) => `${row.contract_name}:${row.transaction_name}`, + ) + + // Sort entries by contract_id, then transaction_name + const sortedEntries = Object.entries(byTransaction).sort((a, b) => { + const txA = a[1]?.[0] + const txB = b[1]?.[0] + if (!txA || !txB) return 0 + + // First sort by contract_id + if (txA.contract_id !== txB.contract_id) { + return txA.contract_id.localeCompare(txB.contract_id) + } + + // Then by transaction_name + return txA.transaction_name.localeCompare(txB.transaction_name) + }) + + for (const [, opcodes] of sortedEntries) { + if (!opcodes) continue + + const tx = opcodes[0] + markdown += `### ${tx.contract_name} - ${tx.transaction_name}\n\n` + markdown += + `- **Total Gas Used:** ${tx.gas_used.toLocaleString()}\n` + + // Display weight metrics if available + if ( + tx.weight_consumed_ref_time !== null && + tx.base_call_weight_ref_time !== null + ) { + const totalRefTime = tx.base_call_weight_ref_time + + tx.weight_consumed_ref_time + const weightConsumedPercent = + ((tx.weight_consumed_ref_time / totalRefTime) * 100) + .toFixed(1) + + markdown += [ + `- **Base Call Weight:** ref_time=${tx.base_call_weight_ref_time.toLocaleString()}, proof_size=${ + tx.base_call_weight_proof_size?.toLocaleString() ?? + 'N/A' + }`, + `- **Total Weight:** ref_time=${totalRefTime.toLocaleString()}, proof_size=${ + ((tx.base_call_weight_proof_size ?? 0) + + (tx.weight_consumed_proof_size ?? 0)) + .toLocaleString() + }`, + `- **Weight Consumed:** ref_time=${tx.weight_consumed_ref_time.toLocaleString()} (${weightConsumedPercent}% of total), proof_size=${ + (tx.weight_consumed_proof_size ?? 0).toLocaleString() + }`, + ].join('\n') + '\n' + } + + markdown += '\n' + + const topOpcodes = opcodes!.slice(0, 20) + const totalGasFromOpcodes = sumOf( + topOpcodes, + (op) => op.total_gas_cost!, + ) + + // Check if weight cost data is available + const hasWeightCost = topOpcodes.some( + (op) => op.total_weight_cost_ref_time !== null, + ) + + const tableData = topOpcodes.map((opcode) => { + const avgGas = (opcode.total_gas_cost! / opcode.count).toFixed( + 1, + ) + + const row: Record = { + 'Opcode': opcode.op, + 'Total Gas': opcode.total_gas_cost?.toLocaleString(), + 'Call Count': opcode.count.toLocaleString(), + 'Avg Gas/Call': avgGas, + } + + if ( + hasWeightCost && opcode.total_weight_cost_ref_time !== null + ) { + const percentOfRefTime = tx.weight_consumed_ref_time + ? ((opcode.total_weight_cost_ref_time / + tx.weight_consumed_ref_time) * 100).toFixed(1) + : '0.0' + + const percentOfProofSize = tx.weight_consumed_proof_size + ? ((opcode.total_weight_cost_proof_size ?? 0) / + tx.weight_consumed_proof_size * 100).toFixed(1) + : '0.0' + + row['ref time'] = opcode.total_weight_cost_ref_time + .toLocaleString() + row['proof size'] = + opcode.total_weight_cost_proof_size?.toLocaleString() ?? + '0' + row['% of ref time'] = `${percentOfRefTime}%` + row['% of proof size'] = `${percentOfProofSize}%` + } else { + const percentOfOpcodes = + ((opcode.total_gas_cost! / totalGasFromOpcodes) * 100) + .toFixed(1) + const percentOfTxGas = + ((opcode.total_gas_cost! / tx.gas_used) * 100).toFixed( + 1, + ) + row['% of opcodes'] = `${percentOfOpcodes}%` + row['% of tx Gas'] = `${percentOfTxGas}%` + } + + return row + }) + + markdown += table(tableData) + '\n\n' + } + } + + await Deno.writeTextFile( + join(REPORTS_DIR, 'opcode_analysis.md'), + markdown, + ) +} + +async function generateContractComparison() { + let markdown = `# Revive Contract Comparison\n\n` + markdown += `Generated on: ${new Date().toISOString().split('T')[0]}\n\n` + markdown += + `Comparison of gas usage across different contract implementations.\n\n` + + // Get all transactions with contract_id extracted in SQL + const txRows = db.prepare(` + SELECT + contract_id, + contract_name, + transaction_name, + gas_used, + weight_consumed_ref_time, + weight_consumed_proof_size, + base_call_weight_ref_time, + base_call_weight_proof_size + FROM transactions + WHERE chain_name == 'eth-rpc' + ORDER BY contract_id, transaction_name, (base_call_weight_ref_time + weight_consumed_ref_time) ASC + `).all() as Array<{ + contract_name: string + transaction_name: string + gas_used: number + contract_id: string + weight_consumed_ref_time: number | null + weight_consumed_proof_size: number | null + base_call_weight_ref_time: number | null + base_call_weight_proof_size: number | null + }> + + const groupedByContractAndTx = Object.groupBy( + txRows, + (row) => `${row.contract_id}:${row.transaction_name}`, + ) + + // Generate one table per (contract_id, operation) + for ( + const [groupKey, implementations] of Object.entries( + groupedByContractAndTx, + ) + ) { + if (!implementations) continue + + const [contract_id, transaction_name] = groupKey.split(':') + markdown += `## ${contract_id} - ${transaction_name}\n\n` + + // Find best (lowest) total weight ref_time + const bestRefTime = Math.min( + ...implementations + .filter((row) => + row.weight_consumed_ref_time !== null && + row.base_call_weight_ref_time !== null + ) + .map((row) => + row.base_call_weight_ref_time! + + row.weight_consumed_ref_time! + ), + ) + + const tableData = implementations.map((row) => { + const result: Record = { + 'Implementation': row.contract_name, + } + + if ( + row.weight_consumed_ref_time !== null && + row.base_call_weight_ref_time !== null + ) { + const totalRefTime = row.base_call_weight_ref_time + + row.weight_consumed_ref_time + const totalProofSize = (row.base_call_weight_proof_size ?? 0) + + (row.weight_consumed_proof_size ?? 0) + const meterPercent = + ((row.weight_consumed_ref_time / totalRefTime) * 100) + .toFixed(1) + const vsBest = totalRefTime === bestRefTime + ? '-' + : `+${((totalRefTime / bestRefTime - 1) * 100).toFixed(1)}%` + + result['ref_time'] = totalRefTime + .toLocaleString() + result['vs Best'] = vsBest + result['% metered'] = `${meterPercent}%` + result['pov'] = totalProofSize.toLocaleString() + } + + return result + }) + + markdown += table(tableData) + '\n\n' + } + + await Deno.writeTextFile( + join(REPORTS_DIR, 'contract_comparison.md'), + markdown, + ) +} + +async function generateBytecodeComparison(contracts: Artifacts) { + let markdown = `# Bytecode Size Comparison\n\n` + markdown += `Generated on: ${new Date().toISOString().split('T')[0]}\n\n` + + // Collect bytecode sizes for all contracts + interface BytecodeEntry { + contract_id: string + contract_name: string + size_bytes: number + vm_type: string + } + + const bytecodeData: BytecodeEntry[] = [] + + for (const artifact of contracts) { + for (const src of artifact.srcs) { + const bytecode = src.getBytecode() + const sizeBytes = bytecode.length / 2 - 1 // Convert hex string to byte count + const vmType = src.supportEvm() ? 'EVM' : 'PVM' + + bytecodeData.push({ + contract_id: artifact.id, + contract_name: src.getName(), + size_bytes: sizeBytes, + vm_type: vmType, + }) + } + } + + // Group by contract ID to compare implementations + const byContractId = Object.groupBy( + bytecodeData, + (entry) => entry.contract_id, + ) + + // Generate per-contract comparison + for (const [contractId, entries] of Object.entries(byContractId)) { + if (!entries || entries.length <= 1) continue // Skip if only one implementation + + markdown += `## ${contractId} Implementations\n\n` + + const sorted = entries.sort((a, b) => a.size_bytes - b.size_bytes) + const smallest = sorted[0].size_bytes + + const tableData = sorted.map((entry) => { + const vsSmallest = entry.size_bytes === smallest + ? '-' + : `+${((entry.size_bytes / smallest - 1) * 100).toFixed(1)}%` + + return { + 'Contract': entry.contract_name, + 'VM Type': entry.vm_type, + 'Size (bytes)': entry.size_bytes.toLocaleString(), + 'vs Smallest': vsSmallest, + } + }) + + markdown += table(tableData) + '\n\n' + } + + await Deno.writeTextFile( + join(REPORTS_DIR, 'bytecode_size_comparison.md'), + markdown, + ) +} diff --git a/benchmark/reports/.gitkeep b/benchmark/reports/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/benchmark/reports/bytecode_size_comparison.md b/benchmark/reports/bytecode_size_comparison.md new file mode 100644 index 0000000..d054218 --- /dev/null +++ b/benchmark/reports/bytecode_size_comparison.md @@ -0,0 +1,23 @@ +# Bytecode Size Comparison + +Generated on: 2025-12-19 + +## Fibonacci Implementations + +| Contract | VM Type | Size (bytes) | vs Smallest | +| ------------------- | ------- | ------------ | ----------- | +| fibonacci_rust | PVM | 220 | - | +| fibonacci_u128_rust | PVM | 296 | +34.5% | +| Fibonacci_evm | EVM | 391 | +77.7% | +| fibonacci_u256_rust | PVM | 698 | +217.3% | +| fibonacci_ink | PVM | 954 | +333.6% | +| Fibonacci_pvm | PVM | 1,890 | +759.1% | + +## SimpleToken Implementations + +| Contract | VM Type | Size (bytes) | vs Smallest | +| -------------------------- | ------- | ------------ | ----------- | +| SimpleToken_evm | EVM | 588 | - | +| simple_token_no_alloc_rust | PVM | 4,370 | +643.2% | +| SimpleToken_pvm | PVM | 5,241 | +791.3% | +| simple_token_ink | PVM | 7,008 | +1091.8% | diff --git a/benchmark/reports/contract_comparison.md b/benchmark/reports/contract_comparison.md new file mode 100644 index 0000000..81c9577 --- /dev/null +++ b/benchmark/reports/contract_comparison.md @@ -0,0 +1,54 @@ +# Revive Contract Comparison + +Generated on: 2025-12-19 + +Comparison of gas usage across different contract implementations. + +## Fibonacci - deploy + +| Implementation | ref_time | vs Best | % metered | pov | +| ------------------- | ------------- | ------- | --------- | ------ | +| fibonacci_rust | 1,520,994,099 | - | 0.0% | 6,934 | +| fibonacci_u128_rust | 1,522,196,875 | +0.1% | 0.0% | 6,934 | +| Fibonacci_evm | 1,525,205,124 | +0.3% | 0.1% | 6,934 | +| fibonacci_u256_rust | 1,528,558,927 | +0.5% | 0.0% | 6,934 | +| Fibonacci_pvm | 1,557,918,056 | +2.4% | 0.7% | 6,934 | +| fibonacci_ink | 1,718,162,422 | +13.0% | 10.8% | 17,191 | + +## Fibonacci - fib_10 + +| Implementation | ref_time | vs Best | % metered | pov | +| ------------------- | ------------- | ------- | --------- | ------ | +| fibonacci_rust | 1,003,971,839 | - | 9.1% | 8,652 | +| fibonacci_u128_rust | 1,096,536,651 | +9.2% | 16.7% | 8,728 | +| fibonacci_ink | 1,149,918,571 | +14.5% | 20.6% | 19,643 | +| Fibonacci_evm | 1,519,150,709 | +51.3% | 39.9% | 8,432 | +| Fibonacci_pvm | 1,587,091,712 | +58.1% | 42.5% | 10,322 | +| fibonacci_u256_rust | 2,512,824,851 | +150.3% | 63.7% | 9,130 | + +## SimpleToken - deploy + +| Implementation | ref_time | vs Best | % metered | pov | +| -------------------------- | ------------- | ------- | --------- | ------ | +| SimpleToken_evm | 1,528,362,837 | - | 0.1% | 6,934 | +| simple_token_no_alloc_rust | 1,586,671,999 | +3.8% | 0.0% | 6,934 | +| SimpleToken_pvm | 1,610,852,522 | +5.4% | 0.6% | 6,934 | +| simple_token_ink | 1,843,184,400 | +20.6% | 11.7% | 17,191 | + +## SimpleToken - mint + +| Implementation | ref_time | vs Best | % metered | pov | +| -------------------------- | ------------- | ------- | --------- | ------ | +| SimpleToken_evm | 1,431,551,949 | - | 36.2% | 49,623 | +| SimpleToken_pvm | 1,497,388,823 | +4.6% | 39.0% | 54,800 | +| simple_token_no_alloc_rust | 1,505,513,738 | +5.2% | 39.3% | 53,929 | +| simple_token_ink | 1,687,565,485 | +17.9% | 45.9% | 56,631 | + +## SimpleToken - transfer + +| Implementation | ref_time | vs Best | % metered | pov | +| -------------------------- | ------------- | ------- | --------- | ------ | +| SimpleToken_evm | 1,461,245,351 | - | 37.5% | 49,655 | +| SimpleToken_pvm | 1,554,253,981 | +6.4% | 41.2% | 54,961 | +| simple_token_no_alloc_rust | 1,562,060,164 | +6.9% | 41.5% | 53,961 | +| simple_token_ink | 2,012,500,642 | +37.7% | 54.6% | 77,209 | diff --git a/benchmark/reports/opcode_analysis.md b/benchmark/reports/opcode_analysis.md new file mode 100644 index 0000000..f49e3fd --- /dev/null +++ b/benchmark/reports/opcode_analysis.md @@ -0,0 +1,453 @@ +# Opcode Analysis + +Generated on: 2025-12-19 + +## Chain: Geth + +### Fibonacci_evm - deploy + +- **Total Gas Used:** 141,635 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | % of opcodes | % of tx Gas | +| --------- | --------- | ---------- | ------------ | ------------ | ----------- | +| CODECOPY | 72 | 1 | 72.0 | 63.7% | 0.1% | +| MSTORE | 12 | 1 | 12.0 | 10.6% | 0.0% | +| JUMPI | 10 | 1 | 10.0 | 8.8% | 0.0% | +| DUP1 | 3 | 1 | 3.0 | 2.7% | 0.0% | +| ISZERO | 3 | 1 | 3.0 | 2.7% | 0.0% | +| PUSH1 | 3 | 1 | 3.0 | 2.7% | 0.0% | +| PUSH2 | 3 | 1 | 3.0 | 2.7% | 0.0% | +| CALLVALUE | 2 | 1 | 2.0 | 1.8% | 0.0% | +| POP | 2 | 1 | 2.0 | 1.8% | 0.0% | +| PUSH0 | 2 | 1 | 2.0 | 1.8% | 0.0% | +| JUMPDEST | 1 | 1 | 1.0 | 0.9% | 0.0% | +| RETURN | 0 | 1 | 0.0 | 0.0% | 0.0% | + +### Fibonacci_evm - fib_10 + +- **Total Gas Used:** 62,284 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | % of opcodes | % of tx Gas | +| ------------ | --------- | ---------- | ------------ | ------------ | ----------- | +| LOG2 | 1,381 | 1 | 1381.0 | 94.8% | 2.2% | +| JUMPI | 10 | 1 | 10.0 | 0.7% | 0.0% | +| MSTORE | 9 | 1 | 9.0 | 0.6% | 0.0% | +| JUMP | 8 | 1 | 8.0 | 0.5% | 0.0% | +| ADD | 3 | 1 | 3.0 | 0.2% | 0.0% | +| AND | 3 | 1 | 3.0 | 0.2% | 0.0% | +| CALLDATALOAD | 3 | 1 | 3.0 | 0.2% | 0.0% | +| DUP1 | 3 | 1 | 3.0 | 0.2% | 0.0% | +| DUP2 | 3 | 1 | 3.0 | 0.2% | 0.0% | +| DUP3 | 3 | 1 | 3.0 | 0.2% | 0.0% | +| DUP4 | 3 | 1 | 3.0 | 0.2% | 0.0% | +| DUP5 | 3 | 1 | 3.0 | 0.2% | 0.0% | +| DUP6 | 3 | 1 | 3.0 | 0.2% | 0.0% | +| EQ | 3 | 1 | 3.0 | 0.2% | 0.0% | +| GT | 3 | 1 | 3.0 | 0.2% | 0.0% | +| ISZERO | 3 | 1 | 3.0 | 0.2% | 0.0% | +| LT | 3 | 1 | 3.0 | 0.2% | 0.0% | +| MLOAD | 3 | 1 | 3.0 | 0.2% | 0.0% | +| PUSH1 | 3 | 1 | 3.0 | 0.2% | 0.0% | +| PUSH2 | 3 | 1 | 3.0 | 0.2% | 0.0% | + +### SimpleToken_evm - deploy + +- **Total Gas Used:** 174,399 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | % of opcodes | % of tx Gas | +| --------- | --------- | ---------- | ------------ | ------------ | ----------- | +| CODECOPY | 102 | 1 | 102.0 | 71.3% | 0.1% | +| MSTORE | 12 | 1 | 12.0 | 8.4% | 0.0% | +| JUMPI | 10 | 1 | 10.0 | 7.0% | 0.0% | +| DUP1 | 3 | 1 | 3.0 | 2.1% | 0.0% | +| ISZERO | 3 | 1 | 3.0 | 2.1% | 0.0% | +| PUSH1 | 3 | 1 | 3.0 | 2.1% | 0.0% | +| PUSH2 | 3 | 1 | 3.0 | 2.1% | 0.0% | +| CALLVALUE | 2 | 1 | 2.0 | 1.4% | 0.0% | +| POP | 2 | 1 | 2.0 | 1.4% | 0.0% | +| PUSH0 | 2 | 1 | 2.0 | 1.4% | 0.0% | +| JUMPDEST | 1 | 1 | 1.0 | 0.7% | 0.0% | +| RETURN | 0 | 1 | 0.0 | 0.0% | 0.0% | + +### SimpleToken_evm - mint + +- **Total Gas Used:** 68,273 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | % of opcodes | % of tx Gas | +| ------------ | --------- | ---------- | ------------ | ------------ | ----------- | +| SSTORE | 20,000 | 1 | 20000.0 | 83.5% | 29.3% | +| SLOAD | 2,100 | 1 | 2100.0 | 8.8% | 3.1% | +| LOG3 | 1,756 | 1 | 1756.0 | 7.3% | 2.6% | +| KECCAK256 | 42 | 1 | 42.0 | 0.2% | 0.1% | +| JUMPI | 10 | 1 | 10.0 | 0.0% | 0.0% | +| MSTORE | 9 | 1 | 9.0 | 0.0% | 0.0% | +| JUMP | 8 | 1 | 8.0 | 0.0% | 0.0% | +| ADD | 3 | 1 | 3.0 | 0.0% | 0.0% | +| AND | 3 | 1 | 3.0 | 0.0% | 0.0% | +| CALLDATALOAD | 3 | 1 | 3.0 | 0.0% | 0.0% | +| DUP1 | 3 | 1 | 3.0 | 0.0% | 0.0% | +| DUP2 | 3 | 1 | 3.0 | 0.0% | 0.0% | +| DUP3 | 3 | 1 | 3.0 | 0.0% | 0.0% | +| DUP4 | 3 | 1 | 3.0 | 0.0% | 0.0% | +| DUP5 | 3 | 1 | 3.0 | 0.0% | 0.0% | +| DUP6 | 3 | 1 | 3.0 | 0.0% | 0.0% | +| EQ | 3 | 1 | 3.0 | 0.0% | 0.0% | +| GT | 3 | 1 | 3.0 | 0.0% | 0.0% | +| ISZERO | 3 | 1 | 3.0 | 0.0% | 0.0% | +| LT | 3 | 1 | 3.0 | 0.0% | 0.0% | + +## Chain: eth-rpc + +### Fibonacci_evm - deploy + +- **Total Gas Used:** 7,677,100 +- **Base Call Weight:** ref_time=1,523,651,115, proof_size=6,934 +- **Total Weight:** ref_time=1,525,205,124, proof_size=6,934 +- **Weight Consumed:** ref_time=1,554,009 (0.1% of total), proof_size=0 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| --------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| CODECOPY | 11 | 1 | 11.0 | 562,689 | 0 | 36.2% | 0.0% | +| CALLVALUE | 7 | 1 | 7.0 | 319,000 | 0 | 20.5% | 0.0% | +| JUMPI | 3 | 1 | 3.0 | 152,800 | 0 | 9.8% | 0.0% | +| ISZERO | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| POP | 1 | 1 | 1.0 | 30,560 | 0 | 2.0% | 0.0% | +| PUSH0 | 1 | 1 | 1.0 | 30,560 | 0 | 2.0% | 0.0% | +| PUSH1 | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| PUSH2 | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| DUP1 | 0 | 1 | 0.0 | 45,840 | 0 | 2.9% | 0.0% | +| JUMPDEST | 0 | 1 | 0.0 | 15,280 | 0 | 1.0% | 0.0% | +| MSTORE | 0 | 1 | 0.0 | 45,840 | 0 | 2.9% | 0.0% | +| RETURN | 0 | 1 | 0.0 | 0 | 0 | 0.0% | 0.0% | + +### Fibonacci_pvm - deploy + +- **Total Gas Used:** 10,767,375 +- **Base Call Weight:** ref_time=1,547,374,289, proof_size=6,934 +- **Total Weight:** ref_time=1,557,918,056, proof_size=6,934 +- **Weight Consumed:** ref_time=10,543,767 (0.7% of total), proof_size=0 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| seal_return | 11 | 1 | 11.0 | 529,465 | 0 | 5.0% | 0.0% | +| call_data_copy | 10 | 1 | 10.0 | 496,576 | 0 | 4.7% | 0.0% | +| value_transferred | 7 | 1 | 7.0 | 319,000 | 0 | 3.0% | 0.0% | +| call_data_size | 6 | 1 | 6.0 | 330,000 | 0 | 3.1% | 0.0% | + +### fibonacci_ink - deploy + +- **Total Gas Used:** 17,092,219 +- **Base Call Weight:** ref_time=1,532,561,153, proof_size=6,934 +- **Total Weight:** ref_time=1,718,162,422, proof_size=17,191 +- **Weight Consumed:** ref_time=185,601,269 (10.8% of total), proof_size=10,257 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage | 47,412 | 1 | 47412.0 | 170,582,310 | 10,257 | 91.9% | 100.0% | +| call_data_copy | 47 | 1 | 47.0 | 2,347,968 | 0 | 1.3% | 0.0% | +| seal_return | 11 | 1 | 11.0 | 529,465 | 0 | 0.3% | 0.0% | +| value_transferred | 7 | 1 | 7.0 | 319,000 | 0 | 0.2% | 0.0% | + +### Fibonacci_evm - fib_10 + +- **Total Gas Used:** 350,299 +- **Base Call Weight:** ref_time=913,100,531, proof_size=8,432 +- **Total Weight:** ref_time=1,519,150,709, proof_size=8,432 +- **Weight Consumed:** ref_time=606,050,178 (39.9% of total), proof_size=0 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ------------ | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| CALLVALUE | 6 | 1 | 6.0 | 319,000 | 0 | 0.1% | 0.0% | +| JUMPI | 3 | 1 | 3.0 | 152,800 | 0 | 0.0% | 0.0% | +| JUMP | 2 | 1 | 2.0 | 122,240 | 0 | 0.0% | 0.0% | +| ADD | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| AND | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| CALLDATALOAD | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP1 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP2 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP3 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP4 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP5 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP6 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| EQ | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| GT | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| ISZERO | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| JUMPDEST | 1 | 1 | 1.0 | 15,280 | 0 | 0.0% | 0.0% | +| LT | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| MSTORE | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| POP | 1 | 1 | 1.0 | 30,560 | 0 | 0.0% | 0.0% | +| PUSH0 | 1 | 1 | 1.0 | 30,560 | 0 | 0.0% | 0.0% | + +### Fibonacci_pvm - fib_10 + +- **Total Gas Used:** 351,658 +- **Base Call Weight:** ref_time=913,100,531, proof_size=8,432 +- **Total Weight:** ref_time=1,587,091,712, proof_size=10,322 +- **Weight Consumed:** ref_time=673,991,181 (42.5% of total), proof_size=1,890 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| seal_return | 11 | 1 | 11.0 | 529,465 | 0 | 0.1% | 0.0% | +| call_data_load | 7 | 1 | 7.0 | 343,000 | 0 | 0.1% | 0.0% | +| value_transferred | 7 | 1 | 7.0 | 319,000 | 0 | 0.0% | 0.0% | +| call_data_size | 6 | 1 | 6.0 | 330,000 | 0 | 0.0% | 0.0% | + +### fibonacci_ink - fib_10 + +- **Total Gas Used:** 342,915 +- **Base Call Weight:** ref_time=913,100,531, proof_size=8,432 +- **Total Weight:** ref_time=1,149,918,571, proof_size=19,643 +- **Weight Consumed:** ref_time=236,818,040 (20.6% of total), proof_size=11,211 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | ---------- | ---------- | ------------- | --------------- | +| get_storage | 1,382 | 1 | 1382.0 | 69,114,511 | 10,257 | 29.2% | 91.5% | +| call_data_copy | 47 | 1 | 47.0 | 2,347,968 | 0 | 1.0% | 0.0% | +| seal_return | 10 | 1 | 10.0 | 529,465 | 0 | 0.2% | 0.0% | +| value_transferred | 6 | 1 | 6.0 | 319,000 | 0 | 0.1% | 0.0% | + +### fibonacci_rust - fib_10 + +- **Total Gas Used:** 339,996 +- **Base Call Weight:** ref_time=913,100,531, proof_size=8,432 +- **Total Weight:** ref_time=1,003,971,839, proof_size=8,652 +- **Weight Consumed:** ref_time=90,871,308 (9.1% of total), proof_size=220 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| -------------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| call_data_copy | 10 | 1 | 10.0 | 497,028 | 0 | 0.5% | 0.0% | + +### fibonacci_u128_rust - fib_10 + +- **Total Gas Used:** 341,847 +- **Base Call Weight:** ref_time=913,100,531, proof_size=8,432 +- **Total Weight:** ref_time=1,096,536,651, proof_size=8,728 +- **Weight Consumed:** ref_time=183,436,120 (16.7% of total), proof_size=296 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| -------------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| call_data_copy | 10 | 1 | 10.0 | 498,384 | 0 | 0.3% | 0.0% | + +### fibonacci_u256_rust - fib_10 + +- **Total Gas Used:** 370,173 +- **Base Call Weight:** ref_time=913,100,531, proof_size=8,432 +- **Total Weight:** ref_time=2,512,824,851, proof_size=9,130 +- **Weight Consumed:** ref_time=1,599,724,320 (63.7% of total), proof_size=698 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| -------------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| call_data_copy | 10 | 1 | 10.0 | 500,192 | 0 | 0.0% | 0.0% | + +### SimpleToken_evm - deploy + +- **Total Gas Used:** 8,082,983 +- **Base Call Weight:** ref_time=1,526,768,837, proof_size=6,934 +- **Total Weight:** ref_time=1,528,362,837, proof_size=6,934 +- **Weight Consumed:** ref_time=1,594,000 (0.1% of total), proof_size=0 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| --------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| CODECOPY | 12 | 1 | 12.0 | 602,680 | 0 | 37.8% | 0.0% | +| CALLVALUE | 6 | 1 | 6.0 | 319,000 | 0 | 20.0% | 0.0% | +| JUMPI | 3 | 1 | 3.0 | 152,800 | 0 | 9.6% | 0.0% | +| DUP1 | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| ISZERO | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| MSTORE | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| POP | 1 | 1 | 1.0 | 30,560 | 0 | 1.9% | 0.0% | +| PUSH0 | 1 | 1 | 1.0 | 30,560 | 0 | 1.9% | 0.0% | +| PUSH1 | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| PUSH2 | 1 | 1 | 1.0 | 45,840 | 0 | 2.9% | 0.0% | +| JUMPDEST | 0 | 1 | 0.0 | 15,280 | 0 | 1.0% | 0.0% | +| RETURN | 0 | 1 | 0.0 | 0 | 0 | 0.0% | 0.0% | + +### SimpleToken_pvm - deploy + +- **Total Gas Used:** 17,673,493 +- **Base Call Weight:** ref_time=1,600,407,215, proof_size=6,934 +- **Total Weight:** ref_time=1,610,852,522, proof_size=6,934 +- **Weight Consumed:** ref_time=10,445,307 (0.6% of total), proof_size=0 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | -------- | ---------- | ------------- | --------------- | +| seal_return | 11 | 1 | 11.0 | 529,465 | 0 | 5.1% | 0.0% | +| call_data_copy | 10 | 1 | 10.0 | 496,576 | 0 | 4.8% | 0.0% | +| call_data_size | 7 | 1 | 7.0 | 330,000 | 0 | 3.2% | 0.0% | +| value_transferred | 6 | 1 | 6.0 | 319,000 | 0 | 3.1% | 0.0% | + +### simple_token_ink - deploy + +- **Total Gas Used:** 21,368,560 +- **Base Call Weight:** ref_time=1,628,371,757, proof_size=6,934 +- **Total Weight:** ref_time=1,843,184,400, proof_size=17,191 +- **Weight Consumed:** ref_time=214,812,643 (11.7% of total), proof_size=10,257 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage | 53,812 | 1 | 53812.0 | 170,600,294 | 10,257 | 79.4% | 100.0% | +| call_data_copy | 47 | 1 | 47.0 | 2,347,968 | 0 | 1.1% | 0.0% | +| seal_return | 10 | 1 | 10.0 | 529,465 | 0 | 0.2% | 0.0% | +| value_transferred | 6 | 1 | 6.0 | 319,000 | 0 | 0.1% | 0.0% | + +### SimpleToken_evm - mint + +- **Total Gas Used:** 520,147 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=1,431,551,949, proof_size=49,623 +- **Weight Consumed:** ref_time=518,044,627 (36.2% of total), proof_size=41,092 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ------------ | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| SSTORE | 56,212 | 1 | 56212.0 | 170,600,294 | 10,257 | 32.9% | 25.0% | +| SLOAD | 1,383 | 1 | 1383.0 | 69,164,719 | 10,289 | 13.4% | 25.0% | +| KECCAK256 | 305 | 1 | 305.0 | 15,219,717 | 0 | 2.9% | 0.0% | +| LOG3 | 275 | 1 | 275.0 | 13,751,564 | 0 | 2.7% | 0.0% | +| CALLVALUE | 7 | 1 | 7.0 | 319,000 | 0 | 0.1% | 0.0% | +| JUMPI | 3 | 1 | 3.0 | 152,800 | 0 | 0.0% | 0.0% | +| JUMP | 2 | 1 | 2.0 | 122,240 | 0 | 0.0% | 0.0% | +| ADD | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| AND | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| CALLDATALOAD | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP1 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP2 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP3 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP4 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP5 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP6 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| EQ | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| GT | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| ISZERO | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| JUMPDEST | 1 | 1 | 1.0 | 15,280 | 0 | 0.0% | 0.0% | + +### SimpleToken_pvm - mint + +- **Total Gas Used:** 521,464 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=1,497,388,823, proof_size=54,800 +- **Weight Consumed:** ref_time=583,881,501 (39.0% of total), proof_size=46,269 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| -------------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage_or_clear | 56,212 | 1 | 56212.0 | 170,600,294 | 10,257 | 29.2% | 22.2% | +| get_storage_or_zero | 1,382 | 1 | 1382.0 | 69,114,511 | 10,257 | 11.8% | 22.2% | +| hash_keccak_256 | 304 | 1 | 304.0 | 15,219,717 | 0 | 2.6% | 0.0% | +| deposit_event | 275 | 1 | 275.0 | 13,751,564 | 0 | 2.4% | 0.0% | +| seal_return | 10 | 1 | 10.0 | 529,465 | 0 | 0.1% | 0.0% | +| call_data_load | 7 | 1 | 7.0 | 343,000 | 0 | 0.1% | 0.0% | +| call_data_size | 7 | 1 | 7.0 | 330,000 | 0 | 0.1% | 0.0% | +| value_transferred | 6 | 1 | 6.0 | 319,000 | 0 | 0.1% | 0.0% | + +### simple_token_ink - mint + +- **Total Gas Used:** 474,068 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=1,687,565,485, proof_size=56,631 +- **Weight Consumed:** ref_time=774,058,163 (45.9% of total), proof_size=48,100 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage | 3,413 | 1 | 3413.0 | 170,624,806 | 10,289 | 22.0% | 21.4% | +| get_storage | 1,382 | 1 | 1382.0 | 69,114,511 | 10,257 | 8.9% | 21.3% | +| deposit_event | 275 | 1 | 275.0 | 13,751,564 | 0 | 1.8% | 0.0% | +| call_data_copy | 47 | 1 | 47.0 | 2,347,968 | 0 | 0.3% | 0.0% | +| seal_return | 11 | 1 | 11.0 | 529,465 | 0 | 0.1% | 0.0% | +| value_transferred | 7 | 1 | 7.0 | 319,000 | 0 | 0.0% | 0.0% | + +### simple_token_no_alloc_rust - mint + +- **Total Gas Used:** 521,627 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=1,505,513,738, proof_size=53,929 +- **Weight Consumed:** ref_time=592,006,416 (39.3% of total), proof_size=45,398 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| --------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage | 56,212 | 1 | 56212.0 | 170,591,302 | 10,257 | 28.8% | 22.6% | +| get_storage | 1,382 | 1 | 1382.0 | 69,114,511 | 10,257 | 11.7% | 22.6% | +| hash_keccak_256 | 305 | 1 | 305.0 | 15,219,717 | 0 | 2.6% | 0.0% | +| deposit_event | 275 | 1 | 275.0 | 13,751,564 | 0 | 2.3% | 0.0% | +| call_data_copy | 10 | 1 | 10.0 | 504,260 | 0 | 0.1% | 0.0% | +| call_data_size | 7 | 1 | 7.0 | 330,000 | 0 | 0.1% | 0.0% | + +### SimpleToken_evm - transfer + +- **Total Gas Used:** 415,141 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=1,461,245,351, proof_size=49,655 +- **Weight Consumed:** ref_time=547,738,029 (37.5% of total), proof_size=41,124 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ------------ | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| SSTORE | 3,412 | 1 | 3412.0 | 170,600,294 | 10,257 | 31.1% | 24.9% | +| SLOAD | 1,384 | 1 | 1384.0 | 69,164,719 | 10,289 | 12.6% | 25.0% | +| KECCAK256 | 304 | 1 | 304.0 | 15,219,717 | 0 | 2.8% | 0.0% | +| LOG3 | 275 | 1 | 275.0 | 13,751,564 | 0 | 2.5% | 0.0% | +| CALLER | 8 | 1 | 8.0 | 403,000 | 0 | 0.1% | 0.0% | +| CALLVALUE | 7 | 1 | 7.0 | 319,000 | 0 | 0.1% | 0.0% | +| JUMPI | 3 | 1 | 3.0 | 152,800 | 0 | 0.0% | 0.0% | +| JUMP | 2 | 1 | 2.0 | 122,240 | 0 | 0.0% | 0.0% | +| ADD | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| CALLDATALOAD | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| CALLDATASIZE | 1 | 1 | 1.0 | 30,560 | 0 | 0.0% | 0.0% | +| DUP1 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP2 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP4 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP5 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP6 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP7 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| DUP8 | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| EQ | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | +| ISZERO | 1 | 1 | 1.0 | 45,840 | 0 | 0.0% | 0.0% | + +### SimpleToken_pvm - transfer + +- **Total Gas Used:** 417,001 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=1,554,253,981, proof_size=54,961 +- **Weight Consumed:** ref_time=640,746,659 (41.2% of total), proof_size=46,430 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| -------------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage_or_clear | 3,412 | 1 | 3412.0 | 170,600,294 | 10,257 | 26.6% | 22.1% | +| get_storage_or_zero | 1,382 | 1 | 1382.0 | 69,114,511 | 10,257 | 10.8% | 22.1% | +| hash_keccak_256 | 304 | 1 | 304.0 | 15,219,717 | 0 | 2.4% | 0.0% | +| deposit_event | 275 | 1 | 275.0 | 13,751,564 | 0 | 2.1% | 0.0% | +| seal_return | 11 | 1 | 11.0 | 529,465 | 0 | 0.1% | 0.0% | +| caller | 8 | 1 | 8.0 | 403,000 | 0 | 0.1% | 0.0% | +| call_data_load | 7 | 1 | 7.0 | 343,000 | 0 | 0.1% | 0.0% | +| call_data_size | 7 | 1 | 7.0 | 330,000 | 0 | 0.1% | 0.0% | +| value_transferred | 6 | 1 | 6.0 | 319,000 | 0 | 0.0% | 0.0% | + +### simple_token_ink - transfer + +- **Total Gas Used:** 480,566 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=2,012,500,642, proof_size=77,209 +- **Weight Consumed:** ref_time=1,098,993,320 (54.6% of total), proof_size=68,678 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| ----------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage | 3,412 | 1 | 3412.0 | 170,624,806 | 10,289 | 15.5% | 15.0% | +| get_storage | 1,382 | 1 | 1382.0 | 69,114,511 | 10,257 | 6.3% | 14.9% | +| deposit_event | 275 | 1 | 275.0 | 13,751,564 | 0 | 1.3% | 0.0% | +| call_data_copy | 47 | 1 | 47.0 | 2,347,968 | 0 | 0.2% | 0.0% | +| seal_return | 11 | 1 | 11.0 | 529,465 | 0 | 0.0% | 0.0% | +| caller | 8 | 1 | 8.0 | 403,000 | 0 | 0.0% | 0.0% | +| value_transferred | 7 | 1 | 7.0 | 319,000 | 0 | 0.0% | 0.0% | + +### simple_token_no_alloc_rust - transfer + +- **Total Gas Used:** 469,957 +- **Base Call Weight:** ref_time=913,507,322, proof_size=8,531 +- **Total Weight:** ref_time=1,562,060,164, proof_size=53,961 +- **Weight Consumed:** ref_time=648,552,842 (41.5% of total), proof_size=45,430 + +| Opcode | Total Gas | Call Count | Avg Gas/Call | ref time | proof size | % of ref time | % of proof size | +| --------------- | --------- | ---------- | ------------ | ----------- | ---------- | ------------- | --------------- | +| set_storage | 56,212 | 1 | 56212.0 | 170,591,302 | 10,257 | 26.3% | 22.6% | +| get_storage | 1,382 | 1 | 1382.0 | 69,114,511 | 10,257 | 10.7% | 22.6% | +| hash_keccak_256 | 304 | 1 | 304.0 | 15,219,717 | 0 | 2.3% | 0.0% | +| deposit_event | 275 | 1 | 275.0 | 13,751,564 | 0 | 2.1% | 0.0% | +| call_data_copy | 10 | 1 | 10.0 | 504,260 | 0 | 0.1% | 0.0% | +| caller | 8 | 1 | 8.0 | 403,000 | 0 | 0.1% | 0.0% | +| call_data_size | 7 | 1 | 7.0 | 330,000 | 0 | 0.1% | 0.0% | diff --git a/cli/playground.ts b/cli/playground.ts index 0b1837a..5ee003a 100755 --- a/cli/playground.ts +++ b/cli/playground.ts @@ -2,27 +2,22 @@ import { env } from '../tools/lib/index.ts' import { abis } from '../codegen/abis.ts' -import { Storage } from '../codegen/addresses.ts' +import { Fibonacci } from '../codegen/addresses.ts' -{ - const { request } = await env.wallet.simulateContract({ - address: Storage, - abi: abis.Storage, - functionName: 'store', - args: [42n], - }) - - const result = await env.wallet.writeContract(request) - console.log('store tx', result) -} +// await env.wallet.sendTransaction({ +// to: '0x3d26c9637dFaB74141bA3C466224C0DBFDfF4A63', +// value: parseEther('1'), +// }) { - const result = await env.wallet.readContract( + const result = await env.wallet.writeContract( { - address: Storage, - abi: abis.Storage, - functionName: 'retrieve', + address: Fibonacci, + abi: abis.Fibonacci, + functionName: 'fibonacci', + args: [5n], }, ) - console.log('retrieve:', result) + + console.log('tx', result) } diff --git a/cli/token_benchmark.ts b/cli/token_benchmark.ts deleted file mode 100755 index 3d0aaf2..0000000 --- a/cli/token_benchmark.ts +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env -S deno run --env-file --allow-all - -import { env } from '../tools/lib/index.ts' -import { abis } from '../codegen/abis.ts' -import { MyToken } from '../codegen/addresses.ts' -import { privateKeyToAccount } from 'viem/accounts' -import { generatePrivateKey } from 'viem/accounts' -import { parseEther } from 'viem' - -const code = await env.wallet.getCode({ address: MyToken }) -if (!code) { - console.error(`Deploy contract first with "deno task deploy"`) - Deno.exit(1) -} else { - console.log(`MyToken code size: ${code.length / 2 - 1} bytes`) -} - -const ITERATIONS = 300 - -// Generate address pool (first half senders, second half receivers) -console.log('Generating address...') -const addressPool = Array.from({ length: ITERATIONS * 2 }, () => { - const privateKey = generatePrivateKey() - const account = privateKeyToAccount(privateKey) - return { privateKey, address: account.address, account } -}) -const senderAccounts = addressPool.slice(0, ITERATIONS) -const receiverAddresses = addressPool.slice(ITERATIONS).map((a) => a.address) -console.log( - `Generated ${ITERATIONS} sender accounts and ${ITERATIONS} receiver addresses`, -) - -// Prefund all addresses to bring accounts into existence -{ - console.log('Prefunding all addresses...') - const nonce = await env.wallet.getTransactionCount(env.wallet.account) - const fundHashes = await Promise.all( - addressPool.map((account, index) => { - return env.wallet.sendTransaction({ - to: account.address, - value: parseEther('1'), - nonce: nonce + addressPool.length - 1 - index, - }) - }), - ) - console.log(`Sent ${fundHashes.length} funding transactions`) - await env.wallet.waitForTransactionReceipt({ hash: fundHashes[0] }) - console.log('All addresses funded') -} - -{ - // Simulate and execute mint with env.wallet - const mintRequest = { - address: MyToken, - abi: abis.MyToken, - functionName: 'mint', - args: [env.wallet.account.address, 10_000_000_000_000_000_000_000_000n], - } as const - const mintHash = await env.wallet.writeContract(mintRequest) - const mintReceipt = await env.wallet.waitForTransactionReceipt({ - hash: mintHash, - }) - - // Simulate and execute transfer with env.wallet - const transferRequest = { - address: MyToken, - abi: abis.MyToken, - functionName: 'transfer', - args: [receiverAddresses[0], 1n], - } as const - const transferHash = await env.wallet.writeContract(transferRequest) - const transferReceipt = await env.wallet.waitForTransactionReceipt({ - hash: transferHash, - }) - - // Build all transactions for the loop - console.log('Building all transactions...') - const allRequests = [] - for (let i = 0; i < ITERATIONS; i++) { - const sender = senderAccounts[i] - const receiverAddress = receiverAddresses[i] - - // Mint transaction from sender (nonce 0) - allRequests.push( - env.wallet.writeContract({ - ...mintRequest, - nonce: 0, - account: sender.account, - gas: mintReceipt.gasUsed, - gasPrice: mintReceipt.effectiveGasPrice, - }), - ) - - // // Transfer transaction from sender (nonce 1) - allRequests.push( - env.wallet.writeContract({ - ...transferRequest, - args: [receiverAddress, 1n], - nonce: 1, - account: sender.account, - gas: transferReceipt.gasUsed, - gasPrice: mintReceipt.effectiveGasPrice, - }), - ) - } - - // Send all transactions at once - console.log('Sending all transactions...') - const allHashes = await Promise.all(allRequests) - console.log( - `Sent ${allHashes.length} transactions (${ITERATIONS} mint + ${ITERATIONS} transfer)`, - ) - - console.log('Waiting for last transaction...') - env.wallet.waitForTransactionReceipt({ - hash: allHashes[allHashes.length - 1], - }) - - console.log(`\nSuccessfully processed ${ITERATIONS} mint + transfer pairs`) -} diff --git a/cli/token_deploy.ts b/cli/token_deploy.ts deleted file mode 100644 index 7a7d748..0000000 --- a/cli/token_deploy.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { deploy } from '../tools/lib/index.ts' -import { readBytecode } from '../utils/index.ts' - -await deploy({ - name: { name: 'MyToken', mappedTo: 'MyTokenEvm' }, - args: [], -}) - -await deploy({ - name: { name: 'MyToken', mappedTo: 'MyTokenPvm' }, - args: [], - bytecodeType: 'polkavm', // Specify `pvm` for PVM bytecode deployment -}) -await deploy({ - name: { name: 'MyToken', mappedTo: 'MyTokenInk' }, - args: [], - bytecode: readBytecode('./ink/ink_erc20/target/ink/ink_erc20.polkavm'), -}) - -await deploy({ - name: { name: 'MyToken', mappedTo: 'MyTokenRustWithAlloc' }, - args: [], - bytecode: readBytecode('./rust/contract_with_alloc/contract.polkavm'), -}) - -await deploy({ - name: { name: 'MyToken', mappedTo: 'MyTokenRustNoAlloc' }, - args: [], - bytecode: readBytecode('./rust/contract_no_alloc/contract.polkavm'), -}) diff --git a/cli/token_execute.ts b/cli/token_execute.ts deleted file mode 100755 index e3d8aa7..0000000 --- a/cli/token_execute.ts +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env -S deno run --env-file --allow-all -import { env } from '../tools/lib/index.ts' -import { abis } from '../codegen/abis.ts' -import { - MyTokenEvm, - MyTokenInk, - MyTokenPvm, - MyTokenRustNoAlloc, - MyTokenRustWithAlloc, -} from '../codegen/addresses.ts' -import { Hex, parseEther } from 'viem' -import Table from 'cli-table3' - -const recipient: Hex = '0x3d26c9637dFaB74141bA3C466224C0DBFDfF4A63' - -// fund recipient if needed -{ - // endow the account wallet with some funds if needed - const endowment = parseEther('1') - const balance = await env.wallet.getBalance({ address: recipient }) - if (balance == 0n) { - console.log(`funding ${recipient}`) - const hash = await env.wallet.sendTransaction({ - to: recipient, - value: endowment, - }) - await env.wallet.waitForTransactionReceipt({ hash }) - } -} - -const addresses = [ - { address: MyTokenEvm, name: 'EVM - solidity' }, - { address: MyTokenPvm, name: 'PVM - solidity' }, - { address: MyTokenInk, name: 'PVM - Ink!' }, - { address: MyTokenRustWithAlloc, name: 'PVM - Rust with alloc' }, - { address: MyTokenRustNoAlloc, name: 'PVM - Rust no alloc' }, -] as const - -type CodeSizeEntry = { - name: string - size: number -} - -type StatEntry = { - operation: string - gas: bigint - weight: { ref_time: bigint; proof_size: bigint } -} - -const codeSizes: CodeSizeEntry[] = [] -const stats: StatEntry[] = [] - -for (const { name, address } of addresses) { - const code = await env.wallet.getCode({ address }) - if (!code) { - console.error(`Deploy contract first with "deno task deploy"`) - Deno.exit(1) - } - codeSizes.push({ - name, - size: code.length / 2 - 1, - }) -} - -// mint token -for (const { name, address } of addresses) { - const { request } = await env.wallet.simulateContract({ - address, - abi: abis.MyToken, - functionName: 'mint', - args: [ - '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac', - 10_000n, - ], - }) - - const hash = await env.wallet.writeContract(request) - const receipt = await env.wallet.waitForTransactionReceipt({ hash }) - const weight = await env.debugClient.postDispatchWeight(hash) - - stats.push({ - operation: `mint (${name})`, - gas: receipt.gasUsed, - weight, - }) -} - -// transfer token -for (const { name, address } of addresses) { - const { request } = await env.wallet.simulateContract({ - address, - abi: abis.MyToken, - functionName: 'transfer', - args: [recipient, 1n], - }) - - const hash = await env.wallet.writeContract(request) - const receipt = await env.wallet.waitForTransactionReceipt({ hash }) - const weight = await env.debugClient.postDispatchWeight(hash) - - stats.push({ - operation: `transfer (${name})`, - gas: receipt.gasUsed, - weight, - }) -} - -// Separate mint and transfer operations -const mintStats = stats.filter((s) => s.operation.includes('mint')) -const transferStats = stats.filter((s) => s.operation.includes('transfer')) - -function createOperationTable(operationStats: StatEntry[], title: string) { - const minGas = operationStats.reduce( - (min, s) => s.gas < min ? s.gas : min, - operationStats[0].gas, - ) - const minRefTime = operationStats.reduce( - (min, s) => s.weight.ref_time < min ? s.weight.ref_time : min, - operationStats[0].weight.ref_time, - ) - const minProofSize = operationStats.reduce( - (min, s) => s.weight.proof_size < min ? s.weight.proof_size : min, - operationStats[0].weight.proof_size, - ) - - const table = new Table({ - head: [ - 'Implementation', - 'Gas Used', - 'Gas %', - 'Ref Time', - 'Ref Time %', - 'Proof Size', - 'Proof Size %', - ], - style: { - head: ['cyan'], - }, - }) - - for (const stat of operationStats) { - const gasPercent = ((Number(stat.gas) / Number(minGas)) * 100).toFixed( - 1, - ) - const refTimePercent = - ((Number(stat.weight.ref_time) / Number(minRefTime)) * 100).toFixed( - 1, - ) - const proofSizePercent = - ((Number(stat.weight.proof_size) / Number(minProofSize)) * 100) - .toFixed(1) - - // Extract implementation name (EVM or PVM) from operation - const implName = stat.operation.match(/\((.*?)\)/)?.[1] || - stat.operation - - table.push([ - implName, - stat.gas.toString(), - `${gasPercent}%`, - stat.weight.ref_time.toString(), - `${refTimePercent}%`, - stat.weight.proof_size.toString(), - `${proofSizePercent}%`, - ]) - } - - console.log(`\n## ${title}\n`) - console.log(table.toString()) -} - -createOperationTable(mintStats, 'Mint Operation') -createOperationTable(transferStats, 'Transfer Operation') - -// Output code size table -const minCodeSize = codeSizes.reduce( - (min, c) => c.size < min ? c.size : min, - codeSizes[0].size, -) - -const codeSizeTable = new Table({ - head: ['Implementation', 'Code Size (bytes)', 'Size %'], - style: { - head: ['cyan'], - }, -}) - -for (const entry of codeSizes) { - const sizePercent = ((entry.size / minCodeSize) * 100).toFixed(1) - codeSizeTable.push([ - entry.name, - entry.size.toString(), - `${sizePercent}%`, - ]) -} - -console.log('\n' + codeSizeTable.toString()) diff --git a/contracts/fibonacci.sol b/contracts/fibonacci.sol new file mode 100644 index 0000000..af3249d --- /dev/null +++ b/contracts/fibonacci.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +contract Fibonacci { + function fibonacci(uint32 n) public { + uint32 result = _fibonacci(n); + if (result == 0) { + revert(); + } + } + + function _fibonacci(uint32 n) internal pure returns (uint32) { + if (n == 0) { + return 0; + } else if (n == 1) { + return 1; + } else { + return _fibonacci(n - 1) + _fibonacci(n - 2); + } + } +} diff --git a/contracts/MyToken.sol b/contracts/simple_token.sol similarity index 97% rename from contracts/MyToken.sol rename to contracts/simple_token.sol index 4012ef0..201bc1c 100644 --- a/contracts/MyToken.sol +++ b/contracts/simple_token.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.27; /// @title Minimal ERC-20-like token with only mint and transfer entry points. -contract MyToken { +contract SimpleToken { uint256 public totalSupply; mapping(address => uint256) private balances; diff --git a/contracts/Storage.sol b/contracts/storage.sol similarity index 100% rename from contracts/Storage.sol rename to contracts/storage.sol diff --git a/deno.json b/deno.json index 792b9c8..62cee2b 100644 --- a/deno.json +++ b/deno.json @@ -17,17 +17,19 @@ "imports": { "@openzeppelin/contracts": "npm:@openzeppelin/contracts@^5.4.0", - "@parity/resolc": "npm:@parity/resolc@0.5.0", "@std/cli": "jsr:@std/cli@^1.0.24", + "@std/collections": "jsr:@std/collections@^1.1.3", + "@std/fs": "jsr:@std/fs@^1.0.20", "@std/path": "jsr:@std/path@^1.1.3", "@std/log": "jsr:@std/log@^0.224.14", "cli-table3": "npm:cli-table3@^0.6.5", "ink": "npm:ink@^6.5.1", - "react": "npm:react@^19.2.1", - "react/jsx-runtime": "npm:react@^19.2.1/jsx-runtime", + "react": "npm:react@^19.2.3", + "react/jsx-runtime": "npm:react@^19.2.3/jsx-runtime", "solc": "npm:solc@^0.8.31", - "viem": "npm:viem@^2.41.2", - "viem/accounts": "npm:viem@^2.41.2/accounts" + "tablemark": "npm:tablemark@^4.1.0", + "viem": "npm:viem@^2.43.1", + "viem/accounts": "npm:viem@^2.43.1/accounts" }, "compilerOptions": { "strict": true, diff --git a/deno.lock b/deno.lock index d172d93..41f2574 100644 --- a/deno.lock +++ b/deno.lock @@ -2,33 +2,54 @@ "version": "5", "specifiers": { "jsr:@std/cli@^1.0.24": "1.0.24", + "jsr:@std/collections@^1.1.3": "1.1.3", "jsr:@std/fmt@^1.0.5": "1.0.8", - "jsr:@std/fs@^1.0.11": "1.0.19", + "jsr:@std/fs@*": "1.0.19", + "jsr:@std/fs@^1.0.11": "1.0.20", + "jsr:@std/fs@^1.0.20": "1.0.20", "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/internal@^1.0.9": "1.0.12", "jsr:@std/io@~0.225.2": "0.225.2", "jsr:@std/log@~0.224.14": "0.224.14", + "jsr:@std/path@*": "1.1.3", + "jsr:@std/path@^1.1.1": "1.1.3", "jsr:@std/path@^1.1.3": "1.1.3", "npm:@openzeppelin/contracts@^5.4.0": "5.4.0", - "npm:@parity/resolc@0.5.0": "0.5.0", "npm:cli-table3@*": "0.6.5", "npm:cli-table3@~0.6.5": "0.6.5", - "npm:ink@^6.5.1": "6.5.1_react@19.2.1", - "npm:react@^19.2.1": "19.2.1", + "npm:ink@^6.5.1": "6.5.1_react@19.2.3", + "npm:react@^19.2.3": "19.2.3", "npm:solc@~0.8.31": "0.8.31", - "npm:viem@^2.41.2": "2.41.2_ws@8.18.3" + "npm:tablemark@*": "4.1.0", + "npm:tablemark@^4.1.0": "4.1.0", + "npm:viem@^2.43.1": "2.43.1_ws@8.18.3" }, "jsr": { "@std/cli@1.0.24": { "integrity": "b655a5beb26aa94f98add6bc8889f5fb9bc3ee2cc3fc954e151201f4c4200a5e", "dependencies": [ - "jsr:@std/internal" + "jsr:@std/internal@^1.0.12" ] }, + "@std/collections@1.1.3": { + "integrity": "bf8b0818886df6a32b64c7d3b037a425111f28278d69fd0995aeb62777c986b0" + }, "@std/fmt@1.0.8": { "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" }, "@std/fs@1.0.19": { - "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06" + "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", + "dependencies": [ + "jsr:@std/internal@^1.0.9", + "jsr:@std/path@^1.1.1" + ] + }, + "@std/fs@1.0.20": { + "integrity": "e953206aae48d46ee65e8783ded459f23bec7dd1f3879512911c35e5484ea187", + "dependencies": [ + "jsr:@std/internal@^1.0.12", + "jsr:@std/path@^1.1.3" + ] }, "@std/internal@1.0.12": { "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" @@ -40,14 +61,14 @@ "integrity": "257f7adceee3b53bb2bc86c7242e7d1bc59729e57d4981c4a7e5b876c808f05e", "dependencies": [ "jsr:@std/fmt", - "jsr:@std/fs", + "jsr:@std/fs@^1.0.11", "jsr:@std/io" ] }, "@std/path@1.1.3": { "integrity": "b015962d82a5e6daea980c32b82d2c40142149639968549c649031a230b1afb3", "dependencies": [ - "jsr:@std/internal" + "jsr:@std/internal@^1.0.12" ] } }, @@ -80,34 +101,6 @@ "@openzeppelin/contracts@5.4.0": { "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==" }, - "@parity/resolc@0.5.0": { - "integrity": "sha512-zSkg5xfkK/rLj+vvf2jinSFX/ih/gJ44DOV4r6Gksz7k0t1Jpg3GtchCnCfGA/aQZXoRWRsCradX3JqE6wgl4w==", - "dependencies": [ - "@types/node", - "commander@13.1.0", - "package-json", - "resolve-pkg", - "solc@0.8.30" - ], - "bin": true - }, - "@pnpm/config.env-replace@1.1.0": { - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==" - }, - "@pnpm/network.ca-file@1.0.2": { - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dependencies": [ - "graceful-fs" - ] - }, - "@pnpm/npm-conf@2.3.1": { - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", - "dependencies": [ - "@pnpm/config.env-replace", - "@pnpm/network.ca-file", - "config-chain" - ] - }, "@scure/base@1.2.6": { "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==" }, @@ -126,14 +119,8 @@ "@scure/base" ] }, - "@types/node@22.19.1": { - "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", - "dependencies": [ - "undici-types" - ] - }, - "abitype@1.1.0": { - "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==" + "abitype@1.2.3": { + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==" }, "ansi-escapes@7.2.0": { "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", @@ -156,6 +143,9 @@ "chalk@5.6.2": { "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==" }, + "change-case@5.4.4": { + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==" + }, "cli-boxes@3.0.0": { "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==" }, @@ -190,25 +180,12 @@ "command-exists@1.2.9": { "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" }, - "commander@13.1.0": { - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==" - }, "commander@8.3.0": { "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" }, - "config-chain@1.1.13": { - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dependencies": [ - "ini", - "proto-list" - ] - }, "convert-to-spaces@2.0.1": { "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==" }, - "deep-extend@0.6.0": { - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, "emoji-regex@10.6.0": { "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" }, @@ -233,16 +210,10 @@ "get-east-asian-width@1.4.0": { "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==" }, - "graceful-fs@4.2.10": { - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, "indent-string@5.0.0": { "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==" }, - "ini@1.3.8": { - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "ink@6.5.1_react@19.2.1": { + "ink@6.5.1_react@19.2.3": { "integrity": "sha512-wF3j/DmkM8q5E+OtfdQhCRw8/0ahkc8CUTgEddxZzpEWPslu7YPL3t64MWRoI9m6upVGpfAg4ms2BBvxCdKRLQ==", "dependencies": [ "@alcalzone/ansi-tokenize", @@ -293,18 +264,12 @@ "js-sha3@0.8.0": { "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, - "ky@1.14.0": { - "integrity": "sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==" - }, "memorystream@0.3.1": { "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==" }, "mimic-fn@2.1.0": { "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, - "minimist@1.2.8": { - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, "onetime@5.1.2": { "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": [ @@ -314,8 +279,8 @@ "os-tmpdir@1.0.2": { "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" }, - "ox@0.9.6": { - "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", + "ox@0.10.5": { + "integrity": "sha512-mXJRiZswmX46abrzNkJpTN9sPJ/Rhevsp5Dfg0z80D55aoLNmEV4oN+/+feSNW593c2CnHavMqSVBanpJ0lUkQ==", "dependencies": [ "@adraffy/ens-normalize", "@noble/ciphers", @@ -327,61 +292,18 @@ "eventemitter3" ] }, - "package-json@10.0.1": { - "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", - "dependencies": [ - "ky", - "registry-auth-token", - "registry-url", - "semver@7.7.3" - ] - }, "patch-console@2.0.0": { "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==" }, - "proto-list@1.2.4": { - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" - }, - "rc@1.2.8": { - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": [ - "deep-extend", - "ini", - "minimist", - "strip-json-comments" - ], - "bin": true - }, - "react-reconciler@0.33.0_react@19.2.1": { + "react-reconciler@0.33.0_react@19.2.3": { "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", "dependencies": [ "react", "scheduler" ] }, - "react@19.2.1": { - "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==" - }, - "registry-auth-token@5.1.0": { - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", - "dependencies": [ - "@pnpm/npm-conf" - ] - }, - "registry-url@6.0.1": { - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dependencies": [ - "rc" - ] - }, - "resolve-from@5.0.0": { - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - }, - "resolve-pkg@2.0.0": { - "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", - "dependencies": [ - "resolve-from" - ] + "react@19.2.3": { + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==" }, "restore-cursor@4.0.0": { "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", @@ -397,10 +319,6 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "bin": true }, - "semver@7.7.3": { - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "bin": true - }, "signal-exit@3.0.7": { "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, @@ -411,28 +329,15 @@ "is-fullwidth-code-point@5.1.0" ] }, - "solc@0.8.30": { - "integrity": "sha512-9Srk/gndtBmoUbg4CE6ypAzPQlElv8ntbnl6SigUBAzgXKn35v87sj04uZeoZWjtDkdzT0qKFcIo/wl63UMxdw==", - "dependencies": [ - "command-exists", - "commander@8.3.0", - "follow-redirects", - "js-sha3", - "memorystream", - "semver@5.7.2", - "tmp" - ], - "bin": true - }, "solc@0.8.31": { "integrity": "sha512-wpccgDgu/aE/rRcF2F/LeN+4knK0734XTcjppyaQOticjYd/Giq1AJE3XPQZKEViAsY3sNaFKl7QpMRYrK35vg==", "dependencies": [ "command-exists", - "commander@8.3.0", + "commander", "follow-redirects", "js-sha3", "memorystream", - "semver@5.7.2", + "semver", "tmp" ], "bin": true @@ -478,8 +383,15 @@ "ansi-regex@6.2.2" ] }, - "strip-json-comments@2.0.1": { - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + "tablemark@4.1.0": { + "integrity": "sha512-B3LDjbDo+ac+D5RwkBOPZZ6ua8716KdT+6NO3DKOCHJq0ezE6vV2r92rjrC1ci2H+ocuysl5ytf1T0QqV65yoA==", + "dependencies": [ + "ansi-regex@6.2.2", + "change-case", + "string-width@8.1.0", + "wordwrapjs", + "wrap-ansi" + ] }, "tmp@0.0.33": { "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", @@ -490,11 +402,8 @@ "type-fest@4.41.0": { "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" }, - "undici-types@6.21.0": { - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" - }, - "viem@2.41.2_ws@8.18.3": { - "integrity": "sha512-LYliajglBe1FU6+EH9mSWozp+gRA/QcHfxeD9Odf83AdH5fwUS7DroH4gHvlv6Sshqi1uXrYFA2B/EOczxd15g==", + "viem@2.43.1_ws@8.18.3": { + "integrity": "sha512-S33pBNlRvOlVv4+L94Z8ydCMDB1j0cuHFUvaC28i6OTxw3uY1P4M3h1YDFK8YC1H9/lIbeBTTvCRhi0FqU/2iw==", "dependencies": [ "@noble/curves", "@noble/hashes", @@ -512,6 +421,9 @@ "string-width@7.2.0" ] }, + "wordwrapjs@5.1.1": { + "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==" + }, "wrap-ansi@9.0.2": { "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dependencies": [ @@ -530,15 +442,17 @@ "workspace": { "dependencies": [ "jsr:@std/cli@^1.0.24", + "jsr:@std/collections@^1.1.3", + "jsr:@std/fs@^1.0.20", "jsr:@std/log@~0.224.14", "jsr:@std/path@^1.1.3", "npm:@openzeppelin/contracts@^5.4.0", - "npm:@parity/resolc@0.5.0", "npm:cli-table3@~0.6.5", "npm:ink@^6.5.1", - "npm:react@^19.2.1", + "npm:react@^19.2.3", "npm:solc@~0.8.31", - "npm:viem@^2.41.2" + "npm:tablemark@^4.1.0", + "npm:viem@^2.43.1" ] } } diff --git a/ink/fibonacci/.gitignore b/ink/fibonacci/.gitignore new file mode 100755 index 0000000..8de8f87 --- /dev/null +++ b/ink/fibonacci/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/ink/fibonacci/Cargo.toml b/ink/fibonacci/Cargo.toml new file mode 100755 index 0000000..a65a796 --- /dev/null +++ b/ink/fibonacci/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "fibonacci" +version = "0.1.0" +authors = ["[your_name] <[your_email]>"] +edition = "2024" + +[dependencies] +ink = { version = "6.0.0-beta.1", default-features = false, features = ["unstable-hostfn"] } + +[dev-dependencies] +ink_e2e = { version = "6.0.0-beta.1" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "sol" + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(ink_abi, values("ink", "sol", "all"))' +] diff --git a/ink/fibonacci/lib.rs b/ink/fibonacci/lib.rs new file mode 100755 index 0000000..3d2adfb --- /dev/null +++ b/ink/fibonacci/lib.rs @@ -0,0 +1,34 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod fibonacci { + + #[ink(storage)] + pub struct Fibonacci; + + impl Fibonacci { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + #[ink(message)] + pub fn fibonacci(&self, n: u32) -> Result<(), ()> { + let result = super::_fibonacci(n); + if result == 0 { + return Err(()); + } + Ok(()) + } + } +} + +fn _fibonacci(n: u32) -> u32 { + if n == 0 { + 0 + } else if n == 1 { + 1 + } else { + _fibonacci(n - 1) + _fibonacci(n - 2) + } +} diff --git a/ink/ink_erc20/.gitignore b/ink/simple_token/.gitignore similarity index 100% rename from ink/ink_erc20/.gitignore rename to ink/simple_token/.gitignore diff --git a/ink/ink_erc20/Cargo.toml b/ink/simple_token/Cargo.toml similarity index 66% rename from ink/ink_erc20/Cargo.toml rename to ink/simple_token/Cargo.toml index 2f08199..78b49a4 100644 --- a/ink/ink_erc20/Cargo.toml +++ b/ink/simple_token/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ink_erc20" +name = "simple_token" version = "6.0.0-beta.1" authors = ["Use Ink "] edition = "2024" @@ -8,19 +8,12 @@ publish = false [dependencies] ink = { version = "6.0.0-beta.1", default-features = false } -[dev-dependencies] -ink_e2e = { version = "6.0.0-beta.1" } - [lib] path = "lib.rs" [features] default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] +std = [ "ink/std", ] [package.metadata.ink-lang] abi = "sol" diff --git a/ink/ink_erc20/lib.rs b/ink/simple_token/lib.rs similarity index 92% rename from ink/ink_erc20/lib.rs rename to ink/simple_token/lib.rs index 2d27547..29980f1 100644 --- a/ink/ink_erc20/lib.rs +++ b/ink/simple_token/lib.rs @@ -1,8 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] #[ink::contract] -mod ink_erc20 { - use ink::{U256, storage::Mapping}; +mod simple_token { + use ink::{storage::Mapping, U256}; /// The zero address, used in minting and burning operations. const ZERO_ADDRESS: Address = Address::repeat_byte(0); @@ -51,14 +51,6 @@ mod ink_erc20 { self.total_supply } - /// Returns the account balance for the specified `owner`. - /// - /// Returns `0` if the account is non-existent. - #[ink(message)] - pub fn balance_of(&self, owner: Address) -> U256 { - self.balance_of_impl(&owner) - } - /// Returns the account balance for the specified `owner`. /// /// Returns `0` if the account is non-existent. diff --git a/rust/contract_no_alloc/Cargo.lock b/rust/contract_no_alloc/Cargo.lock deleted file mode 100644 index 2ce27fa..0000000 --- a/rust/contract_no_alloc/Cargo.lock +++ /dev/null @@ -1,157 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "const-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c06f1eb05f06cf2e380fdded278fbf056a38974299d77960555a311dcf91a52" -dependencies = [ - "keccak-const", - "sha2-const-stable", -] - -[[package]] -name = "contract_no_alloc" -version = "0.1.0" -dependencies = [ - "pallet-revive-uapi", - "picoalloc", - "polkavm-derive", -] - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "keccak-const" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" - -[[package]] -name = "pallet-revive-proc-macro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed97af646322cfc2d394c4737874bf6df507d25dd421a2939304eee02d89c742" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pallet-revive-uapi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0bf9c852c4426130520d546fe9ea0d932914c42ed7ae2970b5e428a3efe7e1" -dependencies = [ - "bitflags", - "const-crypto", - "hex-literal", - "pallet-revive-proc-macro", - "polkavm-derive", -] - -[[package]] -name = "picoalloc" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1912f9b1d3aea43590e3986afdcf4ed1d9662edae24744a095738157d65b6a" - -[[package]] -name = "picosimd" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af35c838647fef3d6d052e27006ef88ea162336eee33063c50a63f163c18cdeb" - -[[package]] -name = "polkavm-common" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1b408db93d4f49f5c651a7844682b9d7a561827b4dc6202c10356076c055c9" -dependencies = [ - "picosimd", -] - -[[package]] -name = "polkavm-derive" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb4463fb0b9dbfafdc1d1a1183df4bf7afa3350d124f29d5700c6bee54556b5" -dependencies = [ - "polkavm-derive-impl-macro", -] - -[[package]] -name = "polkavm-derive-impl" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993ff45b972e09babe68adce7062c3c38a84b9f50f07b7caf393a023eaa6c74a" -dependencies = [ - "polkavm-common", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "polkavm-derive-impl-macro" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4f5352e13c1ca5f0e4d7b4a804fbb85b0e02c45cae435d101fe71081bc8ed8" -dependencies = [ - "polkavm-derive-impl", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "sha2-const-stable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" - -[[package]] -name = "syn" -version = "2.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/rust/contract_no_alloc/Cargo.toml b/rust/contract_no_alloc/Cargo.toml deleted file mode 100644 index f9530f6..0000000 --- a/rust/contract_no_alloc/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "contract_no_alloc" -version = "0.1.0" -edition = "2021" -publish = false - -[[bin]] -name = "contract" -path = "src/contract.rs" - -[profile.release] -opt-level = 3 -lto = true -codegen-units = 1 - -[dependencies] -polkavm-derive = { version = "0.30.0" } -picoalloc = "5.2.0" -pallet-revive-uapi = { version = "0.9.0", default-features = false } - - - - diff --git a/rust/contract_with_alloc/.cargo/config.toml b/rust/contract_with_alloc/.cargo/config.toml deleted file mode 100644 index 8ae662c..0000000 --- a/rust/contract_with_alloc/.cargo/config.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Use a standard rust riscv64 target for cargo check and rust-anaylser -# cargo pvm will use `polkavm_linker::TargetJsonArgs::default()` -[build] -target = "riscv64imac-unknown-none-elf" - - - diff --git a/rust/contract_with_alloc/.gitignore b/rust/contract_with_alloc/.gitignore deleted file mode 100644 index 2dbb5ec..0000000 --- a/rust/contract_with_alloc/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/*.polkavm diff --git a/rust/contract_with_alloc/contract.sol b/rust/contract_with_alloc/contract.sol deleted file mode 100644 index 3f90238..0000000 --- a/rust/contract_with_alloc/contract.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.27; - -interface MyToken { - event Transfer(address indexed from, address indexed to, uint256 value); - error InsufficientBalance(); - - function totalSupply() external view returns (uint256); - function balanceOf(address account) external view returns (uint256); - - function transfer(address to, uint256 amount) external; - function mint(address to, uint256 amount) external; -} diff --git a/rust/contract_no_alloc/.cargo/config.toml b/rust/contracts/.cargo/config.toml similarity index 100% rename from rust/contract_no_alloc/.cargo/config.toml rename to rust/contracts/.cargo/config.toml diff --git a/rust/contract_no_alloc/.gitignore b/rust/contracts/.gitignore similarity index 100% rename from rust/contract_no_alloc/.gitignore rename to rust/contracts/.gitignore diff --git a/rust/contract_with_alloc/Cargo.lock b/rust/contracts/Cargo.lock similarity index 99% rename from rust/contract_with_alloc/Cargo.lock rename to rust/contracts/Cargo.lock index e52c15d..0dfed1e 100644 --- a/rust/contract_with_alloc/Cargo.lock +++ b/rust/contracts/Cargo.lock @@ -140,7 +140,7 @@ dependencies = [ ] [[package]] -name = "contract_with_alloc" +name = "contracts" version = "0.1.0" dependencies = [ "alloy-core", diff --git a/rust/contract_with_alloc/Cargo.toml b/rust/contracts/Cargo.toml similarity index 52% rename from rust/contract_with_alloc/Cargo.toml rename to rust/contracts/Cargo.toml index 93e350d..6775712 100644 --- a/rust/contract_with_alloc/Cargo.toml +++ b/rust/contracts/Cargo.toml @@ -1,12 +1,28 @@ [package] -name = "contract_with_alloc" +name = "contracts" version = "0.1.0" edition = "2021" publish = false [[bin]] -name = "contract" -path = "src/contract.rs" +name = "simple_token_no_alloc" +path = "src/simple_token_no_alloc.rs" + +[[bin]] +name = "erc20_with_alloc" +path = "src/erc20_with_alloc.rs" + +[[bin]] +name = "fibonacci" +path = "src/fibonacci.rs" + +[[bin]] +name = "fibonacci_u128" +path = "src/fibonacci_u128.rs" + +[[bin]] +name = "fibonacci_u256" +path = "src/fibonacci_u256.rs" [profile.release] opt-level = 3 @@ -22,3 +38,4 @@ pallet-revive-uapi = { version = "0.9.0", default-features = false } + diff --git a/rust/contract_no_alloc/contract.sol b/rust/contracts/MyToken.sol similarity index 100% rename from rust/contract_no_alloc/contract.sol rename to rust/contracts/MyToken.sol diff --git a/rust/contracts/src/fibonacci.rs b/rust/contracts/src/fibonacci.rs new file mode 100644 index 0000000..9d7c0dd --- /dev/null +++ b/rust/contracts/src/fibonacci.rs @@ -0,0 +1,56 @@ +#![no_main] +#![no_std] + +use pallet_revive_uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // Safety: The unimp instruction is guaranteed to trap + unsafe { + core::arch::asm!("unimp"); + core::hint::unreachable_unchecked(); + } +} + +/// This is the constructor which is called once per contract. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +/// This is the constructor which is called once per contract. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // We want this contract to be called with the following ABI: + // function fibonacci(uint32) external; + // event FibonacciComputed(uint32 indexed n, uint32 result); + + // โฏ cast calldata "Fibonacci(uint)" "42" | xxd -r -p | xxd -c 32 -g 1 + //00000000: 50 7a 10 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + //00000020: 00 00 00 2a + + // The input is abi encoded as follows: + // - 4 byte selector + // - 32 byte padded integer + + // the actual 4 byte integer is stored at offset 32 + let mut input = [0u8; 4]; + api::call_data_copy(&mut input, 32); + + let n = u32::from_be_bytes(input); + let result = _fibonacci(n); + + if result == 0 { + api::return_value(ReturnFlags::REVERT, &[0u8; 0]); + } +} + +fn _fibonacci(n: u32) -> u32 { + if n == 0 { + 0 + } else if n == 1 { + 1 + } else { + _fibonacci(n - 1) + _fibonacci(n - 2) + } +} diff --git a/rust/contracts/src/fibonacci_u128.rs b/rust/contracts/src/fibonacci_u128.rs new file mode 100644 index 0000000..27b3e18 --- /dev/null +++ b/rust/contracts/src/fibonacci_u128.rs @@ -0,0 +1,55 @@ +#![no_main] +#![no_std] + +use pallet_revive_uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // Safety: The unimp instruction is guaranteed to trap + unsafe { + core::arch::asm!("unimp"); + core::hint::unreachable_unchecked(); + } +} + +/// This is the constructor which is called once per contract. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +/// This is the constructor which is called once per contract. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // We want this contract to be called with the following ABI: + // function fibonacci(uint32) external; + // event FibonacciComputed(uint32 indexed n, uint32 result); + + // โฏ cast calldata "Fibonacci(uint)" "42" | xxd -r -p | xxd -c 32 -g 1 + //00000000: 50 7a 10 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + //00000020: 00 00 00 2a + + // The input is abi encoded as follows: + // - 4 byte selector + // - 32 byte padded integer + + // the actual 16 byte integer is stored at offset 20 + let mut input = [0u8; 16]; + api::call_data_copy(&mut input, 20); + + let n = u128::from_be_bytes(input); + let result = _fibonacci(n); + if result & 0xFFFF == 0xBEEF { + api::return_value(ReturnFlags::REVERT, &[0u8; 0]); + } +} + +fn _fibonacci(n: u128) -> u128 { + if n == 0 { + 0 + } else if n == 1 { + 1 + } else { + _fibonacci(n - 1) + _fibonacci(n - 2) + } +} diff --git a/rust/contracts/src/fibonacci_u256.rs b/rust/contracts/src/fibonacci_u256.rs new file mode 100644 index 0000000..d75ddef --- /dev/null +++ b/rust/contracts/src/fibonacci_u256.rs @@ -0,0 +1,66 @@ +#![no_main] +#![no_std] + +use alloy_core::primitives::U256; +use pallet_revive_uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[global_allocator] +static mut ALLOC: picoalloc::Mutex>> = { + static mut ARRAY: picoalloc::Array<1024> = picoalloc::Array([0u8; 1024]); + + picoalloc::Mutex::new(picoalloc::Allocator::new(unsafe { + picoalloc::ArrayPointer::new(&raw mut ARRAY) + })) +}; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // Safety: The unimp instruction is guaranteed to trap + unsafe { + core::arch::asm!("unimp"); + core::hint::unreachable_unchecked(); + } +} + +/// This is the constructor which is called once per contract. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +/// This is the constructor which is called once per contract. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // We want this contract to be called with the following ABI: + // function fibonacci(uint256) external; + // event FibonacciComputed(uint256 indexed n, uint256 result); + + // โฏ cast calldata "Fibonacci(uint256)" "42" | xxd -r -p | xxd -c 32 -g 1 + //00000000: 50 7a 10 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + //00000020: 00 00 00 2a + + // The input is abi encoded as follows: + // - 4 byte selector + // - 32 byte integer + + // the 32 byte integer is stored at offset 4 + let mut input = [0u8; 32]; + api::call_data_copy(&mut input, 4); + + let n = U256::from_be_bytes(input); + let result = _fibonacci(n); + + if result == 0 { + api::return_value(ReturnFlags::REVERT, &[0u8; 0]); + } +} + +fn _fibonacci(n: U256) -> U256 { + if n == U256::ZERO { + U256::ZERO + } else if n == U256::from(1) { + U256::ONE + } else { + _fibonacci(n - U256::ONE) + _fibonacci(n - U256::from(2)) + } +} diff --git a/rust/contract_no_alloc/src/contract.rs b/rust/contracts/src/simple_token_no_alloc.rs similarity index 98% rename from rust/contract_no_alloc/src/contract.rs rename to rust/contracts/src/simple_token_no_alloc.rs index 378762b..ac050a0 100644 --- a/rust/contract_no_alloc/src/contract.rs +++ b/rust/contracts/src/simple_token_no_alloc.rs @@ -26,7 +26,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { } /// Storage key for totalSupply (slot 0) -#[inline] +#[inline(always)] fn total_supply_key() -> [u8; 32] { [0u8; 32] // Slot 0 } @@ -60,7 +60,7 @@ fn get_total_supply() -> u128 { } } -// #[inline(always)] +#[inline(always)] fn to_word(v: u128) -> [u8; 32] { let mut out = [0u8; 32]; out[16..].copy_from_slice(&v.to_be_bytes()); @@ -68,7 +68,6 @@ fn to_word(v: u128) -> [u8; 32] { } /// Set totalSupply in storage -#[inline] fn set_total_supply(amount: u128) { let key = total_supply_key(); let bytes = amount.to_be_bytes(); @@ -76,7 +75,6 @@ fn set_total_supply(amount: u128) { } /// Get the balance for a given address from storage -#[inline] fn get_balance(addr: &[u8; 20]) -> u128 { let key = balance_key(addr); let mut balance_bytes = [0u8; 16]; @@ -89,7 +87,7 @@ fn get_balance(addr: &[u8; 20]) -> u128 { } /// Set the balance for a given address in storage -#[inline] +#[inline(always)] fn set_balance(addr: &[u8; 20], amount: u128) { let key = balance_key(addr); let bytes = amount.to_be_bytes(); @@ -97,7 +95,6 @@ fn set_balance(addr: &[u8; 20], amount: u128) { } /// Emit a Transfer event -#[inline] fn emit_transfer(from: &[u8; 20], to: &[u8; 20], value: u128) { let mut from_topic = [0u8; 32]; from_topic[12..32].copy_from_slice(from); @@ -111,13 +108,13 @@ fn emit_transfer(from: &[u8; 20], to: &[u8; 20], value: u128) { } /// Revert with an InsufficientBalance error -#[inline] +#[inline(always)] fn revert_insufficient_balance() -> ! { api::return_value(ReturnFlags::REVERT, &INSUFFICIENT_BALANCE_ERROR); } /// Get the caller's address -#[inline] +#[inline(always)] fn get_caller() -> [u8; 20] { let mut caller = [0u8; 20]; api::caller(&mut caller); diff --git a/rust/contract_with_alloc/src/contract.rs b/rust/contracts/src/simple_token_with_alloc.rs similarity index 99% rename from rust/contract_with_alloc/src/contract.rs rename to rust/contracts/src/simple_token_with_alloc.rs index d256548..c7e0b87 100644 --- a/rust/contract_with_alloc/src/contract.rs +++ b/rust/contracts/src/simple_token_with_alloc.rs @@ -11,7 +11,7 @@ use pallet_revive_uapi::{HostFn, HostFnImpl as api, ReturnFlags, StorageFlags}; extern crate alloc; use alloc::vec; -sol!("contract.sol"); +sol!("MyToken.sol"); use crate::MyToken::transferCall; #[global_allocator] diff --git a/scripts/build_tokens.sh b/scripts/build_contracts.sh similarity index 56% rename from scripts/build_tokens.sh rename to scripts/build_contracts.sh index 68c44a3..1a6ca84 100755 --- a/scripts/build_tokens.sh +++ b/scripts/build_contracts.sh @@ -14,7 +14,12 @@ fi # Build ink token contract echo "Building ink token contract..." -cd ink/ink_erc20 +cd ink/simple_token +pop build --release +cd ../.. + +echo "Building ink fibonacci contract..." +cd ink/fibonacci pop build --release cd ../.. @@ -25,18 +30,19 @@ if ! cargo pvm-contract --version &>/dev/null; then fi # Build PVM contracts -echo "Building PVM contract without alloc..." -cd rust/contract_no_alloc -cargo pvm-contract build -cd ../.. - -echo "Building PVM contract with alloc..." -cd rust/contract_with_alloc -cargo pvm-contract build +echo "Building PVM contracts" +cd rust/contracts +cargo pvm-contract build -b simple_token_no_alloc +cargo pvm-contract build -b erc20_with_alloc +cargo pvm-contract build -b fibonacci +cargo pvm-contract build -b fibonacci_u128 +cargo pvm-contract build -b fibonacci_u256 cd ../.. -# Build PVM and resolc contracts +# Build Solidity EVM and resolc contracts echo "Building PVM and resolc contracts..." deno task build --filter MyToken +deno task build --filter Fibonacci +deno task build --filter Fibonacci -echo "All token contracts built successfully!" +echo "All contracts built successfully!" diff --git a/scripts/node-env.sh b/scripts/node-env.sh index d7beb2d..d2af6d9 100755 --- a/scripts/node-env.sh +++ b/scripts/node-env.sh @@ -378,6 +378,7 @@ function eth-rpc() { --no-prometheus \ --dev \ --rpc-port 8546 \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" 2>&1 | tee /tmp/eth-rpc.log | @@ -394,6 +395,7 @@ function eth-rpc() { --no-prometheus \ --dev \ --rpc-port 8546 \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" 2>&1 | tee /tmp/eth-rpc.log | @@ -412,6 +414,7 @@ function eth-rpc() { --no-prometheus \ --dev \ --rpc-port 8546 \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" 2>&1 | lnav { set +x; } 2>/dev/null @@ -422,6 +425,7 @@ function eth-rpc() { --no-prometheus \ --dev \ --rpc-port 8546 \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" { set +x; } 2>/dev/null @@ -470,6 +474,7 @@ function eth-rpc() { --log="$RUST_LOG" \ --no-prometheus \ --dev \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" 2>&1 | tee /tmp/eth-rpc.log | @@ -485,6 +490,7 @@ function eth-rpc() { --log="$RUST_LOG" \ --no-prometheus \ --dev \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" 2>&1 | tee /tmp/eth-rpc.log | @@ -502,6 +508,7 @@ function eth-rpc() { --log="$RUST_LOG" \ --no-prometheus \ --dev \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" 2>&1 | lnav { set +x; } 2>/dev/null @@ -511,6 +518,7 @@ function eth-rpc() { --log="$RUST_LOG" \ --no-prometheus \ --dev \ + --rpc-max-response-size 50 \ --node-rpc-url "$NODE_RPC_URL" \ "${args[@]}" { set +x; } 2>/dev/null diff --git a/tools/build.ts b/tools/build.ts index 36f47fe..e5d9889 100644 --- a/tools/build.ts +++ b/tools/build.ts @@ -1,55 +1,19 @@ -/// - -import solc from 'solc' -import { basename, join } from '@std/path' -import * as log from '@std/log' +import { join } from '@std/path' import { parseArgs } from '@std/cli' +import { compile, generateAbiIndex } from '../utils/build.ts' +import { logger } from '../utils/logger.ts' -type CompileInput = Record - -interface SolcOutput { - errors?: Array<{ - severity: string - formattedMessage: string - }> - contracts: Record< - string, - Record - > -} - -const LOG_LEVEL = (Deno.env.get('LOG_LEVEL')?.toUpperCase() ?? - 'INFO') as log.LevelName -log.setup({ - handlers: { - console: new log.ConsoleHandler(LOG_LEVEL), - }, - loggers: { - default: { - level: LOG_LEVEL, - handlers: ['console'], - }, - }, -}) - -const logger = log.getLogger() -const { filter, solcOnly, force, clean } = parseArgs(Deno.args, { +const { filter, solcOnly, clean } = parseArgs(Deno.args, { string: ['filter'], - boolean: ['solcOnly', 'clean', 'force'], + boolean: ['solcOnly', 'clean'], }) +const currentDir = new URL('.', import.meta.url).pathname +const rootDir = join(currentDir, '..') + // Handle clean flag if (clean) { logger.info('๐Ÿงน Cleaning generated files in codegen directory...') - const currentDir = new URL('.', import.meta.url).pathname - const rootDir = join(currentDir, '..') // Use git clean to remove untracked files const cleanCommand = new Deno.Command('git', { @@ -62,10 +26,6 @@ if (clean) { try { const cleanResult = await cleanCommand.output() if (cleanResult.success) { - const output = new TextDecoder().decode(cleanResult.stdout) - if (output.trim()) { - logger.info(output.trim()) - } logger.info('โœ… Cleaned codegen directory successfully') } else { logger.error(new TextDecoder().decode(cleanResult.stderr)) @@ -77,430 +37,40 @@ if (clean) { } } -async function format(code: string) { - const command = new Deno.Command('deno', { - args: ['fmt', '-'], - stdin: 'piped', - stdout: 'piped', - }) - - const child = command.spawn() - const writer = child.stdin.getWriter() - await writer.write(new TextEncoder().encode(code)) - await writer.close() - - const { stdout } = await child.output() - return stdout -} - -async function computeSha256(content: string): Promise { - const encoder = new TextEncoder() - const data = encoder.encode(content) - const hashBuffer = await crypto.subtle.digest('SHA-256', data) - const hashArray = Array.from(new Uint8Array(hashBuffer)) - return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') -} - -function readCachedHash(hashFile: string): string | null { - try { - return Deno.readTextFileSync(hashFile).trim() - } catch { - return null - } -} - -function writeCachedHash(hashFile: string, hash: string): void { - Deno.writeTextFileSync(hashFile, hash) -} - -let resolcBin = Deno.env.get('RESOLC_BIN') || '' -let resolcVersion = '' - -async function checkResolcExists() { - // If no RESOLC_BIN specified, find it in PATH - if (!resolcBin) { - // Try to find resolc, preferring cargo/system installations over node_modules - const pathsToCheck = [ - `${Deno.env.get('HOME')}/.cargo/bin/resolc`, - '/usr/local/bin/resolc', - '/usr/bin/resolc', - ] - - for (const path of pathsToCheck) { - try { - await Deno.stat(path) - resolcBin = path - break - } catch { - // Continue to next path - } - } - - // If not found in standard locations, use which command - if (!resolcBin) { - try { - const whichCommand = new Deno.Command('which', { - args: ['resolc'], - stdout: 'piped', - stderr: 'piped', - }) - const whichOutput = await whichCommand.output() - if (whichOutput.success) { - const foundPath = new TextDecoder().decode( - whichOutput.stdout, - ).trim() - // Skip if it's in node_modules - if (!foundPath.includes('node_modules')) { - resolcBin = foundPath - } - } - } catch { - // Continue - } - } - - if (!resolcBin) { - logger.error( - `Could not find resolc executable. Please install resolc or set RESOLC_BIN environment variable.`, - ) - Deno.exit(1) - } - } - - try { - const command = new Deno.Command(resolcBin, { - args: ['--version'], - stdout: 'piped', - stderr: 'piped', - }) - const output = await command.output() - if (!output.success) { - logger.error( - `Failed to run ${resolcBin}: ${ - new TextDecoder().decode(output.stderr) - }`, - ) - Deno.exit(1) - } - resolcVersion = new TextDecoder().decode(output.stdout).trim() - } catch (error) { - logger.error( - `Could not find ${resolcBin} executable. Please install resolc or set RESOLC_BIN environment variable.`, - ) - logger.error(`Error: ${error}`) - Deno.exit(1) - } -} - -async function pvmCompile(file: Deno.DirEntry, sources: CompileInput) { - if (resolcVersion === '') { - await checkResolcExists() - } - logger.info(`Compiling ${file.name} with ${resolcBin} ${resolcVersion}`) - logger.debug(`Using resolc binary: ${resolcBin}`) - - const input = { - language: 'Solidity', - sources, - settings: { - optimizer: { - enabled: true, - mode: 'z', - }, - remappings: [ - `@openzeppelin/=${ - join(rootDir, 'node_modules/@openzeppelin/') - }/`, - ], - outputSelection: { - '*': { - '*': [ - 'abi', - 'metadata', - 'evm.bytecode', - 'evm.deployedBytecode', - ], - }, - }, - }, - } - - const command = new Deno.Command(resolcBin, { - args: ['--standard-json'], - stdin: 'piped', - stdout: 'piped', - stderr: 'piped', - }) - - const child = command.spawn() - const writer = child.stdin.getWriter() - await writer.write(new TextEncoder().encode(JSON.stringify(input))) - await writer.close() - - const { stdout, stderr, success } = await child.output() - const stderrText = new TextDecoder().decode(stderr) - const stdoutText = new TextDecoder().decode(stdout) - - if (stderrText.trim().length > 0) { - logger.error(`resolc stderr: ${stderrText}`) - } - - if (!success) { - logger.error(`resolc command failed with exit code`) - Deno.exit(1) - } - - try { - const result = JSON.parse(stdoutText) - - // Check for errors in the compilation output - if (result.errors) { - for (const error of result.errors) { - if (error.severity === 'error') { - logger.error(error.formattedMessage || error.message) - } else if (error.severity === 'warning') { - logger.warn(error.formattedMessage || error.message) - } - } - - if ( - result.errors.some((err: { severity: string }) => - err.severity === 'error' - ) - ) { - Deno.exit(1) - } - } - - return result - } catch (e) { - logger.error(`Failed to parse resolc output: ${e}`) - logger.error(`Output was: ${stdoutText}`) - Deno.exit(1) - } -} - -function tryResolveImport(importPath: string): string { - // Try node_modules first for package imports - if (importPath.startsWith('@')) { - const nodeModulesPath = join(rootDir, 'node_modules', importPath) - try { - Deno.statSync(nodeModulesPath) - return nodeModulesPath - } catch { - // Continue to other resolution strategies - } - } - - // Try relative to contracts directory - const contractsPath = join(contractsDir, importPath) - try { - Deno.statSync(contractsPath) - return contractsPath - } catch { - // Continue to other resolution strategies - } - - // Try relative to root directory - const rootPath = join(rootDir, importPath) - try { - Deno.statSync(rootPath) - return rootPath - } catch { - throw new Error(`Could not resolve import: ${importPath}`) - } -} - -let solcVersion = '' -function evmCompile(file: Deno.DirEntry, sources: CompileInput) { - if (solcVersion === '') { - solcVersion = solc.version() - } - logger.info(`Compile ${file.name} with solc ${solcVersion}`) - const input = { - language: 'Solidity', - sources, - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - outputSelection: { - '*': { - '*': ['*'], - }, - }, - }, - } - - return solc.compile(JSON.stringify(input), { - import: (relativePath: string) => { - const source = Deno.readTextFileSync( - tryResolveImport(relativePath), - ) - return { contents: source } - }, - }) -} - logger.debug('Compiling contracts...') -const currentDir = new URL('.', import.meta.url).pathname -const rootDir = join(currentDir, '..') const contractsDir = join(rootDir, 'contracts') -const codegenDir = join(rootDir, 'codegen') -const abiDir = join(codegenDir, 'abi') -const pvmDir = join(codegenDir, 'pvm') -const evmDir = join(codegenDir, 'evm') -let generateAbiIndex = false - -const input = Array.from(Deno.readDirSync(contractsDir)) +const contracts = Array.from(Deno.readDirSync(contractsDir)) .filter((f) => f.isFile && f.name.endsWith('.sol')) .filter((f) => !filter || f.name.includes(filter)) -for (const file of input) { - const name = basename(file.name) - const sourceFilePath = join(contractsDir, file.name) - const sourceContent = Deno.readTextFileSync(sourceFilePath) - const sourceHash = await computeSha256(sourceContent) - const inputSources = { - [name]: { - content: sourceContent, - }, - } - - // Create marker files to track if this source has been compiled - const pvmSourceMarkerFile = join(pvmDir, `.${name}.sha256.txt`) - const pvmSourceMarkerHash = readCachedHash(pvmSourceMarkerFile) - const needsPvmCompilation = !solcOnly && - (force || pvmSourceMarkerHash !== sourceHash) - - const evmSourceMarkerFile = join(evmDir, `.${name}.sha256.txt`) - const evmSourceMarkerHash = readCachedHash(evmSourceMarkerFile) - const needsEvmCompilation = force || evmSourceMarkerHash !== sourceHash - - if (needsPvmCompilation) { - const reviveOut = await pvmCompile(file, inputSources) - - for (const contracts of Object.values(reviveOut.contracts)) { - for (const [name, contract] of Object.entries(contracts)) { - if (contract?.evm?.bytecode?.object) { - const pvmFile = join(pvmDir, `${name}.polkavm`) - logger.info(`๐Ÿ“œ Add PVM contract ${name}`) - const bytecode = new Uint8Array( - contract.evm.bytecode.object - .match(/.{1,2}/g)! - .map((byte) => parseInt(byte, 16)), - ) - Deno.writeFileSync(pvmFile, bytecode) - } - } - } - writeCachedHash(pvmSourceMarkerFile, sourceHash) - } else if (!solcOnly) { - logger.debug( - `โญ๏ธ Skipping PVM compilation for ${file.name} (unchanged)`, - ) - } - - if (!needsEvmCompilation) { - logger.debug( - `โญ๏ธ Skipping EVM compilation for ${file.name} (unchanged)`, - ) - continue - } - - const evmOut = JSON.parse( - evmCompile(file, inputSources), - ) as SolcOutput - - if (evmOut.errors) { - for (const error of evmOut.errors) { - if (error.severity === 'warning') { - logger.warn(error.formattedMessage) - } else { - logger.error(error.formattedMessage) - } - } - - if (evmOut.errors.some((err) => err.severity !== 'warning')) { - Deno.exit(1) - } - } - - for (const contracts of Object.values(evmOut.contracts)) { - for (const [name, contract] of Object.entries(contracts)) { - const evmFile = join(evmDir, `${name}.bin`) - const abiFile = join(abiDir, `${name}.ts`) - - // Only write bytecode if it exists and is not empty - if (contract.evm?.bytecode?.object) { - const bytecodeHex = contract.evm.bytecode.object - if (bytecodeHex.length > 0) { - logger.info(`๐Ÿ“œ Add EVM contract ${name}`) - const bytecode = new Uint8Array( - bytecodeHex - .match(/.{1,2}/g)! - .map((byte) => parseInt(byte, 16)), - ) - Deno.writeFileSync(evmFile, bytecode) - } - } +if (contracts.length === 0) { + logger.warn('No contracts found to compile') + Deno.exit(0) +} - logger.info(`๐Ÿ“œ Add ABI ${name}`) - const abi = contract.abi - const abiName = `${name}Abi` - const tsContent = `export const ${abiName} = ${ - JSON.stringify( - abi, - null, - 2, - ) - } as const\n` - Deno.writeTextFileSync(abiFile, tsContent) - generateAbiIndex = true - } +for (const contract of contracts) { + const sourceFilePath = join(contractsDir, contract.name) + const sourceContent = Deno.readTextFileSync(sourceFilePath) - // Mark that we've compiled this source file for EVM - writeCachedHash(evmSourceMarkerFile, sourceHash) + await compile({ + fileName: contract.name, + sourceContent, + rootDir, + compiler: (Deno.env.get('SOLC_BIN') as 'solc') ?? 'solc', + generateAbi: true, + }) - if (needsEvmCompilation || needsPvmCompilation) { - logger.info(`โœ… Compiled ${file.name} successfully`) - } + if (!solcOnly) { + await compile({ + fileName: contract.name, + sourceContent, + rootDir, + compiler: 'resolc', + }) } } -if (generateAbiIndex) { - logger.debug('๐Ÿ“ฆ Generating ABI index file...') - const indexCode = [ - ` - export type Abis = typeof abis - export const abis = { - `.trimEnd(), - ] +await generateAbiIndex(rootDir) - try { - const abiFiles = Array.from(Deno.readDirSync(abiDir)) - .filter((f) => f.isFile && f.name.endsWith('.ts')) - .sort((a, b) => a.name.localeCompare(b.name)) - - for (const abiFile of abiFiles) { - const contractName = basename(abiFile.name, '.ts') - const abiName = `${contractName}Abi` - const importStatement = - `import { ${abiName} } from './abi/${contractName}.ts'` - indexCode.unshift(importStatement) - indexCode.push(`${contractName}: ${abiName},`) - } - } catch (error) { - logger.warn( - `โš ๏ธ Could not read ABI directory (it may not exist yet): ${error}`, - ) - } - - indexCode.push('}') - Deno.writeFileSync( - join(codegenDir, `abis.ts`), - await format(indexCode.join('\n')), - ) -} +logger.info('โœจ All contracts compiled successfully') diff --git a/tools/deploy.ts b/tools/deploy.ts index a20d9f1..fc3b0f9 100644 --- a/tools/deploy.ts +++ b/tools/deploy.ts @@ -1,5 +1,3 @@ -//! Call with deno task deploy [--filter ] - import { deploy } from './lib/index.ts' /** @@ -27,3 +25,9 @@ await deploy({ args: [], // bytecodeType: 'polkavm', // Specify `pvm` for PVM bytecode deployment }) + +await deploy({ + name: 'Fibonacci', + args: [], + // bytecodeType: 'polkavm', // Specify `pvm` for PVM bytecode deployment +}) diff --git a/tools/lib/index.ts b/tools/lib/index.ts index d845997..485e6ad 100644 --- a/tools/lib/index.ts +++ b/tools/lib/index.ts @@ -1,24 +1,14 @@ import { writeFileSync } from 'node:fs' import { createEnv } from '../../utils/index.ts' -import { ContractConstructorArgs, formatEther } from 'viem' +import { ContractConstructorArgs, formatEther, TransactionReceipt } from 'viem' import { Abis } from '../../codegen/abis.ts' import { existsSync, readFileSync } from 'node:fs' import { join } from 'node:path' import { Hex } from 'viem' -import { parseArgs } from '@std/cli/parse-args' - -const flags = parseArgs(Deno.args, { - string: ['filter'], - alias: { - f: 'filter', - }, -}) - -const { filter } = flags const codegenDir = join(import.meta.dirname!, '..', '..', 'codegen') -const rpcUrl = Deno.env.get('RPC_URL') ?? 'http://localhost:8545' +const rpcUrl = Deno.env.get('ETH_RPC_URL') ?? 'http://localhost:8545' /// load private key, default account is 0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac function loadPrivateKey(): Hex { @@ -46,7 +36,7 @@ let firstDeploy = true * This function deploys a contract identified by its name, with the specified * arguments and optional value, and updates the addresses file with the contract's address. * - * @param options.name - The name of the contract to deploy, or an object with name and mappedTo. + * @param options.name - The name of the contract to deploy, or an object with id and name. * @param options.args - The arguments required by the contract's constructor. * @param [options.value] - An optional value (in wei) to send with the deployment. * @param [options.bytecodeType] - The type of bytecode to deploy ('evm' or 'polkavm'). @@ -58,12 +48,12 @@ export async function deploy({ bytecodeType, bytecode, }: { - name: K | { name: K; mappedTo: string } + name: K | { id: K; name: string } args: ContractConstructorArgs value?: bigint bytecodeType?: 'evm' | 'polkavm' bytecode?: Hex -}): Promise { +}): Promise { let contractName: K let mappedTo: string | undefined @@ -71,12 +61,8 @@ export async function deploy({ contractName = name mappedTo = undefined } else { - contractName = name.name - mappedTo = name.mappedTo - } - - if (filter && !contractName.toLowerCase().includes(filter.toLowerCase())) { - return '0x' + contractName = name.id + mappedTo = name.name } if (firstDeploy) { @@ -178,5 +164,5 @@ export const chain = defineChain({ console.log( `โœ… ${id} deployed: ${address} at block ${receipt.blockNumber}\n tx hash: ${receipt.transactionHash}`, ) - return address + return receipt } diff --git a/utils/build.ts b/utils/build.ts new file mode 100644 index 0000000..c1afac4 --- /dev/null +++ b/utils/build.ts @@ -0,0 +1,260 @@ +import { join } from '@std/path' +import { logger } from './logger.ts' + +export type CompileInput = Record + +export interface CompileOutput { + errors?: Array<{ + severity: string + formattedMessage: string + }> + contracts: Record< + string, + Record< + string, + { + evm?: { + bytecode?: { + object: string + } + } + abi: unknown + } + > + > +} + +async function format(code: string) { + const command = new Deno.Command('deno', { + args: ['fmt', '-'], + stdin: 'piped', + stdout: 'piped', + }) + + const child = command.spawn() + const writer = child.stdin.getWriter() + await writer.write(new TextEncoder().encode(code)) + await writer.close() + + const { stdout } = await child.output() + return stdout +} + +const compilerVersions: Record = {} + +async function checkCompilerExists(compiler: 'solc' | 'resolc') { + if (compilerVersions[compiler]) return + + try { + const command = new Deno.Command(compiler, { + args: ['--version'], + stdout: 'piped', + stderr: 'piped', + }) + const output = await command.output() + if (!output.success) { + logger.error( + `Failed to run ${compiler}: ${ + new TextDecoder().decode(output.stderr) + }`, + ) + Deno.exit(1) + } + const versionOutput = new TextDecoder().decode(output.stdout) + // For solc: extract "Version: x.x.x", for resolc: just use trimmed output + const match = versionOutput.match(/Version: ([^\n]+)/) + compilerVersions[compiler] = match + ? match[1].trim() + : versionOutput.trim() + } catch (error) { + logger.error( + `Could not find ${compiler} executable. Please install ${compiler}.`, + ) + logger.error(`Error: ${error}`) + Deno.exit(1) + } +} + +async function compileWithBinary( + compiler: 'solc' | 'resolc', + sources: CompileInput, + rootDir: string, +): Promise { + await checkCompilerExists(compiler) + logger.info(`Compiling with ${compiler} ${compilerVersions[compiler]}`) + + const optimizerSettings = compiler === 'resolc' + ? { enabled: true, mode: 'z' } + : { enabled: true, runs: 200 } + + const input = { + language: 'Solidity', + sources, + settings: { + optimizer: optimizerSettings, + remappings: [ + `@openzeppelin/=${ + join(rootDir, 'node_modules/@openzeppelin/') + }/`, + ], + outputSelection: { + '*': { + '*': ['abi', 'evm.bytecode', 'metadata'], + }, + }, + }, + } + + const command = new Deno.Command(compiler, { + args: ['--standard-json'], + stdin: 'piped', + stdout: 'piped', + stderr: 'piped', + }) + + const child = command.spawn() + const writer = child.stdin.getWriter() + await writer.write(new TextEncoder().encode(JSON.stringify(input))) + await writer.close() + + const { stdout, stderr, success } = await child.output() + const stderrText = new TextDecoder().decode(stderr) + const stdoutText = new TextDecoder().decode(stdout) + + if (stderrText.trim().length > 0) { + logger.error(`${compiler} stderr: ${stderrText}`) + } + + if (!success) { + logger.error(`${compiler} command failed`) + Deno.exit(1) + } + + try { + const result = JSON.parse(stdoutText) as CompileOutput + + // Check for errors in the compilation output + if (result.errors) { + for (const error of result.errors) { + if (error.severity === 'error') { + logger.error(error.formattedMessage) + } else if (error.severity === 'warning') { + logger.warn(error.formattedMessage) + } + } + + if (result.errors.some((err) => err.severity === 'error')) { + throw new Error(`Compilation failed with ${compiler}`) + } + } + + return result + } catch (e) { + logger.error(`Failed to parse ${compiler} output: ${e}`) + logger.error(`Output was: ${stdoutText}`) + Deno.exit(1) + } +} + +export async function compile(options: { + fileName: string + sourceContent: string + rootDir: string + compiler: 'solc' | 'resolc' + generateAbi?: boolean +}) { + const { fileName, sourceContent, rootDir, compiler, generateAbi = false } = + options + + const codegenDir = join(rootDir, 'codegen') + const abiDir = join(codegenDir, 'abi') + const outputDir = compiler.includes('resolc') + ? join(codegenDir, 'pvm') + : join(codegenDir, 'evm') + + const sources = { + [fileName]: { + content: sourceContent, + }, + } + + logger.info(`Compiling ${fileName} with ${compiler}...`) + const output = await compileWithBinary(compiler, sources, rootDir) + + for (const contracts of Object.values(output.contracts)) { + for (const [contractName, contract] of Object.entries(contracts)) { + if (contract?.evm?.bytecode?.object) { + const bytecodeHex = contract.evm.bytecode.object + if (bytecodeHex.length > 0) { + const ext = compiler === 'resolc' ? 'polkavm' : 'bin' + const outputFile = join(outputDir, `${contractName}.${ext}`) + const label = compiler === 'resolc' ? 'PVM' : 'EVM' + logger.info(`๐Ÿ“œ Add ${label} contract ${contractName}`) + const bytecode = new Uint8Array( + bytecodeHex + .match(/.{1,2}/g)! + .map((byte) => parseInt(byte, 16)), + ) + Deno.writeFileSync(outputFile, bytecode) + } + } + + if (generateAbi) { + logger.info(`๐Ÿ“œ Add ABI ${contractName}`) + const abi = contract.abi + const abiName = `${contractName}Abi` + const tsContent = `export const ${abiName} = ${ + JSON.stringify( + abi, + null, + 2, + ) + } as const\n` + Deno.writeTextFileSync( + join(abiDir, `${contractName}.ts`), + tsContent, + ) + } + } + } + + logger.info(`โœ… Compiled ${fileName} with ${compiler} successfully`) +} + +export async function generateAbiIndex(rootDir: string) { + const codegenDir = join(rootDir, 'codegen') + const abiDir = join(codegenDir, 'abi') + + logger.debug('๐Ÿ“ฆ Generating ABI index file...') + const indexCode = [ + ` + export type Abis = typeof abis + export const abis = { + `.trimEnd(), + ] + + try { + const abiFiles = Array.from(Deno.readDirSync(abiDir)) + .filter((f) => f.isFile && f.name.endsWith('.ts')) + .sort((a, b) => a.name.localeCompare(b.name)) + + for (const abiFile of abiFiles) { + const contractName = abiFile.name.replace('.ts', '') + const abiName = `${contractName}Abi` + const importStatement = + `import { ${abiName} } from './abi/${contractName}.ts'` + indexCode.unshift(importStatement) + indexCode.push(`${contractName}: ${abiName},`) + } + } catch (error) { + logger.warn( + `โš ๏ธ Could not generate abi: ${error}`, + ) + } + + indexCode.push('}') + Deno.writeFileSync( + join(codegenDir, `abis.ts`), + await format(indexCode.join('\n')), + ) +} diff --git a/utils/index.ts b/utils/index.ts index 1c892ff..4c9ec0f 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,26 +1,34 @@ import { Abi, - CallParameters, ContractConstructorArgs, createClient, defineChain, formatTransactionRequest, hexToNumber, parseEther, + TransactionRequest, } from 'viem' import { Abis, abis } from '../codegen/abis.ts' import { createWalletClient, Hex, http, publicActions } from 'viem' import { nonceManager, privateKeyToAccount } from 'viem/accounts' -type TracerType = 'callTracer' | 'prestateTracer' | 'opcodeTracer' +type TracerType = + | 'callTracer' + | 'prestateTracer' + | 'opcodeTracer' + | 'syscallTracer' type TracerConfig = { - callTracer: { withLog?: boolean; onlyTopCall?: boolean } + syscallTracer: { + enableReturnData?: boolean + } opcodeTracer: { - disableStack?: boolean + limit?: number enableMemory?: boolean + disableStack?: boolean disableStorage?: boolean enableReturnData?: boolean } + callTracer: { withLog?: boolean; onlyTopCall?: boolean } prestateTracer: { diffMode?: boolean disableCode?: boolean @@ -71,17 +79,7 @@ export async function createEnv({ }), }) const { result } = await resp.json() - const id = String(result).split('/')[0] || 'Unknown' - - if (id == 'Geth') { - return 'Geth' - } else if (chainId == 420_420_421) { - return 'Westend' - } else if (chainId == 420_420_420) { - return 'KitchenSink' - } else { - return 'Unknown' - } + return String(result).split('/')[0] || 'Unknown' } catch (e) { console.error(`Failed to get chain name from ${rpcUrl}`, e) Deno.exit(1) @@ -157,52 +155,60 @@ export async function createEnv({ txHash: Hex, tracer: Tracer, tracerConfig?: TracerConfig[Tracer], - ) { - const params: Record = tracer == null - ? (tracerConfig ?? {}) - : { tracer, tracerConfig } - + ): Promise { return client.request({ - method: 'debug_traceTransaction' as never, - params: [txHash, params], - }) - }, - postDispatchWeight( - transactionHash: Hex, - ) { - return client.request({ - method: 'polkadot_postDispatchWeight' as never, + method: 'debug_traceTransaction' as 'eth_chainId', params: [ - transactionHash, - ], + txHash, + tracer == 'opcodeTracer' + ? tracerConfig + : { tracer, tracerConfig }, + ] as never, }) }, traceBlock( blockNumber: bigint, tracer: Tracer, tracerConfig?: TracerConfig[Tracer], - ) { + ): Promise { return client.request({ - method: 'debug_traceBlockByNumber' as never, + method: 'debug_traceBlockByNumber' as 'eth_chainId', params: [ `0x${blockNumber.toString(16)}`, - { tracer, tracerConfig }, - ], + { + tracer: tracer == 'opcodeTracer' ? null : tracer, + tracerConfig, + }, + ] as never, }) }, traceCall( - args: CallParameters, - tracer: Tracer | null, - tracerConfig?: TracerConfig[Tracer], - ) { - const params: Record = tracer == null - ? (tracerConfig ?? {}) - : { tracer, tracerConfig } + args: TransactionRequest, + tracer: Tracer, + tracerConfig: TracerConfig[Tracer], + blockOrTag: 'latest' | Hex = 'latest', + ): Promise { + return client.request({ + method: 'debug_traceCall' as 'eth_chainId', + params: [ + formatTransactionRequest(args), + blockOrTag, + { + tracer: tracer == 'opcodeTracer' ? null : tracer, + tracerConfig, + }, + ] as never, + }) + }, + postDispatchWeight( + txHash: Hex, + ): Promise<{ ref_time: bigint; proof_size: bigint }> { return client.request({ - method: 'debug_traceCall' as never, - params: [formatTransactionRequest(args), 'latest', params], + // deno-lint-ignore no-explicit-any + method: 'polkadot_postDispatchWeight' as any, + params: [txHash] as never, }) }, })) diff --git a/utils/logger.ts b/utils/logger.ts new file mode 100644 index 0000000..bcc3fd5 --- /dev/null +++ b/utils/logger.ts @@ -0,0 +1,18 @@ +import * as log from '@std/log' + +const LOG_LEVEL = (Deno.env.get('LOG_LEVEL')?.toUpperCase() ?? + 'INFO') as log.LevelName + +log.setup({ + handlers: { + console: new log.ConsoleHandler(LOG_LEVEL), + }, + loggers: { + default: { + level: LOG_LEVEL, + handlers: ['console'], + }, + }, +}) + +export const logger = log.getLogger()