diff --git a/package.json b/package.json index ed54db7..5cc57b4 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@remixproject/plugin-utils": "^0.3.38", "@sveltejs/adapter-netlify": "^4.3.6", "bootstrap": "^5.3.3", - "ethers": "^6.13.4" + "ethers": "^6.13.4", + "solc": "^0.8.28" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbd3b7e..927600d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: ethers: specifier: ^6.13.4 version: 6.13.4 + solc: + specifier: ^0.8.28 + version: 0.8.28 devDependencies: '@sveltejs/adapter-auto': specifier: ^3.0.0 @@ -660,6 +663,13 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + command-exists@1.2.9: + resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -926,6 +936,10 @@ packages: magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -973,6 +987,10 @@ packages: encoding: optional: true + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1027,6 +1045,10 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} @@ -1053,6 +1075,11 @@ packages: resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} engines: {node: '>=18'} + solc@0.8.28: + resolution: {integrity: sha512-AFCiJ+b4RosyyNhnfdVH4ZR1+TxiL91iluPjw0EJslIu4LXGM9NYqi2z5y8TqochC4tcH9QsHfwWhOIC9jPDKA==} + engines: {node: '>=10.0.0'} + hasBin: true + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1091,6 +1118,10 @@ packages: tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -2048,6 +2079,10 @@ snapshots: dependencies: delayed-stream: 1.0.0 + command-exists@1.2.9: {} + + commander@8.3.0: {} + cookie@0.6.0: {} core-util-is@1.0.3: {} @@ -2330,6 +2365,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + memorystream@0.3.1: {} + mime-db@1.52.0: {} mime-types@2.1.35: @@ -2358,6 +2395,8 @@ snapshots: dependencies: whatwg-url: 5.0.0 + os-tmpdir@1.0.2: {} + package-json-from-dist@1.0.1: {} pako@1.0.11: {} @@ -2427,6 +2466,8 @@ snapshots: safe-buffer@5.1.2: {} + semver@5.7.2: {} + set-cookie-parser@2.7.1: {} set-function-length@1.2.2: @@ -2454,6 +2495,18 @@ snapshots: mrmime: 2.0.0 totalist: 3.0.1 + solc@0.8.28: + dependencies: + command-exists: 1.2.9 + commander: 8.3.0 + follow-redirects: 1.15.9 + js-sha3: 0.8.0 + memorystream: 0.3.1 + semver: 5.7.2 + tmp: 0.0.33 + transitivePeerDependencies: + - debug + source-map-js@1.2.1: {} string-width@4.2.3: @@ -2513,6 +2566,10 @@ snapshots: globalyzer: 0.1.0 globrex: 0.1.2 + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + totalist@3.0.1: {} tr46@0.0.3: {} diff --git a/src/app.d.ts b/src/app.d.ts index ad92508..e2c24df 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -13,3 +13,8 @@ declare global { declare interface Window { ethereum?: import('ethers').Eip1193Provider & import('ethers').BrowserProvider; } + +// solc does not have a type definition file. +declare module 'solc' { + export function compile(input: string, opts?: any): any; +} diff --git a/src/lib/api.ts b/src/lib/api.ts index 38a29c4..26180f0 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,6 +1,7 @@ import type { CreateApprovalProcessRequest } from "./models/approval-process"; import type { Credentials } from "./models/auth"; import type { DeployContractRequest, UpdateDeploymentRequest } from "./models/deploy"; +import type { CompilerInput } from "./models/solc"; class ApiClient { credentials: Credentials | null = null; @@ -72,6 +73,16 @@ class ApiClient { return response.json(); } + + async compile(input: CompilerInput) { + const response = await fetch("/compiler", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ input }), + }); + + return response.json(); + } } export const API = new ApiClient(); diff --git a/src/lib/models/solc.ts b/src/lib/models/solc.ts new file mode 100644 index 0000000..3f635e2 --- /dev/null +++ b/src/lib/models/solc.ts @@ -0,0 +1,35 @@ +export type CompilerInput = { + /** + * The language, currently only Solidity is supported. + */ + language: string; + /** + * Name of the file and the content of the file. + * e.g. { 'test.sol': { content: 'contract C { function f() public { L.f(); } }' } } + */ + sources: ContractSources; + /** + * Settings for the compiler. + * e.g. { outputSelection: { '*': { '*': ['*'] } }" + */ + settings: { + outputSelection: Record>; + }; +}; + +export type ContractSources = { + [source: string]: { content: string }; +}; + +export function buildCompilerInput(sources: ContractSources): CompilerInput { + return { + sources, + language: 'Solidity', + settings: { + outputSelection: { + '*': { '*': ['*'] } + } + } + }; +} + diff --git a/src/lib/wizard/components/Configure.svelte b/src/lib/wizard/components/Configure.svelte new file mode 100644 index 0000000..e9eb6b9 --- /dev/null +++ b/src/lib/wizard/components/Configure.svelte @@ -0,0 +1,27 @@ + + +

