UNPKG

superfuse-wizard

Version:

Interactive smart contract generator based on Superchain 's interoperability standard.

204 lines (166 loc) 5.82 kB
// import 'array.prototype.flatmap/auto'; import type { ReferencedContract, TestContract, ContractFunction, FunctionArgument, Value, NatspecTag, ImportContract } from './contract'; import type { Options, Helpers }from './options'; import { withHelpers } from './options'; import type { Lines} from '../utils/format-lines'; import { formatLines, spaceBetween } from '../utils/format-lines'; import { mapValues } from '../utils/map-values'; import SOLIDITY_VERSION from './solidity-version.json' assert { type: "json" }; export function printTestContract(contract: TestContract, opts?: Options): string { const helpers = withHelpers(opts); const fns = mapValues( sortedFunctions(contract), fns => fns.map(fn => printFunction(fn)), ); const hasOverrides = fns.override.some(l => l.length > 0); return formatLines( ...spaceBetween( [ `// SPDX-License-Identifier: ${contract.license}`, `pragma solidity ^${SOLIDITY_VERSION};`, ], // contract.dependencies.map(p => `import {${p.name}} from "${helpers.transformImport(p).path}";`), printImports(contract.imports, helpers), contract.outsideCode, [ ...printNatspecTags(contract.natspecTags), [`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '), contract.usings.map(p => `using ${p.library.name} for ${p.usingFor} ;`), spaceBetween( contract.variables, // printConstructor(contract, helpers), ...fns.code, ...fns.modifiers, hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [], ...fns.override, ), `}`, ], ), ); } function printInheritance(contract: TestContract, { transformName }: Helpers): [] | [string] { if (contract.parents.length > 0) { return ['is ' + contract.parents.map(p => transformName(p.contract)).join(', ')]; } else { return []; } } function printImports(imports: ImportContract[], helpers: Helpers): string[] { // Sort imports by name imports.sort((a, b) => { if (a.name < b.name) return -1; if (a.name > b.name) return 1; return 0; }); const lines: string[] = []; imports.map(p => { const importContract = helpers.transformImport(p); if (importContract.name === ''){ lines.push(`import "${importContract.path}";`); } else{ lines.push(`import {${importContract.name}} from "${importContract.path}";`); } }); return lines; } type SortedFunctions = Record<'code' | 'modifiers' | 'override', ContractFunction[]>; // Functions with code first, then those with modifiers, then the rest function sortedFunctions(contract: TestContract): SortedFunctions { const fns: SortedFunctions = { code: [], modifiers: [], override: [] }; for (const fn of contract.functions) { if (fn.code.length > 0) { fns.code.push(fn); } else if (fn.modifiers.length > 0) { fns.modifiers.push(fn); } else { fns.override.push(fn); } } return fns; } export function printValue(value: Value): string { if (typeof value === 'object') { if ('lit' in value) { return value.lit; } else if ('note' in value) { return `${printValue(value.value)} /* ${value.note} */`; } else { throw Error('Unknown value type'); } } else if (typeof value === 'number') { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { throw new Error(`Number not representable (${value})`); } } else { return JSON.stringify(value); } } function printFunction(fn: ContractFunction): Lines[] { if (fn.override.size <= 1 && fn.modifiers.length === 0 && fn.code.length === 0 && !fn.final) { return [] } const modifiers: string[] = [fn.kind, ...fn.modifiers]; if (fn.mutability !== 'nonpayable') { modifiers.splice(1, 0, fn.mutability); } if (fn.override.size === 1) { modifiers.push(`override`); } else if (fn.override.size > 1) { modifiers.push(`override(${[...fn.override].join(', ')})`); } if (fn.returns?.length) { modifiers.push(`returns (${fn.returns.join(', ')})`); } const code = [...fn.code]; if (fn.override.size > 0 && !fn.final) { const superCall = `super.${fn.name}(${fn.args.map(a => a.name).join(', ')});`; code.push(fn.returns?.length ? 'return ' + superCall : superCall); } if (modifiers.length + fn.code.length > 1) { return printFunction2( fn.comments, 'function ' + fn.name, fn.args.map(a => printArgument(a)), modifiers, code, ); } else { return []; } } // generic for functions and constructors // kindedName = 'function foo' or 'constructor' function printFunction2(comments: string[], kindedName: string, args: string[], modifiers: string[], code: Lines[]): Lines[] { const fn: Lines[] = [...comments]; const headingLength = [kindedName, ...args, ...modifiers] .map(s => s.length) .reduce((a, b) => a + b); const braces = code.length > 0 ? '{' : '{}'; if (headingLength <= 72) { fn.push([`${kindedName}(${args.join(', ')})`, ...modifiers, braces].join(' ')); } else { fn.push(`${kindedName}(${args.join(', ')})`, modifiers, braces); } if (code.length > 0) { fn.push(code, '}'); } return fn; } function printArgument(arg: FunctionArgument): string { let type: string | ReferencedContract; if (typeof arg.type === 'string') { if (/^[A-Z]/.test(arg.type)) { `Type ${arg.type} is not a primitive type. Define it as a ContractReference`; } type = arg.type; } else { type = arg.type;; } return [type, arg.name].join(' '); } function printNatspecTags(tags: NatspecTag[]): string[] { return tags.map(({ key, value }) => `/// ${key} ${value}`); }