Skip to content

Commit

Permalink
Merge pull request #2 from 0xsequence/simple-updates
Browse files Browse the repository at this point in the history
Update wallet fixes
  • Loading branch information
Agusx1211 authored Apr 9, 2024
2 parents dc9a38d + 8bb09c2 commit f1ccab0
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 106 deletions.
67 changes: 67 additions & 0 deletions src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,70 @@ export function toUpperFirst(s: string) {
if (!s || s.length === 0) return s
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()
}

export type ParsedFunctionSelector = { name: string, inputs: { name?: string, type: string }[] }

export function parsedToAbi(parsed: ParsedFunctionSelector) {
return [{
constant: false,
outputs: [],
name: parsed.name,
inputs: parsed.inputs.map((input, index) => {
return {
name: input.name || index + 'Arg',
type: input.type
}
}),
payable: false,
stateMutability: 'nonpayable',
type: 'function',
}]
}

export function parseFunctionSelector(selector: string): ParsedFunctionSelector {
const [name, inputs] = selector.split('(')
if (!inputs) {
throw new Error('Missing input arguments')
}

const inputTypes = inputs.slice(0, -1).split(',').filter((input) => input.trim() !== '')

const res = {
name,
inputs: inputTypes.map((input) => {
// Name is optional
const parts = input.split(' ')
if (parts.length === 1) {
return { type: parts[0] }
}

const trimmed0 = parts[0].trim()
const trimmed1 = parts[1].trim()

if (trimmed0 === '') {
return { type: trimmed1 }
}

if (trimmed1 === '') {
return { type: trimmed0 }
}

return {
name: trimmed0,
type: trimmed1
}
})
}

if (res.name === '') {
throw new Error('Empty function name')
}

for (const input of res.inputs) {
if (!/^(address|uint\d+|int\d+|bool|bytes\d*|string)$/.test(input.type)) {
throw new Error('Invalid type: ' + input.type)
}
}

return res
}
16 changes: 12 additions & 4 deletions src/components/Actions.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { Table } from "@mantine/core";
import { Text, Table, Box } from "@mantine/core";
import { TransactionsEntry } from "../stores/db/Transactions";

