Skip to content
This repository has been archived by the owner on Jul 15, 2024. It is now read-only.

Commit

Permalink
Fast and dirty drag-n-drop (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszradomski authored Oct 30, 2023
1 parent 20cc9c6 commit 969d71b
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 96 deletions.
3 changes: 2 additions & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"react-dom": "^18.2.0",
"zod": "^3.20.2",
"zustand": "^4.3.2",
"@total-typescript/ts-reset": "^0.3.7"
"@total-typescript/ts-reset": "^0.3.7",
"@l2beat/discovery-types": "0.4.1"
},
"devDependencies": {
"@vitejs/plugin-react-swc": "^3.0.0",
Expand Down
135 changes: 86 additions & 49 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import '@total-typescript/ts-reset'

import { DiscoveryOutput } from '@l2beat/discovery-types'
import cx from 'classnames'
import { useState } from 'react'

import { deleteNode } from './api/delete'
import { discover } from './api/discover'
import { merge } from './api/merge'
import { SimpleNode } from './api/SimpleNode'
import { transformContracts } from './api/transform'
import { nodeToSimpleNode } from './store/actions/updateNodes'
import { useStore } from './store/store'
import { Sidebar } from './view/Sidebar'
Expand Down Expand Up @@ -49,6 +51,20 @@ export function App() {
setNodes((nodes) => merge(nodes, result))
}

async function discoverFromFile(discoveredFile: File) {
console.log('LOADING')

markLoading('Discovery.json parse', true)

const contents = await discoveredFile.text()
const parsed: unknown = JSON.parse(contents)
const discovery = parsed as DiscoveryOutput
const result = transformContracts(discovery)

markLoading('Discovery.json parse', false)
setNodes((nodes) => merge(nodes, result))
}

function deleteNodeAction(id: string[]) {
try {
const newNodes = deleteNode(nodes, id)
Expand All @@ -60,61 +76,82 @@ export function App() {

return (
<div
className={cx(
'grid h-full w-full grid-rows-[_1fr]',
showSidebar ? 'grid-cols-[1fr,_400px]' : 'grid-cols-[1fr]',
)}
id="drop_zone"
className="h-full w-full"
onDrop={(event) => {
event.preventDefault()
;[...event.dataTransfer.items].forEach((item) => {
if (item.kind === 'file') {
const file = item.getAsFile()
if (file) {
discoverFromFile(file).catch((e) => {
throw e
})
}
}
})
}}
onDragOver={(event) => {
event.preventDefault()
}}
>
<div className="relative flex h-full w-full items-center justify-center">
<Viewport
nodes={nodes}
loading={loading}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onDiscover={discoverContract}
/>

<div className="absolute top-0 w-full p-2">
<div className="flex flex-row content-center items-center justify-between">
<div className="ml-2">Contracts loaded: {nodes.length}</div>

<div>
<button
className={cx(
'rounded bg-blue-500 py-2 px-4 font-bold text-white',
!loading.global && 'hover:bg-blue-700',
)}
type="button"
disabled={loading.global}
onClick={() => void showPrompt()}
>
Discover!
{loading.global && '🔄'}
</button>
</div>
<div
className={cx(
'grid h-full w-full grid-rows-[_1fr]',
showSidebar ? 'grid-cols-[1fr,_400px]' : 'grid-cols-[1fr]',
)}
>
<div className="relative flex h-full w-full items-center justify-center">
<Viewport
nodes={nodes}
loading={loading}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onDiscover={discoverContract}
/>

<div>
<button
className="ml-2 text-2xl"
type="button"
disabled={loading.global}
onClick={clear}
title="Clear"
>
🚮
</button>
<div className="absolute top-0 w-full p-2">
<div className="flex flex-row content-center items-center justify-between">
<div className="ml-2">Contracts loaded: {nodes.length}</div>

<div>
<button
className={cx(
'rounded bg-blue-500 py-2 px-4 font-bold text-white',
!loading.global && 'hover:bg-blue-700',
)}
type="button"
disabled={loading.global}
onClick={() => void showPrompt()}
>
Discover!
{loading.global && '🔄'}
</button>
</div>

<div>
<button
className="ml-2 text-2xl"
type="button"
disabled={loading.global}
onClick={clear}
title="Clear"
>
🚮
</button>
</div>
</div>
</div>
</div>
</div>

{showSidebar && (
<div className="row-span-2 bg-white p-2 drop-shadow-xl">
<Sidebar
selectedNodes={selectedNodes}
onDeleteNodes={deleteNodeAction}
/>
</div>
)}
{showSidebar && (
<div className="row-span-2 bg-white p-2 drop-shadow-xl">
<Sidebar
selectedNodes={selectedNodes}
onDeleteNodes={deleteNodeAction}
/>
</div>
)}
</div>
</div>
)
}
4 changes: 2 additions & 2 deletions packages/frontend/src/api/SimpleNode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContractParameters } from './types'
import { ContractParameters } from '@l2beat/discovery-types'

interface SimpleNodeShared {
id: string
Expand All @@ -25,4 +25,4 @@ export interface UnknownNode extends SimpleNodeShared {
type: 'Unknown'
}

export type SimpleNode = ContractNode | EOANode | UnknownNode
export type SimpleNode = EOANode | UnknownNode | ContractNode
5 changes: 3 additions & 2 deletions packages/frontend/src/api/discover.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DiscoveryOutput } from '@l2beat/discovery-types'

import { SimpleNode } from './SimpleNode'
import { transformContracts } from './transform'
import { ProjectParameters } from './types'

export async function discover(
address: string,
Expand All @@ -13,6 +14,6 @@ export async function discover(
return []
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const discovery = (await res.json()) as ProjectParameters
const discovery = (await res.json()) as DiscoveryOutput
return transformContracts(discovery)
}
64 changes: 24 additions & 40 deletions packages/frontend/src/api/transform.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {
ContractParameters,
ContractValue,
DiscoveryOutput,
} from '@l2beat/discovery-types'

import { ContractNode, EOANode, SimpleNode } from './SimpleNode'
import { ContractParameters, ContractValue, ProjectParameters } from './types'

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'

export function transformContracts(discovery: ProjectParameters): SimpleNode[] {
export function transformContracts(discovery: DiscoveryOutput): SimpleNode[] {
const contractNodes: ContractNode[] = discovery.contracts.map((contract) => {
const { proxyFields, implementations } = getProxyDetails(contract)
return {
type: 'Contract',
id: contract.address,
id: contract.address.toString(),
name: emojifyContractName(contract),
discovered: true,
fields: [...proxyFields, ...mapFields(contract.values)].filter(
Expand All @@ -20,12 +25,12 @@ export function transformContracts(discovery: ProjectParameters): SimpleNode[] {

const eoaNodes: EOANode[] = discovery.eoas.map((address) => ({
type: 'EOA',
id: address,
name: `🧍 EOA ${address}`,
id: address.toString(),
name: `🧍 EOA ${address.toString()}`,
discovered: true,
fields: [],
data: {
address,
address: address.toString(),
},
}))

Expand All @@ -39,7 +44,7 @@ interface FieldProps {
}

function mapFields(
values: ContractValue | undefined,
values: Record<string, ContractValue> | ContractValue | undefined,
prefix = '',
): FieldProps[] {
if (values === undefined) {
Expand Down Expand Up @@ -98,58 +103,37 @@ function getProxyDetails(contract: ContractParameters): {
implementations: string[]
} {
const proxyFields: FieldProps[] = []
const implementations: string[] = []
const implementations: string[] =
contract.implementations?.map((a) => a.toString()) ?? []
switch (contract.upgradeability.type) {
case 'immutable':
break
case 'gnosis safe':
implementations.push(contract.upgradeability.masterCopy)
break
case 'EIP1967 proxy':
proxyFields.push({ name: 'admin', value: contract.upgradeability.admin })
implementations.push(contract.upgradeability.implementation)
proxyFields.push({
name: 'admin',
value: contract.upgradeability.admin.toString(),
})
break
case 'ZeppelinOS proxy':
if (contract.upgradeability.admin) {
proxyFields.push({
name: 'admin',
value: contract.upgradeability.admin,
value: contract.upgradeability.admin.toString(),
})
}
implementations.push(contract.upgradeability.implementation)
break
case 'StarkWare proxy':
implementations.push(contract.upgradeability.implementation)
break
case 'StarkWare diamond':
implementations.push(
contract.upgradeability.implementation,
...Object.values(contract.upgradeability.facets),
)
break
case 'Arbitrum proxy':
case 'new Arbitrum proxy':
proxyFields.push({ name: 'admin', value: contract.upgradeability.admin })
implementations.push(
contract.upgradeability.userImplementation,
contract.upgradeability.adminImplementation,
)
proxyFields.push({
name: 'admin',
value: contract.upgradeability.admin.toString(),
})
break
case 'resolved delegate proxy':
proxyFields.push({
name: 'addressManager',
value: contract.upgradeability.addressManager,
value: contract.upgradeability.addressManager.toString(),
})
implementations.push(contract.upgradeability.implementation)
break
case 'EIP897 proxy':
implementations.push(contract.upgradeability.implementation)
break
case 'call implementation proxy':
implementations.push(contract.upgradeability.implementation)
break
case 'EIP2535 diamond proxy':
implementations.push(...contract.upgradeability.facets)
break
}

Expand Down
7 changes: 5 additions & 2 deletions packages/frontend/src/view/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ function SidebarForSingleNode({
}

const humanReadableName = node.type === 'Contract' ? node.name : node.type
const etherscanLink = `https://etherscan.io/address/${node.data.address}`
const sourceLink = node.type === 'Contract' ? node.data.code : undefined
const etherscanLink = `https://etherscan.io/address/${node.data.address.toString()}`
const sourceLink =
node.type === 'Contract'
? `https://vscode.blockscan.com/ethereum/${node.data.address.toString()}`
: undefined

return (
<>
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,13 @@
methods "^1.1.2"
path-to-regexp "^6.1.0"

"@l2beat/[email protected]":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@l2beat/discovery-types/-/discovery-types-0.4.1.tgz#174be097d96a9e5b3dd0ba61e88b35642e70c2e5"
integrity sha512-e9m2Yb4K0jkwCToQyu6h1bXf921gXJQ1M38c5LlH/dG1SpY3EwuJzZ1XGjY/UWxHe1eElNw7Ge+zwQuWT7ObEw==
dependencies:
zod "^3.22.2"

"@nodelib/[email protected]":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
Expand Down Expand Up @@ -5447,6 +5454,11 @@ zod@^3.20.2:
resolved "https://registry.yarnpkg.com/zod/-/zod-3.20.2.tgz#068606642c8f51b3333981f91c0a8ab37dfc2807"
integrity sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==

zod@^3.22.2:
version "3.22.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==

zustand@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.2.tgz#bb121fcad84c5a569e94bd1a2695e1a93ba85d39"
Expand Down

1 comment on commit 969d71b

@vercel
Copy link

@vercel vercel bot commented on 969d71b Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.