diff --git a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json index e1dd413634..8a09760ae9 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json +++ b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json @@ -29,6 +29,8 @@ "filePanel.tssoltestghaction": "Mocha Chai Test Workflow", "filePanel.workspace.slitherghaction": "Adds a preset yml file to run slither analysis on github actions CI", "filePanel.slitherghaction": "Slither Workflow", + "filePanel.workspace.helperscripts": "Adds convenient scripts to the 'scripts' directory", + "filePanel.helperscripts": "Web3 Scripts", "filePanel.newFile": "New File", "filePanel.newFolder": "New Folder", "filePanel.rename": "Rename", diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 0e23965cdb..f83ace346b 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -13,6 +13,8 @@ import { ROOT_PATH, slitherYml, solTestYml, tsSolTestYml } from '../utils/consta import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB' import { getUncommittedFiles } from '../utils/gitStatusFilter' import { AppModal, ModalTypes } from '@remix-ui/app' +import { contractDeployerScripts } from '../scripts/contract-deployer' +import { etherscanScripts } from '../scripts/etherscan' declare global { interface Window { remixFileSystemCallback: IndexedDBStorage; } @@ -677,6 +679,12 @@ export const createSlitherGithubAction = async () => { plugin.call('fileManager', 'open', path) } +export const createHelperScripts = async () => { + await contractDeployerScripts(plugin) + await etherscanScripts(plugin) + plugin.call('notification', 'toast', 'scripts added in the "scripts" folder') +} + export const checkoutRemoteBranch = async (branch: string, remote: string) => { const localChanges = await hasLocalChanges() diff --git a/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx b/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx index 9941dee29a..b2400613b6 100644 --- a/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx +++ b/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx @@ -15,6 +15,7 @@ export interface HamburgerMenuProps { addGithubAction: () => void, addTsSolTestGithubAction: () => void, addSlitherGithubAction: () => void, + addHelperScripts: () => void, showIconsMenu: boolean, hideWorkspaceOptions: boolean, hideLocalhostOptions: boolean @@ -70,6 +71,11 @@ export function HamburgerMenu (props: HamburgerMenuProps) { props.addSlitherGithubAction() props.hideIconsMenu(!showIconsMenu) }}> + + { + props.addHelperScripts() + props.hideIconsMenu(!showIconsMenu) + }}> ) } \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts index 44783948be..9af5f8e19d 100644 --- a/libs/remix-ui/workspace/src/lib/contexts/index.ts +++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts @@ -45,6 +45,7 @@ export const FileSystemContext = createContext<{ dispatchCreateSolidityGithubAction: () => Promise, dispatchCreateTsSolGithubAction: () => Promise, dispatchCreateSlitherGithubAction: () => Promise + dispatchCreateHelperScripts: () => Promise }>(null) \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index ae038142a7..8fb509e37a 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -8,7 +8,7 @@ import { browserReducer, browserInitialState } from '../reducers/workspace' import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, uploadFolder, handleDownloadWorkspace, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder, - showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction + showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction, createHelperScripts } from '../actions' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -183,6 +183,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => { await createSlitherGithubAction() } + const dispatchCreateHelperScripts = async () => { + await createHelperScripts() + } + useEffect(() => { dispatchInitWorkspace() }, []) @@ -299,7 +303,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => { dispatchCheckoutRemoteBranch, dispatchCreateSolidityGithubAction, dispatchCreateTsSolGithubAction, - dispatchCreateSlitherGithubAction + dispatchCreateSlitherGithubAction, + dispatchCreateHelperScripts } return ( diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index de26194620..615180fd93 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -209,6 +209,10 @@ export function Workspace () { global.dispatchCreateSlitherGithubAction() } + const addHelperScripts = () => { + global.dispatchCreateHelperScripts() + } + const downloadWorkspaces = async () => { try { await global.dispatchHandleDownloadFiles() @@ -726,6 +730,7 @@ export function Workspace () { hideIconsMenu={hideIconsMenu} addGithubAction={addGithubAction} addSlitherGithubAction={addSlitherGithubAction} + addHelperScripts={addHelperScripts} addTsSolTestGithubAction={addTsSolTestGithubAction} showIconsMenu={showIconsMenu} hideWorkspaceOptions={ currentWorkspace === LOCALHOST } diff --git a/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/create2-factory-deploy.ts b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/create2-factory-deploy.ts new file mode 100644 index 0000000000..35a7c1b4e7 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/create2-factory-deploy.ts @@ -0,0 +1,258 @@ +import { ethers } from 'ethers' + +// https://etherscan.io/address/0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2#code +export const CREATE2_DEPLOYER_ADDRESS = + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"; + +/** + * Deploy the given contract + * @param {string} address of the factory contract + * @param {string} contractName name of the contract to deploy + * @param {Array} args list of constructor' parameters + * @param {number} salt (using during address generation) + * @param {number} accountIndex account index from the exposed account + * @return {string} deployed contract address + */ +export const deploy = async (contractName: string, args: Array, salt: string, accountIndex?: number): Promise => { + + console.log(`deploying ${contractName}`) + + const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex) + + const factory = new ethers.Contract(CREATE2_DEPLOYER_ADDRESS, contractDeployerAbi, signer); + + const contract = await ethers.getContractFactory(contractName) + const initCode = contract.getDeployTransaction(args) + + const codeHash = ethers.utils.keccak256(initCode.data) + const saltBytes = ethers.utils.id(salt) + const deployedAddress = await factory.computeAddress(saltBytes, codeHash) + try { + const tx = await factory.deploy(0, saltBytes, initCode.data) + await tx.wait() + return deployedAddress + } catch (e) { + console.error(e.message) + console.error(`Please check a contract isn't already deployed at that address`) + throw e + } +} + +export const contractDeployerAbi = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "codeHash", + "type": "bytes32" + } + ], + "name": "computeAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "codeHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "deployer", + "type": "address" + } + ], + "name": "computeAddressWithDeployer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "code", + "type": "bytes" + } + ], + "name": "deploy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "deployERC1820Implementer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "payoutAddress", + "type": "address" + } + ], + "name": "killCreate2Deployer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/index.ts b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/index.ts new file mode 100644 index 0000000000..bcf3e80186 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/index.ts @@ -0,0 +1,6 @@ +export const contractDeployerScripts = async (plugin) => { + await plugin.call('fileManager', 'writeFile', + 'scripts/contract-deployer/create2-factory-deploy.ts' , + // @ts-ignore + (await import('!!raw-loader!./create2-factory-deploy.ts')).default) +} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/scripts/etherscan/index.ts b/libs/remix-ui/workspace/src/lib/scripts/etherscan/index.ts new file mode 100644 index 0000000000..34000b7ca2 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/scripts/etherscan/index.ts @@ -0,0 +1,11 @@ +export const etherscanScripts = async (plugin) => { + await plugin.call('fileManager', 'writeFile', + 'scripts/etherscan/verifyScript.ts' , + // @ts-ignore + (await import('!!raw-loader!./verifyScript.ts')).default) + + await plugin.call('fileManager', 'writeFile', + 'scripts/etherscan/receiptGuidScript.ts' , + // @ts-ignore + (await import('!!raw-loader!./receiptGuidScript.ts')).default) +} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/scripts/etherscan/receiptGuidScript.ts b/libs/remix-ui/workspace/src/lib/scripts/etherscan/receiptGuidScript.ts new file mode 100644 index 0000000000..a09a9fb780 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/scripts/etherscan/receiptGuidScript.ts @@ -0,0 +1,8 @@ +/** + * @param {string} apikey - etherscan api key. + * @param {string} guid - receipt id. + * @returns {{ status, message, succeed }} receiptStatus + */ +export const receiptStatus = async (apikey: string, guid: string) => { + return await remix.call('etherscan' as any, 'receiptStatus', guid, apikey) +} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/scripts/etherscan/verifyScript.ts b/libs/remix-ui/workspace/src/lib/scripts/etherscan/verifyScript.ts new file mode 100644 index 0000000000..3531595239 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/scripts/etherscan/verifyScript.ts @@ -0,0 +1,13 @@ +/** + * @param {string} apikey - etherscan api key. + * @param {string} contractAddress - Address of the contract to verify. + * @param {string} contractArguments - Parameters used in the contract constructor during the initial deployment. It should be the hex encoded value. + * @param {string} contractName - Name of the contract + * @param {string} contractFile - File where the contract is located + * @returns {{ guid, status, message, succeed }} verification result + */ +export const verify = async (apikey: string, contractAddress: string, contractArguments: string, contractName: string, contractFile: string) => { + const compilationResultParam = await remix.call('compilerArtefacts' as any, 'getCompilerAbstract', contractFile) + console.log('verifying.. ' + contractName) + return await remix.call('etherscan' as any, 'verify', apikey, contractAddress, contractArguments, contractName, compilationResultParam) +} \ No newline at end of file