export function Actions(props: { transaction: TransactionsEntry}) {
const rows = props.transaction.transactions.map((element, i) => (
<Table.Tr key={i}>
<Table.Tr key={i} style={{ verticalAlign: "text-top" }}>
<Table.Td>{element.to}</Table.Td>
<Table.Td>{element.value || "0"}</Table.Td>
<Table.Td>{element.data || "0x"}</Table.Td>
<Table.Td>
<Box>
<Text style={{ lineBreak: "anywhere"}}>
<p>
{element.data || "0x"}
</p>
</Text>
</Box>
</Table.Td>
<Table.Td>{element.gasLimit || element.revertOnError ? "Auto" : "0"}</Table.Td>
<Table.Td>{element.revertOnError ? "Yes" : "No"}</Table.Td>
<Table.Td>{element.delegateCall ? "Yes" : "No"}</Table.Td>
Expand All @@ -28,4 +36,4 @@ export function Actions(props: { transaction: TransactionsEntry}) {
<Table.Tbody>{rows}</Table.Tbody>
</Table>
)
}
}
141 changes: 141 additions & 0 deletions src/components/ActionsDecoded.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { Box, Loader, Space, Title } from "@mantine/core";
import { FlatTransaction, TransactionsEntry } from "../stores/db/Transactions";
import { AccountStatus } from "@0xsequence/account";
import { ethers } from "ethers";
import useFunctionSignature, { FunctionSignature } from "../stores/FunctionSignature";
import { decodeFunctionData } from "viem";
import { ParsedFunctionSelector, parseFunctionSelector, parsedToAbi } from "../Utils";
import { UpdateDiff } from "./UpdateDiff";
import { useWalletConfig } from "../stores/Sequence";
import { universal } from "@0xsequence/core";

export function ActionArguments(props: { action: FlatTransaction, signature: FunctionSignature }) {
let parsed: ParsedFunctionSelector | undefined = undefined
let error: string | undefined = undefined

try {
parsed = parseFunctionSelector(props.signature.text_signature)
} catch (e) {
error = (e as Error).message || "Unknown error"
}

const decoded = parsed && decodeFunctionData({
abi: parsedToAbi(parsed),
data: (props.action.data || "0x") as `0x${string}`
})

return <>
{decoded && <Box>
{decoded.args?.map((input, i) => <Box key={i}>
- <b>{parsed?.inputs[i]?.name || i.toString()}</b>: {(input as any).toString()}
</Box>)}
</Box>}
{error && <Box>
{error}
</Box>}
</>
}

export function ActionUpdateImageHash(props: { action: FlatTransaction, state: { loading: boolean, state?: AccountStatus }}) {
const { action, state } = props

const imageHash = ethers.utils.arrayify(action.data || "0x").slice(4)
const toConfig = useWalletConfig(ethers.utils.hexlify(imageHash))
const fromConfig = state.state?.onChain?.config

const isStale = (() => {
if (!toConfig.config) return
if (!fromConfig) return

const coderFrom = universal.genericCoderFor(fromConfig.version)
const coderTo = universal.genericCoderFor(toConfig.config.version)

return coderFrom.config.checkpointOf(fromConfig).toNumber() >= coderTo.config.checkpointOf(toConfig.config).toNumber()
})()

return <Box>
<b>Notice! This transaction settles pending commits</b>
{isStale && <Box mt="md">
(In Development) Already executed transactions will show invalid config changes
</Box>}
{(state.loading || toConfig.loading) && <Box>Loading...</Box>}
{!state.loading && !state.state && "Error loading account status"}
{(toConfig.error || (!toConfig.loading && !toConfig.config)) && <Box style={{ color: "red" }}>
<b>WARNING! WARNING! WARNING! Committing to unknown config, please contact Sequence Support</b>
</Box>}
{fromConfig && toConfig.config && <Box mt="md"><UpdateDiff from={fromConfig} to={toConfig.config} noAlert /></Box>}
</Box>
}

const updateImageHashSelector = "29561426"

const warningMethods = [{
name: "Self Execute",
selector: "61c2926c"
}, {
name: "Update Code",
selector: "025b22bc"
}, {
name: "Update ImageHash + IPFS",
selector: "d0748f71"
}, {
name: "Set extra ImageHash",
selector: "4598154f"
}, {
name: "Add hook",
selector: "b93ea7ad"
}]

export function ActionDecode(props: { action: FlatTransaction, state: { loading: boolean, state?: AccountStatus }}) {
const { action, state } = props

const hasCall = action.data && action.data.length >= 10
const hasValue = action.value && action.value !== "0"

const data = action.data?.toLowerCase() || "0x"
const updateImageHash = data.startsWith(updateImageHashSelector) || data.startsWith("0x" + updateImageHashSelector)
const decoded = useFunctionSignature(action.data?.slice(0, 10) || "0x")

const warning = warningMethods.find(w => data.startsWith(w.selector) || data.startsWith("0x" + w.selector))

return <Box m="md" style={{
fontSize: '12px', fontFamily: 'Courier New, monospace'
}}>
{warning && <Box mt="md" style={{ color: "red" }}>
WARNING! Transaction is a <b>{warning.name}</b> method, this method is not natively supported by the Enchanter. Be careful!<br/>
<b>This transaction may be used to take control of the Wallet.</b>
</Box>}
{!hasValue && !hasCall && <Box mt="md">
Calls {action.to} without value or common data
</Box>}
{hasValue && <Box mt="md">
Transfer {ethers.utils.formatEther(action.value || 0)} NATIVE to {action.to}
</Box>}
{hasCall && <Box mt="md">
{decoded.loading && <Loader />}
{decoded.error && <Box>
{decoded.error}
</Box>}
{decoded.signature && <Box mt="md">
Calling <b>{decoded.signature.text_signature}</b> on {action.to}
<ActionArguments action={action} signature={decoded.signature} />
</Box>}
</Box>}
{updateImageHash && <Box mt="md">
<ActionUpdateImageHash action={action} state={state} />
</Box>}
</Box>
}

export function ActionsDecoded(props: { transaction: TransactionsEntry, state: { loading: boolean, state?: AccountStatus }}) {
const { transaction, state } = props

return <>
<Space h="md" />
{transaction.transactions.map((action, i) => <Box>
<Title order={6}>Action #{i}</Title>
<ActionDecode key={i} action={action} state={state} />
</Box>
)}
</>
}
5 changes: 3 additions & 2 deletions src/components/UpdateDiff.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ function getChanges(

export function UpdateDiff(props: {
from: UpdateSnapshot | commons.config.Config,
to: UpdateSnapshot | commons.config.Config
to: UpdateSnapshot | commons.config.Config,
noAlert?: boolean
}) {
const from = toUpdateSnapshot(props.from)
const to = toUpdateSnapshot(props.to)
Expand Down Expand Up @@ -131,7 +132,7 @@ export function UpdateDiff(props: {
)) }
</Box> }
</Box>
{ delRows.length > 0 && <Alert variant="light" color="red" title="Removing Signers" icon={<IconFlag/>}>
{ !props.noAlert && delRows.length > 0 && <Alert variant="light" color="red" title="Removing Signers" icon={<IconFlag/>}>
Revoking weight only takes effect after sending a transaction on-chain, networks will be updated independently.
</Alert>}
<Divider my="md" />
Expand Down
Loading

0 comments on commit f1ccab0

Please sign in to comment.