+ Contract to compile: {getMainContractName(wizardState.sources)} + + +

diff --git a/src/lib/wizard/index.ts b/src/lib/wizard/index.ts index eba0485..dcac451 100644 --- a/src/lib/wizard/index.ts +++ b/src/lib/wizard/index.ts @@ -1,4 +1,20 @@ +import type { ContractSources } from "../models/solc"; +import { wizardState } from "./state.svelte"; + +export interface DefenderDeployMessage { + kind: 'oz-wizard-defender-deploy'; + sources: ContractSources; +} + export const initWizardPlugin = () => { // when users configure a contract, the plugin gets the results. - // listenToContracts(); + listenToContracts(); +} + +function listenToContracts() { + window.addEventListener('message', function (e: MessageEvent) { + if (e.data.kind === 'oz-wizard-defender-deploy') { + wizardState.sources = e.data.sources; + } + }); } diff --git a/src/lib/wizard/state.svelte.ts b/src/lib/wizard/state.svelte.ts new file mode 100644 index 0000000..26de299 --- /dev/null +++ b/src/lib/wizard/state.svelte.ts @@ -0,0 +1,5 @@ +import type { ContractSources } from "../models/solc"; + +export const wizardState = $state<{ sources: ContractSources | undefined }>({ + sources: undefined, +}); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 51c8760..9703914 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -20,20 +20,17 @@ // assumes that when in dev mode and // the ancestor origin is localhost, we are in the wizard if (dev && ancestorOrigin.includes("localhost")) { - parent = 'wizard'; - return; + return parent = 'wizard'; } - if (ancestorOrigin.includes("remix.ethereum")) { - parent = 'remix'; - return initRemixPlugin(); + if (ancestorOrigin.includes("wizard.openzeppelin")) { + return parent = 'wizard'; } - if (ancestorOrigin.includes("wizard.openzeppelin")) { - parent = 'wizard'; - // TODO: init wizard plugin - return; + if (ancestorOrigin.includes("remix.ethereum")) { + return parent = 'remix'; } + }); diff --git a/src/routes/compiler/+server.ts b/src/routes/compiler/+server.ts new file mode 100644 index 0000000..fcb0260 --- /dev/null +++ b/src/routes/compiler/+server.ts @@ -0,0 +1,19 @@ +import type { CompilerInput } from "$lib/models/solc"; +import { json } from '@sveltejs/kit'; +import { SolidityCompiler } from "./compiler"; +import { attempt } from "$lib/utils/attempt"; + + +export async function POST({ request }: { request: Request }) { + const { input }: { input: CompilerInput } = await request.json(); + + const compiler = new SolidityCompiler(); + + const [output, error] = await attempt(() => JSON.parse(compiler.compile(input))); + + if (error) { + return json({ success: false, error: error.msg }); + } + + return json({ success: true, data: { output } }); +} diff --git a/src/routes/compiler/compiler.ts b/src/routes/compiler/compiler.ts new file mode 100644 index 0000000..5582bf6 --- /dev/null +++ b/src/routes/compiler/compiler.ts @@ -0,0 +1,20 @@ +import solc from 'solc'; + +import type { ContractSources } from "$lib/models/solc"; +import type { CompilerInput } from "$lib/models/solc"; + +export class SolidityCompiler { + getContent(path: string, contents: ContractSources) { + if (contents[path]) { + return contents[path]; + } + return { error: 'File not found' }; + } + + compile(input: CompilerInput, contents?: ContractSources) { + const shouldFindImports = contents !== undefined; + const findImports = (path: string) => shouldFindImports ? this.getContent(path, contents) : undefined; + const output = solc.compile(JSON.stringify(input), shouldFindImports ? { import: findImports } : undefined); + return output; + } +} diff --git a/src/routes/remix.svelte b/src/routes/remix.svelte index a36362f..6e704fc 100644 --- a/src/routes/remix.svelte +++ b/src/routes/remix.svelte @@ -1,4 +1,6 @@

diff --git a/src/routes/wizard.svelte b/src/routes/wizard.svelte index 105abb4..4546bc9 100644 --- a/src/routes/wizard.svelte +++ b/src/routes/wizard.svelte @@ -1,4 +1,10 @@ -

Defender Deploy in Wizard

\ No newline at end of file + +