Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate solidity scan in udapp #4735

Merged
merged 23 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/remix-ide/src/app/tabs/locales/en/udapp.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@
"udapp.pinnedAt": "Pinned at",
"udapp.filePath": "File path",

"udapp.solScan.iconTooltip": "Click to scan this contract for vulnerabilities using third-party SolidityScan [BETA]",
"udapp.solScan.modalTitle": "Permission to share code",
"udapp.solScan.modalMessage": "To scan the contract for vulnerabilities & possible risks, smart contract code will be shared to third-party SolidityScan (https://solidityscan.com/).\n\n Would you like to continue?",
"udapp.solScan.modalOkLabel": "Continue",
"udapp.solScan.modalCancelLabel": "Cancel",
"udapp.solScan.errModalTitle": "Scan error",
"udapp.solScan.successModalTitle": "Scan result",


"udapp._comment_recorderCardUI.tsx": "libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx",
"udapp.transactionsRecorded": "Transactions recorded",
"udapp.transactionsCountTooltip": "The number of recorded transactions",
Expand Down
1 change: 1 addition & 0 deletions apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
--text-background: #222336;
--text-bg-mark: #8388b2;
--custom-select: #35384c;
--runtab: #8A93B0;
--body-bg: #222336;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
Expand Down
3 changes: 2 additions & 1 deletion libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
}

