diff --git a/packages/govern-console/.eslintrc.js b/packages/govern-console/.eslintrc.js index a893cb905..4910dbde5 100644 --- a/packages/govern-console/.eslintrc.js +++ b/packages/govern-console/.eslintrc.js @@ -36,7 +36,7 @@ module.exports = { settings: { react: { pragma: 'React', - version: '16.6', + version: '16.13.1', }, 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], diff --git a/packages/govern-console/package.json b/packages/govern-console/package.json index d82eb07b0..312efe935 100644 --- a/packages/govern-console/package.json +++ b/packages/govern-console/package.json @@ -19,7 +19,7 @@ "apollo-link-http": "^1.5.17", "bn.js": "^5.1.3", "clipboard-polyfill": "^2.8.6", - "dayjs": "^1.8.35", + "date-fns": "^2.16.1", "ethers": "^5.0.14", "graphql": "^15.0.0", "graphql-tag": "^2.10.3", diff --git a/packages/govern-console/src/App.tsx b/packages/govern-console/src/App.tsx index 847295495..ee364fffe 100644 --- a/packages/govern-console/src/App.tsx +++ b/packages/govern-console/src/App.tsx @@ -2,9 +2,9 @@ import React from 'react' import { HashRouter as Router, Route, Switch } from 'react-router-dom' import 'styled-components/macro' import TopHeader from './components/Header/Header' -import DaoSelector from './pages/DaoSelector' -import DaoView from './pages/DaoView' -import ErcTool from './Tools/Erc' +import SelectDao from './pages/SelectDao' +import ViewDao from './pages/ViewDao' +import ErcTool from './apps/Erc' function App() { return ( @@ -19,13 +19,13 @@ function App() { - + - + diff --git a/packages/govern-console/src/Tools/Erc.tsx b/packages/govern-console/src/apps/Erc.tsx similarity index 100% rename from packages/govern-console/src/Tools/Erc.tsx rename to packages/govern-console/src/apps/Erc.tsx diff --git a/packages/govern-console/src/components/Frame/Frame.tsx b/packages/govern-console/src/components/Frame/Frame.tsx new file mode 100644 index 000000000..d2d8595c7 --- /dev/null +++ b/packages/govern-console/src/components/Frame/Frame.tsx @@ -0,0 +1,32 @@ +import * as React from 'react' +import 'styled-components/macro' + +type FrameProps = { + children: React.ReactNode +} + +export default function Frame({ children }: FrameProps) { + return ( +
+ {children} +
+ ) +} diff --git a/packages/govern-console/src/components/Info/Info.tsx b/packages/govern-console/src/components/Info/Info.tsx new file mode 100644 index 000000000..1f5581c26 --- /dev/null +++ b/packages/govern-console/src/components/Info/Info.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import 'styled-components/macro' + +type InfoProps = { + mode: 'error' | 'info' | 'success' | '' + children: React.ReactNode +} + +function resolveColorsFromStatus(mode: string): string { + if (mode === 'error') { + return 'red' + } + if (mode === 'success') { + return 'green' + } + return 'cyan' +} + +export default function Info({ mode, children }: InfoProps) { + return ( +
+ {children} +
+ ) +} diff --git a/packages/govern-console/src/components/NewAction.tsx b/packages/govern-console/src/components/NewAction/NewAction.tsx similarity index 67% rename from packages/govern-console/src/components/NewAction.tsx rename to packages/govern-console/src/components/NewAction/NewAction.tsx index 4b3ef06f2..3156236ee 100644 --- a/packages/govern-console/src/components/NewAction.tsx +++ b/packages/govern-console/src/components/NewAction/NewAction.tsx @@ -4,11 +4,14 @@ import { useWallet } from 'use-wallet' import abiCoder from 'web3-eth-abi' import { toHex } from 'web3-utils' import 'styled-components/macro' -import Button from './Button' -import { useContract } from '../lib/web3-contracts' -import queueAbi from '../lib/abi/GovernQueue.json' +import Button from '../Button' +import Frame from '../Frame/Frame' +import { useContract } from '../../lib/web3-contracts' +import queueAbi from '../../lib/abi/GovernQueue.json' const EMPTY_BYTES = '0x00' +const EMPTY_FAILURE_MAP = + '0x0000000000000000000000000000000000000000000000000000000000000000' type Input = { name: string | undefined @@ -37,6 +40,9 @@ export default function NewAction({ const [contractAddress, setContractAddress] = useState('') const [parsedAbi, setParsedAbi] = useState([]) const [proof, setProof] = useState('') + const [executionResult, setExecutionResult] = useState('') + const [type, setType] = useState('') + const queueContract = useContract(queueAddress, queueAbi) const handleParseAbi = useCallback( @@ -52,6 +58,24 @@ export default function NewAction({ [abi], ) + const handleSetExecutionResult = useCallback( + (result, message) => { + if (result === 'confirmed') { + setType('green') + setExecutionResult(message) + } + if (result === 'info') { + setType('cyan') + setExecutionResult(message) + } + if (result === 'error') { + setType('red') + setExecutionResult(message) + } + }, + [setExecutionResult], + ) + return ( <>

New action

-
+ + {executionResult && ( +
+ {executionResult} +
+ )}
abiItem?.type === 'function' && ( -
+ -
+ ), )} -
+ ) } @@ -174,6 +186,7 @@ type ContractCallHandlerProps = { contractAddress: string config: any executor: string + handleSetExecutionResult: (result: string, v: string) => void inputs: Input[] | any[] name: string proof: string @@ -185,6 +198,7 @@ function ContractCallHandler({ config, contractAddress, executor, + handleSetExecutionResult, inputs, name, proof, @@ -238,48 +252,61 @@ function ContractCallHandler({ const bnNonce = new BN(nonce.toString()) const newNonce = bnNonce.add(new BN('1')) - // Right now + 120 seconds into the future (in the future, this should be configurable) - const currentDate = Math.round(Date.now() / 1000) + 120 - - const tx = await queueContract['schedule']( - { - payload: { - nonce: newNonce.toString(), - executionTime: currentDate, - submitter: account, - executor, - actions: [ - { - to: contractAddress, - value: EMPTY_BYTES, - data: encodedFunctionCall, - }, - ], - allowFailuresMap: '0x0000000000000000000000000000000000000000000000000000000000000000', - proof: proof ? toHex(proof) : EMPTY_BYTES, - }, - config: { - executionDelay: config.executionDelay, - scheduleDeposit: { - token: config.scheduleDeposit.token.id, - amount: config.scheduleDeposit.amount, - }, - challengeDeposit: { - token: config.challengeDeposit.token.id, - amount: config.challengeDeposit.amount, + // TODO: handle token approvals + // Current time + 30 secs buffer. + // This is necessary for DAOs with lower execution delays, in which + // the tx getting picked up by a later block can make the tx fail. + const currentDate = + Math.ceil(Date.now() / 1000) + Number(config.executionDelay) + 30 + const container = { + payload: { + nonce: newNonce.toString(), + executionTime: currentDate, + submitter: account, + executor, + actions: [ + { + to: contractAddress, + value: EMPTY_BYTES, + data: encodedFunctionCall, }, - resolver: config.resolver, - rules: config.rules, - }, + ], + allowFailuresMap: EMPTY_FAILURE_MAP, + proof: proof ? toHex(proof) : EMPTY_BYTES, }, - { - gasLimit: 500000, + config: { + executionDelay: config.executionDelay, + scheduleDeposit: { + token: config.scheduleDeposit.token, + amount: config.scheduleDeposit.amount, + }, + challengeDeposit: { + token: config.challengeDeposit.token, + amount: config.challengeDeposit.amount, + }, + resolver: config.resolver, + rules: config.rules, }, - ) + } + + const tx = await queueContract.schedule(container, { + gasLimit: 500000, + }) + + handleSetExecutionResult('info', `Sending transaction.`) + await tx.wait(1) setResult(tx.hash) + handleSetExecutionResult( + 'confirmed', + `Transaction sent successfully. hash: ${tx.hash}`, + ) } catch (e) { console.error(e) + handleSetExecutionResult( + 'error', + `There was an error with the transaction.`, + ) } }, [ @@ -287,8 +314,9 @@ function ContractCallHandler({ config, contractAddress, executor, - proof, + handleSetExecutionResult, queueContract, + proof, rawAbiItem, values, ], diff --git a/packages/govern-console/src/components/ViewAction/ViewAction.tsx b/packages/govern-console/src/components/ViewAction/ViewAction.tsx new file mode 100644 index 000000000..9cbcf2d20 --- /dev/null +++ b/packages/govern-console/src/components/ViewAction/ViewAction.tsx @@ -0,0 +1,376 @@ +import React, { useMemo, useCallback, useState } from 'react' +import { useParams } from 'react-router-dom' +import 'styled-components/macro' +import Button from '../Button' +import Frame from '../Frame/Frame' +import Info from '../Info/Info' +import { useContract } from '../../lib/web3-contracts' +import { shortenAddress } from '../../lib/web3-utils' +import queueAbi from '../../lib/abi/GovernQueue.json' +import { useWallet } from '../../Providers/Wallet' + +const EMPTY_FAILURE_MAP = + '0x0000000000000000000000000000000000000000000000000000000000000000' + +type Collateral = { + token: string + amount: string +} + +type Config = { + executionDelay: string + scheduleDeposit: Collateral + challengeDeposit: Collateral + resolver: string + rules: string +} + +type Action = { + id: string + to: string + value: string + data: string +} + +type Payload = { + id: string + nonce: string + executionTime: string + submitter: string + executor: any + actions: Action[] + proof: string +} + +type ContainerEventChallenge = { + id: string + container: any + createdAt: string + actor: string + collateral: Collateral + disputeId: string + reason: string + resolver: string +} + +type ContainerEventExecute = { + id: string + container: any + createdAt: string + execResults: string[] +} + +type ContainerEventResolve = { + id: string + container: any + createdAt: string + approved: Boolean +} + +type ContainerEventRule = { + id: string + container: any + createdAt: string + ruling: string +} + +type ContainerEventSchedule = { + id: string + container: any + createdAt: string + collateral: Collateral +} + +type ContainerEventSubmitEvidence = { + id: string + container: any + createdAt: string + evidence: string + submitter: string + finished: Boolean +} + +type ContainerEventVeto = { + id: string + container: any + created: string + reason: string +} + +type ContainerEvent = + | ContainerEventChallenge + | ContainerEventExecute + | ContainerEventResolve + | ContainerEventRule + | ContainerEventSchedule + | ContainerEventSubmitEvidence + | ContainerEventVeto + +type Container = { + id: string + queue: string + state: string + config: Config + payload: Payload + history: ContainerEvent[] +} + +type ViewActionWrapperProps = { + containers: Container[] + queueAddress: string +} + +type ViewActionProps = { + container: Container + queueAddress: string +} + +function ViewAction({ container, queueAddress }: ViewActionProps) { + const { wallet } = useWallet() + const { status: accountStatus } = wallet + const [executionStatus, setExecutionStatus] = useState('') + const [statusType, setStatusType] = useState< + 'error' | 'info' | 'success' | '' + >('') + const queueContract = useContract(queueAddress, queueAbi) + + const handleSetExecutionStatus = useCallback( + (result, message) => { + setStatusType(result) + setExecutionStatus(message) + }, + [setExecutionStatus, setStatusType], + ) + + const execute = useCallback(async () => { + if (accountStatus !== 'connected') { + alert('Executing actions requires a signer. Please connect your account.') + return + } + const payloadActions = container.payload.actions.map((action: Action) => ({ + to: action.to, + value: action.value, + data: action.data, + })) + const craftedContainer = { + payload: { + nonce: container.payload.nonce, + executionTime: container.payload.executionTime, + submitter: container.payload.submitter, + executor: container.payload.executor.address, + actions: payloadActions, + allowFailuresMap: EMPTY_FAILURE_MAP, + proof: container.payload.proof, + }, + config: { + executionDelay: container.config.executionDelay, + scheduleDeposit: { + token: container.config.scheduleDeposit.token, + amount: container.config.scheduleDeposit.amount, + }, + challengeDeposit: { + token: container.config.challengeDeposit.token, + amount: container.config.challengeDeposit.amount, + }, + resolver: container.config.resolver, + rules: container.config.rules, + }, + } + + try { + const tx = await queueContract!.execute(craftedContainer, { + gasLimit: 500000, + }) + handleSetExecutionStatus('info', `Sending transaction.`) + await tx.wait(1) + handleSetExecutionStatus( + 'success', + `Transaction sent successfully. hash: ${tx.hash}`, + ) + } catch (err) { + console.log(err) + handleSetExecutionStatus( + 'error', + `There was an error with the transaction.`, + ) + } + }, [accountStatus, container, handleSetExecutionStatus, queueContract]) + + const veto = useCallback(async () => { + if (accountStatus !== 'connected') { + alert('Executing actions requires a signer. Please connect your account.') + return + } + try { + const containerHash = container.id + const tx = await queueContract!.veto(containerHash, '0x00', { + gasLimit: 500000, + }) + handleSetExecutionStatus('info', `Sending transaction.`) + await tx.wait(1) + handleSetExecutionStatus( + 'success', + `Transaction sent successfully. hash: ${tx.hash}`, + ) + } catch (err) { + console.log(err) + handleSetExecutionStatus( + 'error', + `There was an error with the transaction.`, + ) + } + }, [accountStatus, container, handleSetExecutionStatus, queueContract]) + + const challenge = useCallback(async () => { + if (accountStatus !== 'connected') { + alert('Executing actions requires a signer. Please connect your account.') + return + } + const payloadActions = container.payload.actions.map((action: Action) => ({ + to: action.to, + value: action.value, + data: action.data, + })) + // TODO: handle token approvals first + const craftedContainer = { + payload: { + nonce: container.payload.nonce, + executionTime: container.payload.executionTime, + submitter: container.payload.submitter, + executor: container.payload.executor.address, + actions: payloadActions, + allowFailuresMap: EMPTY_FAILURE_MAP, + proof: container.payload.proof, + }, + config: { + executionDelay: container.config.executionDelay, + scheduleDeposit: { + token: container.config.scheduleDeposit.token, + amount: container.config.scheduleDeposit.amount, + }, + challengeDeposit: { + token: container.config.challengeDeposit.token, + amount: container.config.challengeDeposit.amount, + }, + resolver: container.config.resolver, + rules: container.config.rules, + }, + } + try { + const tx = await queueContract!.challenge(craftedContainer, '0x00', { + gasLimit: 500000, + }) + handleSetExecutionStatus('info', `Sending transaction.`) + await tx.wait(1) + handleSetExecutionStatus( + 'success', + `Transaction sent successfully. hash: ${tx.hash}`, + ) + } catch (err) { + console.log(err) + handleSetExecutionStatus( + 'error', + `There was an error with the transaction.`, + ) + } + }, [accountStatus, container, handleSetExecutionStatus, queueContract]) + + return ( + <> + +

Action {shortenAddress(container.id)}

+

Status

+

{container.state}

+ {executionStatus && {executionStatus}} + + + +

Available actions

+ + + + + + +

Action Payload

+

Nonce

+

{container.payload.nonce}

+

Execution time

+

{container.payload.executionTime}

+

Submitter

+

{container.payload.submitter}

+

Proof (Justification)

+

{container.payload.proof}

+

On-chain actions

+

+

+

+ + + +

Action Configuration

+

Execution Delay

+

{container.config.executionDelay}

+

Schedule deposit

+

Token: {container.config.scheduleDeposit.token}

+

Amount: {container.config.scheduleDeposit.amount}

+

Challenge deposit

+

Token: {container.config.challengeDeposit.token}

+

Amount: {container.config.challengeDeposit.amount}

+

Resolver

+

{container.config.resolver}

+

Rules

+

{container.config.rules}

+ + + ) +} + +export default function ViewActionWrapper({ + containers, + queueAddress, +}: ViewActionWrapperProps) { + const { containerId }: any = useParams() + const container = useMemo(() => { + return containers.find(container => container.id === containerId) + }, [containerId, containers]) + + if (!container) { + return Container not found. + } + + return +} diff --git a/packages/govern-console/src/components/ViewDao/ViewDao.tsx b/packages/govern-console/src/components/ViewDao/ViewDao.tsx new file mode 100644 index 000000000..e430693dc --- /dev/null +++ b/packages/govern-console/src/components/ViewDao/ViewDao.tsx @@ -0,0 +1,150 @@ +import React, { useCallback, useMemo } from 'react' +import { useHistory, useParams } from 'react-router-dom' +import 'styled-components/macro' +import Button from '../Button' +import Frame from '../Frame/Frame' +import { KNOWN_GOVERN_ROLES, KNOWN_QUEUE_ROLES } from '../../lib/known-roles' +import { shortenAddress, ETH_ANY_ADDRESS } from '../../lib/web3-utils' + +type ViewDaoProps = { + dao: any +} + +export default function ViewDao({ dao }: ViewDaoProps) { + const { daoAddress }: any = useParams() + const history = useHistory() + + const handleNewAction = useCallback(() => { + history.push(`/${daoAddress}/new-action`) + }, [history, daoAddress]) + + const hasActions = useMemo(() => dao.queue.queued.length > 0, [dao]) + + return ( + <> +

+ Info for {daoAddress} +

+ +

Govern Executor

+

Address

+

{dao.executor.address}

+

Govern Queue

+

Address

+

{dao.queue.address}

+

Config

+

Execution delay: {dao.queue.config.executionDelay}

+

Schedule collateral:

+ +

Challenge collateral:

+ + + +

Actions

+ {hasActions + ? dao.queue.queued.map(({ id }: { id: string }) => ( + + )) + : 'No actions.'} +
+ +
+ + +

Permissions for Govern

+ {dao.executor.roles.map((role: any) => { + return ( +
+

+ {KNOWN_GOVERN_ROLES.get(role.selector)} - {role.selector} +

+

+ Who has permission:{' '} + {role.who === ETH_ANY_ADDRESS ? 'Anyone' : role.who} +

+
+ ) + })} + + +

Permissions for GovernQueue

+ {dao.queue.roles.map((role: any) => { + return ( +
+

+ {KNOWN_QUEUE_ROLES.get(role.selector)} - {role.selector} +

+

+ Who has permission:{' '} + {role.who === ETH_ANY_ADDRESS ? 'Anyone' : role.who} +

+
+ ) + })} + + + ) +} + +type ActionCardProps = { + id: string +} + +function ActionCard({ id }: ActionCardProps) { + const history = useHistory() + const { daoAddress }: any = useParams() + + const handleCardClick = useCallback(() => { + history.push(`${daoAddress}/view-action/${id}`) + }, [daoAddress, history, id]) + + return ( + + ) +} diff --git a/packages/govern-console/src/environment.ts b/packages/govern-console/src/environment.ts index b6ff3e85d..04971bcb9 100644 --- a/packages/govern-console/src/environment.ts +++ b/packages/govern-console/src/environment.ts @@ -1,10 +1,8 @@ -// rinkeby const CHAIN_ID_DEFAULT = 4 const ENV_VARS = { CHAIN_ID() { - // @ts-ignore - const chainId = parseInt(process.env.REACT_APP_CHAIN_ID) + const chainId = parseInt(process.env.REACT_APP_CHAIN_ID ?? '4') return isNaN(chainId) ? CHAIN_ID_DEFAULT : chainId }, ENABLE_SENTRY() { diff --git a/packages/govern-console/src/index.tsx b/packages/govern-console/src/index.tsx index 66c25adfa..b84f347bc 100644 --- a/packages/govern-console/src/index.tsx +++ b/packages/govern-console/src/index.tsx @@ -4,22 +4,26 @@ import { createGlobalStyle } from 'styled-components' import 'styled-components/macro' import { ApolloClient, + ApolloProvider, InMemoryCache, NormalizedCacheObject, - ApolloProvider -} from '@apollo/client'; +} from '@apollo/client' import App from './App' import GeneralProvider from './Providers/GeneralProvider' -export const rinkebyClient: ApolloClient = new ApolloClient({ - uri: 'https://api.thegraph.com/subgraphs/name/aragon/aragon-govern-rinkeby', - cache: new InMemoryCache() -}); +export const rinkebyClient: ApolloClient = new ApolloClient( + { + uri: 'https://api.thegraph.com/subgraphs/name/evalir/aragon-govern-rinkeby', + cache: new InMemoryCache(), + }, +) -export const mainnetClient: ApolloClient = new ApolloClient({ - uri: 'https://api.thegraph.com/subgraphs/name/aragon/aragon-govern-mainnet', - cache: new InMemoryCache() -}) +export const mainnetClient: ApolloClient = new ApolloClient( + { + uri: 'https://api.thegraph.com/subgraphs/name/aragon/aragon-govern-mainnet', + cache: new InMemoryCache(), + }, +) const GlobalStyle = createGlobalStyle` *, *:before, *:after { @@ -68,7 +72,6 @@ render( - - , + , document.getElementById('root'), ) diff --git a/packages/govern-console/src/lib/date-utils.js b/packages/govern-console/src/lib/date-utils.js deleted file mode 100644 index 742a89b36..000000000 --- a/packages/govern-console/src/lib/date-utils.js +++ /dev/null @@ -1,15 +0,0 @@ -import dayjs from 'dayjs' - -export function getExecutionTimeFromUnix(futureTimestamp) { - const now = dayjs() - const executionTime = dayjs.unix(Number(futureTimestamp)) - - const minutesDiff = executionTime.diff(now, 'm') - - if (minutesDiff <= 0) { - const secondsDiff = executionTime.diff(now, 's') - return executionTime.diff(now, 's') <= 0 ? 'None' : `0m ${secondsDiff}s` - } - - return `${minutesDiff}m` -} diff --git a/packages/govern-console/src/lib/known-contracts.js b/packages/govern-console/src/lib/known-contracts.ts similarity index 82% rename from packages/govern-console/src/lib/known-contracts.js rename to packages/govern-console/src/lib/known-contracts.ts index c3eb2e84c..66e41748f 100644 --- a/packages/govern-console/src/lib/known-contracts.js +++ b/packages/govern-console/src/lib/known-contracts.ts @@ -12,11 +12,12 @@ const KNOWN_CONTRACTS_BY_ENV = new Map([ const ABIS = new Map([ ['AGREEMENT', agreementAbi], - ['TOKEN', erc20Abi] + ['TOKEN', erc20Abi], ]) -export function getKnownContract(name) { +export function getKnownContract(name: string): any { const knownContracts = KNOWN_CONTRACTS_BY_ENV.get('4') || {} + // @ts-ignore return [knownContracts[name] || null, ABIS.get(name) || []] } diff --git a/packages/govern-console/src/lib/known-roles.ts b/packages/govern-console/src/lib/known-roles.ts new file mode 100644 index 000000000..611165af6 --- /dev/null +++ b/packages/govern-console/src/lib/known-roles.ts @@ -0,0 +1,10 @@ +export const KNOWN_QUEUE_ROLES = new Map([ + ['0x977d8964', 'schedule'], + ['0x3a139c71', 'execute'], + ['0x70576158', 'challenge'], + ['0x72896761', 'configure'], + ['0xc04c87b8', 'veto'], + ['0x586df604', 'ROOT_ROLE'], +]) + +export const KNOWN_GOVERN_ROLES = new Map([['0xc2d85afc', 'exec']]) diff --git a/packages/govern-console/src/lib/theme.ts b/packages/govern-console/src/lib/theme.ts new file mode 100644 index 000000000..5ea60925b --- /dev/null +++ b/packages/govern-console/src/lib/theme.ts @@ -0,0 +1,3 @@ +export default { + +} diff --git a/packages/govern-console/src/lib/utils.ts b/packages/govern-console/src/lib/utils.ts new file mode 100644 index 000000000..56012e8f1 --- /dev/null +++ b/packages/govern-console/src/lib/utils.ts @@ -0,0 +1,8 @@ +export function log(...params) { + if ( + process.env.NODE_ENV !== 'production' && + process.env.NODE_ENV !== 'test' + ) { + console.log(...params) + } +} diff --git a/packages/govern-console/src/lib/web3-contracts.js b/packages/govern-console/src/lib/web3-contracts.js index cb769ebf7..591533536 100644 --- a/packages/govern-console/src/lib/web3-contracts.js +++ b/packages/govern-console/src/lib/web3-contracts.js @@ -6,7 +6,7 @@ import { getDefaultProvider, } from 'ethers' import { useWallet } from 'use-wallet' -import { getKnownContract } from './known-contracts.js' +import { getKnownContract } from './known-contracts' import { useChainId } from '../Providers/ChainId' import { bigNum, getNetworkNode } from './web3-utils' diff --git a/packages/govern-console/src/lib/web3-utils.js b/packages/govern-console/src/lib/web3-utils.js index ddcd3acbf..8585880cc 100644 --- a/packages/govern-console/src/lib/web3-utils.js +++ b/packages/govern-console/src/lib/web3-utils.js @@ -1,7 +1,9 @@ import env from '../environment' import { utils as EthersUtils } from 'ethers' + export const DEFAULT_LOCAL_CHAIN = 'private' export const ETH_FAKE_ADDRESS = `0x${''.padEnd(40, '0')}` +export const ETH_ANY_ADDRESS = '0xffffffffffffffffffffffffffffffffffffffff' const ETH_ADDRESS_SPLIT_REGEX = /(0x[a-fA-F0-9]{40}(?:\b|\.|,|\?|!|;))/g const ETH_ADDRESS_TEST_REGEX = /(0x[a-fA-F0-9]{40}(?:\b|\.|,|\?|!|;))/g @@ -113,8 +115,9 @@ export function isLocalOrUnknownNetwork(chainId = env('CHAIN_ID')) { export function hexToAscii(hexx) { const hex = hexx.toString() let str = '' - for (let i = 0; i < hex.length && hex.substr(i, 2) !== '00'; i += 2) + for (let i = 0; i < hex.length && hex.substr(i, 2) !== '00'; i += 2) { str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)) + } return str } diff --git a/packages/govern-console/src/pages/DaoView.tsx b/packages/govern-console/src/pages/DaoView.tsx deleted file mode 100644 index 7c2bc8a55..000000000 --- a/packages/govern-console/src/pages/DaoView.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import React, { useCallback, useMemo } from 'react' -import { - Route, - Switch, - useHistory, - useRouteMatch, - useParams, -} from 'react-router-dom' -import 'styled-components/macro' -import { gql, useQuery } from '@apollo/client' -import Button from '../components/Button' -import NewAction from '../components/NewAction' -import { useChainId } from '../Providers/ChainId' -import { rinkebyClient, mainnetClient } from '../index' - -const ANY_ADDRESS = '0xffffffffffffffffffffffffffffffffffffffff' - -const KNOWN_QUEUE_ROLES = new Map([ - ['0x977d8964', 'schedule'], - ['0x3a139c71', 'execute'], - ['0x70576158', 'challenge'], - ['0x72896761', 'configure'], - ['0xc04c87b8', 'veto'], - ['0x586df604', 'ROOT_ROLE'], -]) - -const KNOWN_GOVERN_ROLES = new Map([['0xc2d85afc', 'exec']]) - -const DAO_QUERY = gql` - query DAOQuery($name: String) { - registryEntry(id: $name) { - name - executor { - address - roles { - selector - who - frozen - } - } - queue { - address - roles { - selector - who - frozen - } - queued { - id - state - payload { - nonce - executionTime - submitter - proof - } - } - config { - executionDelay - scheduleDeposit { - token - amount - } - challengeDeposit { - token - amount - } - resolver - rules - } - } - } - } -` - -export default function DaoView() { - const { chainId } = useChainId() - const { daoAddress }: any = useParams() - const { path } = useRouteMatch() - const { data, loading, error } = useQuery(DAO_QUERY, { - variables: { - name: daoAddress, - }, - client: chainId === 4 ? rinkebyClient : mainnetClient, - }) - - if (loading) { - return

Loading...

- } - - if (error) { - console.error(error) - return

Error

- } - - if (!data.registryEntry) { - return

DAO not found.

- } - - return ( - - - - - - - - - - -

Route not found :(

-
-
- ) -} - -type DaoInfoProps = { - dao: any -} - -function DaoInfo({ dao }: DaoInfoProps) { - const { daoAddress }: any = useParams() - - return ( - <> -

- Info for {daoAddress} -

-
-

Govern Executor

-

Address

-

{dao.executor.address}

-

Govern Queue

-

Address

-

{dao.queue.address}

-

Config

-

Execution delay: {dao.queue.config.executionDelay}

-

Schedule collateral:

-
    -
  • Token: {dao.queue.config.scheduleDeposit.token}
  • -
  • Amount: {dao.queue.config.scheduleDeposit.amount}
  • -
-

Challenge collateral:

-
    -
  • Token: {dao.queue.config.challengeDeposit.token}
  • -
  • Amount: {dao.queue.config.challengeDeposit.amount}
  • -
-
- - ) -} - -function Actions({ dao }: DaoInfoProps) { - const history = useHistory() - const { daoAddress }: any = useParams() - - const handleNewAction = useCallback(() => { - history.push(`/${daoAddress}/new-action`) - }, [history, daoAddress]) - - const hasActions = useMemo(() => dao.queue.queued.length > 0, [dao]) - - return ( -
-

Actions

- {hasActions - ? dao.queue.queued.map(({ id }: { id: string }) => ( - - )) - : 'No actions.'} - -
- ) -} - -type PermissionsProps = { - dao: any -} - -function Permissions({ dao }: PermissionsProps) { - return ( - <> -
-

Permissions for Govern

- {dao.executor.roles.map((role: any) => { - return ( -
-

- {KNOWN_GOVERN_ROLES.get(role.selector)} - {role.selector} -

-

- Who has permission:{' '} - {role.who === ANY_ADDRESS ? 'Anyone' : role.who} -

-
- ) - })} -
-
-

Permissions for GovernQueue

- {dao.queue.roles.map((role: any) => { - return ( -
-

- {KNOWN_QUEUE_ROLES.get(role.selector)} - {role.selector} -

-

- Who has permission:{' '} - {role.who === ANY_ADDRESS ? 'Anyone' : role.who} -

-
- ) - })} -
- - ) -} - -type ActionCardProps = { - id: string -} - -function ActionCard({ id }: ActionCardProps) { - const history = useHistory() - - const handleCardClick = useCallback(() => { - history.push(`/tools/${id}`) - }, [history, id]) - - return ( - - ) -} diff --git a/packages/govern-console/src/pages/DaoSelector.tsx b/packages/govern-console/src/pages/SelectDao.tsx similarity index 92% rename from packages/govern-console/src/pages/DaoSelector.tsx rename to packages/govern-console/src/pages/SelectDao.tsx index cf1579e9d..a2cc8456c 100644 --- a/packages/govern-console/src/pages/DaoSelector.tsx +++ b/packages/govern-console/src/pages/SelectDao.tsx @@ -48,10 +48,7 @@ export default function DaoSelector() { `} /> - @@ -93,7 +90,11 @@ function ToolCard({ name, id }: ToolCardProps) { width: 280px; height: 320px; border: 2px solid transparent; - border-image: linear-gradient(to bottom right, #AD41BB 20%, #FF7D7D 100%); + border-image: linear-gradient( + to bottom right, + #ad41bb 20%, + #ff7d7d 100% + ); border-image-slice: 1; padding: 16px; cursor: pointer; diff --git a/packages/govern-console/src/pages/ViewDao.tsx b/packages/govern-console/src/pages/ViewDao.tsx new file mode 100644 index 000000000..be7e20632 --- /dev/null +++ b/packages/govern-console/src/pages/ViewDao.tsx @@ -0,0 +1,127 @@ +import React from 'react' +import { Route, Switch, useRouteMatch, useParams } from 'react-router-dom' +import 'styled-components/macro' +import { gql, useQuery } from '@apollo/client' +import NewAction from '../components/NewAction/NewAction' +import ViewAction from '../components/ViewAction/ViewAction' +import ViewDao from '../components/ViewDao/ViewDao' +import { useChainId } from '../Providers/ChainId' +import { rinkebyClient, mainnetClient } from '../index' + +const DAO_QUERY = gql` + query DAOQuery($name: String) { + registryEntry(id: $name) { + name + executor { + address + roles { + selector + who + frozen + } + } + queue { + address + roles { + selector + who + frozen + } + queued { + id + state + payload { + nonce + executionTime + submitter + proof + actions { + id + to + value + data + } + executor { + address + } + } + config { + executionDelay + scheduleDeposit { + token + amount + } + challengeDeposit { + token + amount + } + resolver + rules + } + } + config { + executionDelay + scheduleDeposit { + token + amount + } + challengeDeposit { + token + amount + } + resolver + rules + } + } + } + } +` + +export default function DaoView() { + const { chainId } = useChainId() + const { daoAddress }: any = useParams() + const { path } = useRouteMatch() + const { data, loading, error } = useQuery(DAO_QUERY, { + variables: { + name: daoAddress, + }, + client: chainId === 4 ? rinkebyClient : mainnetClient, + }) + + if (loading) { + return

Loading DAO data...

+ } + + if (error) { + console.error(error) + return

Error fetching DAO data.

+ } + + if (!data.registryEntry) { + return

DAO not found.

+ } + + return ( + + + + + + + + + + + +

Route not found :(

+
+
+ ) +} diff --git a/yarn.lock b/yarn.lock index 60bdced45..d06245bad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8615,6 +8615,11 @@ date-fns@2.0.0-alpha.22: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.22.tgz#c65f5dd7e959c59f8ea5591e458afc76a2a0d8d0" integrity sha512-ovlz0I0OFhyn9bgTfqFAUvJvo8VS9mUuOxZ6cwKwBjSx42lMpXPGolcINYZoTNAG2M4YJ3QoApiQQljQEuy/Uw== +date-fns@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" + integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== + dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -8628,11 +8633,6 @@ dateformat@~1.0.4-1.2.3: get-stdin "^4.0.1" meow "^3.3.0" -dayjs@^1.8.35: - version "1.9.4" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.4.tgz#fcde984e227f4296f04e7b05720adad2e1071f1b" - integrity sha512-ABSF3alrldf7nM9sQ2U+Ln67NRwmzlLOqG7kK03kck0mw3wlSSEKv/XhKGGxUjQcS57QeiCyNdrFgtj9nWlrng== - death@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318"