From b829f16543c3b96b018ca8f6e92f3f51c222f939 Mon Sep 17 00:00:00 2001 From: Marcos Carlomagno Date: Mon, 16 Dec 2024 11:02:15 -0300 Subject: [PATCH] Add support to defender deploy (#416) Co-authored-by: Eric Lau Co-authored-by: makiopen --- packages/core/get-imports.d.ts | 1 + packages/core/get-imports.js | 1 + packages/core/src/get-imports.test.ts | 86 ++++++++++ packages/core/src/get-imports.ts | 45 ++++++ packages/core/src/scripts/prepare.ts | 4 +- packages/ui/src/App.svelte | 180 +++++++++++++++++---- packages/ui/src/DefenderDeployModal.svelte | 47 ++++++ packages/ui/src/Dropdown.svelte | 2 +- packages/ui/src/HelpTooltip.svelte | 2 +- packages/ui/src/OverflowMenu.svelte | 2 +- packages/ui/src/Wiz.svelte | 2 +- packages/ui/src/cairo/App.svelte | 29 ++-- packages/ui/src/embed.ts | 4 +- packages/ui/src/icons/ArrowsLeft.svelte | 5 + packages/ui/src/icons/ArrowsRight.svelte | 5 + packages/ui/src/post-config.ts | 2 +- packages/ui/src/post-message.ts | 23 ++- packages/ui/src/standalone.css | 4 - packages/ui/src/styles/global.css | 4 + packages/ui/src/styles/tippy.css | 3 +- 20 files changed, 391 insertions(+), 60 deletions(-) create mode 100644 packages/core/get-imports.d.ts create mode 100644 packages/core/get-imports.js create mode 100644 packages/core/src/get-imports.test.ts create mode 100644 packages/core/src/get-imports.ts create mode 100644 packages/ui/src/DefenderDeployModal.svelte create mode 100644 packages/ui/src/icons/ArrowsLeft.svelte create mode 100644 packages/ui/src/icons/ArrowsRight.svelte diff --git a/packages/core/get-imports.d.ts b/packages/core/get-imports.d.ts new file mode 100644 index 00000000..4c0a4417 --- /dev/null +++ b/packages/core/get-imports.d.ts @@ -0,0 +1 @@ +export * from './src/get-imports'; diff --git a/packages/core/get-imports.js b/packages/core/get-imports.js new file mode 100644 index 00000000..c93672cc --- /dev/null +++ b/packages/core/get-imports.js @@ -0,0 +1 @@ +module.exports = require('./dist/get-imports'); \ No newline at end of file diff --git a/packages/core/src/get-imports.test.ts b/packages/core/src/get-imports.test.ts new file mode 100644 index 00000000..5678fdc5 --- /dev/null +++ b/packages/core/src/get-imports.test.ts @@ -0,0 +1,86 @@ +import test from 'ava'; + +import { getImports } from './get-imports'; +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { generateSources } from './generate/sources'; +import { buildGeneric } from './build-generic'; + +test('erc20 basic', t => { + const c = buildERC20({ name: 'MyToken', symbol: 'MTK', permit: false }); + const sources = getImports(c); + const files = Object.keys(sources).sort(); + + t.deepEqual(files, [ + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/token/ERC20/ERC20.sol', + '@openzeppelin/contracts/token/ERC20/IERC20.sol', + '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol', + '@openzeppelin/contracts/utils/Context.sol', + ]); +}); + +test('erc721 auto increment', t => { + const c = buildERC721({ name: 'MyToken', symbol: 'MTK', mintable: true, incremental: true }); + const sources = getImports(c); + const files = Object.keys(sources).sort(); + + t.deepEqual(files, [ + '@openzeppelin/contracts/access/Ownable.sol', + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/token/ERC721/ERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', + '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', + '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', + '@openzeppelin/contracts/utils/Context.sol', + '@openzeppelin/contracts/utils/Panic.sol', + '@openzeppelin/contracts/utils/Strings.sol', + '@openzeppelin/contracts/utils/introspection/ERC165.sol', + '@openzeppelin/contracts/utils/introspection/IERC165.sol', + '@openzeppelin/contracts/utils/math/Math.sol', + '@openzeppelin/contracts/utils/math/SafeCast.sol', + '@openzeppelin/contracts/utils/math/SignedMath.sol', + ]); +}); + +test('erc721 auto increment uups', t => { + const c = buildERC721({ name: 'MyToken', symbol: 'MTK', mintable: true, incremental: true, upgradeable: 'uups' }); + const sources = getImports(c); + const files = Object.keys(sources).sort(); + + t.deepEqual(files, [ + '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', + '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol', + '@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol', + '@openzeppelin/contracts/interfaces/IERC1967.sol', + '@openzeppelin/contracts/interfaces/draft-IERC1822.sol', + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol', + '@openzeppelin/contracts/proxy/beacon/IBeacon.sol', + '@openzeppelin/contracts/token/ERC721/IERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', + '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', + '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', + '@openzeppelin/contracts/utils/Address.sol', + '@openzeppelin/contracts/utils/Errors.sol', + '@openzeppelin/contracts/utils/Panic.sol', + '@openzeppelin/contracts/utils/StorageSlot.sol', + '@openzeppelin/contracts/utils/Strings.sol', + '@openzeppelin/contracts/utils/introspection/IERC165.sol', + '@openzeppelin/contracts/utils/math/Math.sol', + '@openzeppelin/contracts/utils/math/SafeCast.sol', + '@openzeppelin/contracts/utils/math/SignedMath.sol', + ]); +}); + +test('can get imports for all combinations', t => { + for (const { options } of generateSources('all')) { + const c = buildGeneric(options); + getImports(c); + } + t.pass(); +}); \ No newline at end of file diff --git a/packages/core/src/get-imports.ts b/packages/core/src/get-imports.ts new file mode 100644 index 00000000..9c51ab86 --- /dev/null +++ b/packages/core/src/get-imports.ts @@ -0,0 +1,45 @@ +import type { Contract } from './contract'; +import { reachable } from './utils/transitive-closure'; + +import contracts from '../openzeppelin-contracts'; +import { withHelpers } from './options'; + +export interface SolcInputSources { + [source: string]: { + content: string; + }; +} + +/** +* Gets the source code for all imports of a contract, including all transitive dependencies, +* in a format compatible with the Solidity compiler input's `sources` field. +* +* Does not include the contract itself (use `printContract` for that if needed). + * + * @param c The contract to get imports for. + * @returns A record of import paths to `content` that contains the source code for each contract. + */ +export function getImports(c: Contract): SolcInputSources { + const { transformImport } = withHelpers(c); + + const result: SolcInputSources = {}; + + const fileName = c.name + '.sol'; + + const dependencies = { + [fileName]: c.imports.map(i => transformImport(i).path), + ...contracts.dependencies, + }; + + const allImports = reachable(dependencies, fileName); + + for (const importPath of allImports) { + const source = contracts.sources[importPath]; + if (source === undefined) { + throw new Error(`Source for ${importPath} not found`); + } + result[importPath] = { content: source }; + } + + return result; +} \ No newline at end of file diff --git a/packages/core/src/scripts/prepare.ts b/packages/core/src/scripts/prepare.ts index 19c2d1c2..a499b200 100644 --- a/packages/core/src/scripts/prepare.ts +++ b/packages/core/src/scripts/prepare.ts @@ -26,7 +26,7 @@ async function main() { ); for (const [sourceFile, { ast }] of Object.entries(buildInfo.output.sources)) { - if (sourceFile.startsWith('@openzeppelin/contracts')) { + if (sourceFile.startsWith('@openzeppelin/contracts') || sourceFile.startsWith('@openzeppelin/community-contracts')) { const sourceDependencies = (dependencies[sourceFile] ??= new Set()); for (const imp of findAll('ImportDirective', ast)) { sourceDependencies.add(imp.absolutePath); @@ -35,7 +35,7 @@ async function main() { } for (const [sourceFile, { content }] of Object.entries(buildInfo.input.sources)) { - if (sourceFile.startsWith('@openzeppelin/contracts')) { + if (sourceFile.startsWith('@openzeppelin/contracts') || sourceFile.startsWith('@openzeppelin/community-contracts')) { sources[sourceFile] = content; } } diff --git a/packages/ui/src/App.svelte b/packages/ui/src/App.svelte index ea58a6a5..15705bcd 100644 --- a/packages/ui/src/App.svelte +++ b/packages/ui/src/App.svelte @@ -16,20 +16,24 @@ import DownloadIcon from './icons/DownloadIcon.svelte'; import ZipIcon from './icons/ZipIcon.svelte'; import FileIcon from './icons/FileIcon.svelte'; - import OzIcon from './icons/OzIcon.svelte'; + import ArrowsLeft from './icons/ArrowsLeft.svelte'; + import ArrowsRight from './icons/ArrowsRight.svelte'; import Dropdown from './Dropdown.svelte'; import OverflowMenu from './OverflowMenu.svelte'; import Tooltip from './Tooltip.svelte'; import Wiz from './Wiz.svelte'; + import DefenderDeployModal from './DefenderDeployModal.svelte'; import type { KindedOptions, Kind, Contract, OptionsErrorMessages } from '@openzeppelin/wizard'; import { ContractBuilder, buildGeneric, printContract, sanitizeKind, OptionsError } from '@openzeppelin/wizard'; + import { getImports } from '@openzeppelin/wizard/get-imports'; import { postConfig } from './post-config'; import { remixURL } from './remix'; import { saveAs } from 'file-saver'; import { injectHyperlinks } from './utils/inject-hyperlinks'; import { InitialOptions } from './initial-options'; + import { postMessageToIframe } from './post-message'; const dispatch = createEventDispatcher(); @@ -44,6 +48,8 @@ export let initialOpts: InitialOptions = {}; let initialValuesSet = false; + let showDeployModal = false; + let allOpts: { [k in Kind]?: Required } = {}; let errors: { [k in Kind]?: OptionsErrorMessages } = {}; @@ -87,6 +93,17 @@ $: code = printContract(contract); $: highlightedCode = injectHyperlinks(hljs.highlight('solidity', code).value); + $: if (showDeployModal) postMessageToIframe('defender-deploy', { + kind: 'oz-wizard-defender-deploy', + sources: getSolcSources(contract) + });; + + const getSolcSources = (contract: Contract) => { + const sources = getImports(contract); + sources[contract.name] = { content: code }; + return sources; + } + const language = 'solidity'; let copied = false; @@ -174,7 +191,7 @@ } -
+
@@ -205,20 +222,11 @@
- - - - - @@ -235,13 +243,14 @@ class="action-button" class:disabled={opts?.upgradeable === "transparent"} on:click={remixHandler} + title="Open in Remix" > - Open in Remix +
Transparent upgradeable contracts are not supported on Remix. - Try using Remix with UUPS upgradability or use Hardhat or Truffle with + Try using Remix with UUPS upgradability or use Hardhat or Foundry with OpenZeppelin Upgrades.
@@ -288,8 +297,8 @@
-
-
+
+
@@ -313,17 +322,131 @@
-
-
{@html highlightedCode}
+
+
+ +
+
+ +
+
+        {@html highlightedCode}
+      
+