const modal = (modalData: AppModal) => {
const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData
const { id, title, message, validationFn, okLabel, okFn, cancelLabel, cancelFn, modalType, modalParentClass, defaultValue, hideFn, data } = modalData
return new Promise((resolve, reject) => {
dispatch({
type: modalActionTypes.setModal,
Expand All @@ -37,6 +37,7 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
cancelLabel,
cancelFn,
modalType: modalType || ModalTypes.default,
modalParentClass,
defaultValue: defaultValue,
hideFn,
resolve,
Expand Down
1 change: 1 addition & 0 deletions libs/remix-ui/app/src/lib/remix-app/interface/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface AppModal {
cancelLabel?: string | JSX.Element
cancelFn?: () => void,
modalType?: ModalTypes,
modalParentClass?: string
defaultValue?: string
hideFn?: () => void,
resolve?: (value?:any) => void,
Expand Down
1 change: 1 addition & 0 deletions libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
cancelLabel: action.payload.cancelLabel,
cancelFn: action.payload.cancelFn,
modalType: action.payload.modalType,
modalParentClass: action.payload.modalParentClass,
defaultValue: action.payload.defaultValue,
hideFn: action.payload.hideFn,
resolve: action.payload.resolve,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
style={{ display: props.hide ? 'none' : 'block' }}
role="dialog"
>
<div className="modal-dialog" role="document">
<div className={'modal-dialog ' + (props.modalParentClass ? props.modalParentClass : '')} role="document">
<div
ref={modal}
tabIndex={-1}
Expand Down
1 change: 1 addition & 0 deletions libs/remix-ui/modal-dialog/src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ModalDialogProps {
cancelLabel?: string | JSX.Element,
cancelFn?: () => void,
modalClass?: string,
modalParentClass?: string
showCancelIcon?: boolean,
hide?: boolean,
handleHide: (hideState?: boolean) => void,
Expand Down
46 changes: 46 additions & 0 deletions libs/remix-ui/run-tab/src/lib/components/solScanTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import parse from 'html-react-parser';

interface SolScanTableProps {
scanDetails: Record<string, any>[],
fileName: string
}

export function SolScanTable(props: SolScanTableProps) {
const { scanDetails, fileName } = props

return (
<>
<p>Scanning successful! <b>{scanDetails.length} warnings </b> found for file: <b>{fileName}</b></p>
<p>See the warning details below. For more details, <a href="https://solidityscan.com/signup" target='blank'>Go to SolidityScan</a></p>
<table className="table table-bordered table-hover">
<thead>
<tr>
<td scope="col" style={{ wordBreak: "keep-all" }}>NAME</td>
<td scope="col" style={{ wordBreak: "keep-all" }}>SEVERITY</td>
<td scope="col" style={{ wordBreak: "keep-all" }}>CONFIDENCE</td>
<td scope="col" style={{ wordBreak: "keep-all" }}>DESCRIPTION</td>
<td scope="col" style={{ wordBreak: "keep-all" }}>REMEDIATION</td>
</tr>
</thead>
<tbody>
{
Array.from(scanDetails, (template) => {
return (
<tr key={template.template_details.issue_id}>
<td scope="col">{template.template_details.issue_name}</td>
<td scope="col">{template.template_details.issue_severity}</td>
<td scope="col">{template.template_details.issue_confidence}</td>
<td scope="col">{parse(template.template_details.static_issue_description)}</td>
<td scope="col">{template.template_details.issue_remediation ? parse(template.template_details.issue_remediation) : 'Not Available' }</td>
</tr>
)
})
}

</tbody>
</table>
</>
)
}
127 changes: 115 additions & 12 deletions libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { FuncABI } from '@remix-project/core-plugin'
import { CopyToClipboard } from '@remix-ui/clipboard'
import * as remixLib from '@remix-project/remix-lib'
import * as ethJSUtil from '@ethereumjs/util'
import axios from 'axios'
import { AppModal } from '@remix-ui/app'
import { ContractGUI } from './contractGUI'
import { SolScanTable } from './solScanTable'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { BN } from 'bn.js'
import { CustomTooltip, is0XPrefixed, isHexadecimal, isNumeric, shortenAddress } from '@remix-ui/helper'
Expand Down Expand Up @@ -217,6 +220,100 @@ export function UniversalDappUI(props: UdappProps) {
setCalldataValue(value)
}

const handleScanContinue = async () => {
await props.plugin.call('notification', 'toast', 'Processing data to scan...')
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'initiateScan'])
const workspace = await props.plugin.call('filePanel', 'getCurrentWorkspace')
const fileName = props.instance.filePath || `${workspace.name}/${props.instance.contractData.contract.file}`
const filePath = `.workspaces/${fileName}`
const file = await props.plugin.call('fileManager', 'readFile', filePath)

const urlResponse = await axios.post(`https://solidityscan.remixproject.org/uploadFile`, { file, fileName })

if (urlResponse.data.status === 'success') {
const ws = new WebSocket('wss://solidityscan.remixproject.org/solidityscan')

ws.addEventListener('error', console.error);

ws.addEventListener('open', async (event) => {
await props.plugin.call('notification', 'toast', 'Initiating scan...')
})

ws.addEventListener('message', async (event) => {
const data = JSON.parse(event.data)
if (data.type === "auth_token_register" && data.payload.message === "Auth token registered.") {
// Message on Bearer token successful registration
const reqToInitScan = {
"action": "message",
"payload": {
"type": "private_project_scan_initiate",
"body": {
"file_urls": [
urlResponse.data.result.url
],
"project_name": "RemixProject",
"project_type": "new"
}
}
}
ws.send(JSON.stringify(reqToInitScan))
} else if (data.type === "scan_status" && data.payload.scan_status === "download_failed") {
// Message on failed scan
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'scanFailed'])
const modal: AppModal = {
id: 'SolidityScanError',
title: <FormattedMessage id="udapp.solScan.errModalTitle" />,
message: data.payload.scan_status_err_message,
okLabel: 'Close'
}
await props.plugin.call('notification', 'modal', modal)
} else if (data.type === "scan_status" && data.payload.scan_status === "scan_done") {
// Message on successful scan
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'scanSuccess'])
const url = data.payload.scan_details.link

const { data: scanData } = await axios.post('https://solidityscan.remixproject.org/downloadResult', { url })
const scanDetails: Record<string, any>[] = scanData.scan_report.multi_file_scan_details

let modal: AppModal

if (scanDetails && scanDetails.length) {
modal = {
id: 'SolidityScanSuccess',
title: <FormattedMessage id="udapp.solScan.successModalTitle" />,
message: <SolScanTable scanDetails={scanDetails} fileName={fileName}/>,
okLabel: 'Close',
modalParentClass: 'modal-xl'
}
} else {
modal = {
id: 'SolidityScanError',
title: <FormattedMessage id="udapp.solScan.errModalTitle" />,
message: "Some error occurred! Please try again",
okLabel: 'Close'
}
}
await props.plugin.call('notification', 'modal', modal)
}

})
}
}

const askPermissionToScan = async () => {
_paq.push(['trackEvent', 'udapp', 'solidityScan', 'askPermissionToScan'])
const modal: AppModal = {
id: 'SolidityScanPermissionHandler',
title: <FormattedMessage id="udapp.solScan.modalTitle" />,
message: <FormattedMessage id="udapp.solScan.modalMessage" />,
okLabel: <FormattedMessage id="udapp.solScan.modalOkLabel" />,
okFn: handleScanContinue,
cancelLabel: <FormattedMessage id="udapp.solScan.modalCancelLabel" />
}

await props.plugin.call('notification', 'modal', modal)
}

const label = (key: string | number, value: string) => {
return (
<div className="d-flex mt-2 flex-row label_item">
Expand Down Expand Up @@ -293,20 +390,26 @@ export function UniversalDappUI(props: UdappProps) {
</div>
<div className="udapp_cActionsWrapper" data-id="universalDappUiContractActionWrapper">
<div className="udapp_contractActionsContainer">
<div className="d-flex justify-content-between" data-id="instanceContractBal">
<label>
<div className="d-flex flex-row justify-content-between align-items-center pb-2" data-id="instanceContractBal">
<span className="remixui_runtabBalancelabel run-tab">
<b><FormattedMessage id="udapp.balance" />:</b> {instanceBalance} ETH
</label>
{props.exEnvironment && props.exEnvironment.startsWith('injected') && (
<CustomTooltip placement="top" tooltipClasses="text-nowrap" tooltipId="udapp_udappEditTooltip" tooltipText={<FormattedMessage id="udapp.tooltipTextEdit" />}>
<i
className="fas fa-edit btn btn-sm p-0"
onClick={() => {
props.editInstance(props.instance)
}}
></i>
</span>
<div></div>
<div className="d-flex align-self-center">
{props.exEnvironment && props.exEnvironment.startsWith('injected') && (
<CustomTooltip placement="top" tooltipClasses="text-nowrap" tooltipId="udapp_udappEditTooltip" tooltipText={<FormattedMessage id="udapp.tooltipTextEdit" />}>
<i
className="fas fa-edit pr-3"
onClick={() => {
props.editInstance(props.instance)
}}
></i>
</CustomTooltip>
)}
<CustomTooltip placement="top" tooltipClasses="text-nowrap" tooltipId="udapp_udappSolScanTooltip" tooltipText={<FormattedMessage id="udapp.solScan.iconTooltip" />}>
<i className="fas fa-qrcode p-0" onClick={askPermissionToScan}></i>
</CustomTooltip>
)}
</div>
</div>
{ props.isPinnedContract && props.instance.pinnedAt ? (
<div className="d-flex" data-id="instanceContractPinnedAt">
Expand Down
5 changes: 5 additions & 0 deletions libs/remix-ui/run-tab/src/lib/css/run-tab.css
Original file line number Diff line number Diff line change
Expand Up @@ -525,3 +525,8 @@
width: 100%;
}

.remixui_runtabBalancelabel {
font-size: 0.688rem;
line-height: 0.75rem;
color: var(--runtab);
}