From fef8bbae1d14defd610da320f66004084b1dfbd5 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 24 Sep 2021 01:07:24 -0300 Subject: [PATCH 01/14] Add 4bytes signatures with param names to docker image --- .dockerignore | 1 + Dockerfile | 1 + nginx.conf | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/.dockerignore b/.dockerignore index df120e1..ee24273 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,5 +2,6 @@ node_modules 4bytes !4bytes/signatures +!4bytes/with_parameter_names trustwallet !trustwallet/blockchains/ethereum/assets diff --git a/Dockerfile b/Dockerfile index 7583d23..223578a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ RUN find . -name logo.png | parallel magick convert {} -filter Lanczos -resize 3 FROM nginx:1.21.1-alpine RUN apk add jq COPY 4bytes/signatures /usr/share/nginx/html/signatures/ +COPY 4bytes/with_parameter_names /usr/share/nginx/html/with_parameter_names/ COPY --from=logobuilder /assets /usr/share/nginx/html/assets/ COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /otterscan-build/build /usr/share/nginx/html/ diff --git a/nginx.conf b/nginx.conf index 55189ba..8e55be6 100644 --- a/nginx.conf +++ b/nginx.conf @@ -43,6 +43,40 @@ server { } } + location /with_parameter_names { + root /usr/share/nginx/html; + expires 30d; + + # Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/ + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + # + # Om nom nom cookies + # + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; + + # + # Custom headers and headers various browsers *should* be OK with but aren't + # + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + + # + # Tell client that this pre-flight info is valid for 20 days + # + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + if ($request_method = 'GET') { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' always; + } + } + location /assets { root /usr/share/nginx/html; expires 30d; From ccb33b463748d7b4353653c403c4b6f0b2013c23 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 06:51:50 -0300 Subject: [PATCH 02/14] First working version of tx input decoding from 4bytes DB; if there is 4bytes AND sourcify info, prioritize sourcify for completeness --- src/transaction/Details.tsx | 31 ++++++++++-- src/use4Bytes.ts | 97 +++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index bea1587..57a0233 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -1,5 +1,9 @@ import React, { useMemo } from "react"; -import { TransactionDescription } from "@ethersproject/abi"; +import { + TransactionDescription, + Fragment, + Interface, +} from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { toUtf8String } from "@ethersproject/strings"; import { Tab } from "@headlessui/react"; @@ -32,6 +36,7 @@ import RelativePosition from "../components/RelativePosition"; import PercentagePosition from "../components/PercentagePosition"; import ModeTab from "../components/ModeTab"; import DecodedParamsTable from "./DecodedParamsTable"; +import { rawInputTo4Bytes, use4BytesFull } from "../use4Bytes"; type DetailsProps = { txData: TransactionData; @@ -62,6 +67,20 @@ const Details: React.FC = ({ } }, [txData]); + const fourBytes = rawInputTo4Bytes(txData.data); + const fourBytesEntry = use4BytesFull(fourBytes); + const fourBytesTxDesc = useMemo(() => { + if (!txData || !fourBytesEntry?.signatureWithoutParamNames) { + return undefined; + } + const sig = fourBytesEntry?.signatureWithoutParamNames; + const functionFragment = Fragment.fromString(`function ${sig}`); + const intf = new Interface([functionFragment]); + return intf.parseTransaction({ data: txData.data, value: txData.value }); + }, [txData, fourBytesEntry]); + + const resolvedTxDesc = txDesc ?? fourBytesTxDesc; + return ( @@ -322,14 +341,16 @@ const Details: React.FC = ({ - {txDesc === undefined ? ( + {fourBytes === "0x" ? ( + <>No parameters + ) : resolvedTxDesc === undefined ? ( <>Waiting for data... - ) : txDesc === null ? ( + ) : resolvedTxDesc === null ? ( <>No decoded data ) : ( )} diff --git a/src/use4Bytes.ts b/src/use4Bytes.ts index dad9f75..5f837e6 100644 --- a/src/use4Bytes.ts +++ b/src/use4Bytes.ts @@ -4,6 +4,21 @@ import { fourBytesURL } from "./url"; const cache = new Map(); +export type FourBytesEntry = { + name: string; + signatureWithoutParamNames: string | undefined; + signatures: string[] | undefined; +}; + +const simpleTransfer: FourBytesEntry = { + name: "Transfer", + signatureWithoutParamNames: undefined, + signatures: undefined, +}; + +const fullCache = new Map(); + +// TODO: deprecate and remove export const use4Bytes = (rawFourBytes: string) => { const runtime = useContext(RuntimeContext); const assetsURLPrefix = runtime.config?.assetsURLPrefix; @@ -68,3 +83,85 @@ export const use4Bytes = (rawFourBytes: string) => { return name; }; + +export const rawInputTo4Bytes = (rawInput: string) => rawInput.substr(0, 10); + +/** + * Extract 4bytes DB info + * + * @param rawFourBytes an hex string containing the 4bytes signature in the "0xXXXXXXXX" format. + */ +export const use4BytesFull = ( + rawFourBytes: string +): FourBytesEntry | null | undefined => { + if (rawFourBytes !== "0x") { + if (rawFourBytes.length !== 10 || !rawFourBytes.startsWith("0x")) { + throw new Error( + `rawFourBytes must contain a 4 bytes hex method signature starting with 0x; received value: "${rawFourBytes}"` + ); + } + } + + const runtime = useContext(RuntimeContext); + const assetsURLPrefix = runtime.config?.assetsURLPrefix; + + const fourBytes = rawFourBytes.slice(2); + const [entry, setEntry] = useState( + fullCache.get(fourBytes) + ); + useEffect(() => { + if (assetsURLPrefix === undefined) { + return; + } + if (fourBytes === "") { + return; + } + + const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes); + fetch(signatureURL) + .then(async (res) => { + if (!res.ok) { + console.error(`Signature does not exist in 4bytes DB: ${fourBytes}`); + fullCache.set(fourBytes, null); + setEntry(null); + return; + } + + const sig = await res.text(); + const cut = sig.indexOf("("); + let method = sig.slice(0, cut); + method = method.charAt(0).toUpperCase() + method.slice(1); + + const entry: FourBytesEntry = { + name: method, + signatureWithoutParamNames: sig, + signatures: undefined, + }; + setEntry(entry); + fullCache.set(fourBytes, entry); + }) + .catch((err) => { + console.error(`Couldn't fetch signature URL ${signatureURL}`, err); + setEntry(null); + fullCache.set(fourBytes, null); + }); + }, [fourBytes, assetsURLPrefix]); + + if (rawFourBytes === "0x") { + return simpleTransfer; + } + if (assetsURLPrefix === undefined) { + return undefined; + } + + // Try to resolve 4bytes name + if (entry === null || entry === undefined) { + return entry; + } + + // Simulates LRU + // TODO: implement LRU purging + fullCache.delete(fourBytes); + fullCache.set(fourBytes, entry); + return entry; +}; From b57518ba964bcfa7fbcab9e5df3bdd9d18814972 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 14:29:09 -0300 Subject: [PATCH 03/14] Use common function --- src/components/MethodName.tsx | 4 ++-- src/use4Bytes.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MethodName.tsx b/src/components/MethodName.tsx index 1ed596d..ff45cae 100644 --- a/src/components/MethodName.tsx +++ b/src/components/MethodName.tsx @@ -1,12 +1,12 @@ import React from "react"; -import { use4Bytes } from "../use4Bytes"; +import { rawInputTo4Bytes, use4Bytes } from "../use4Bytes"; type MethodNameProps = { data: string; }; const MethodName: React.FC = ({ data }) => { - const rawFourBytes = data.slice(0, 10); + const rawFourBytes = rawInputTo4Bytes(data); const methodName = use4Bytes(rawFourBytes); const isSimpleTransfer = data === "0x"; const methodTitle = isSimpleTransfer diff --git a/src/use4Bytes.ts b/src/use4Bytes.ts index 5f837e6..a1ab465 100644 --- a/src/use4Bytes.ts +++ b/src/use4Bytes.ts @@ -84,7 +84,7 @@ export const use4Bytes = (rawFourBytes: string) => { return name; }; -export const rawInputTo4Bytes = (rawInput: string) => rawInput.substr(0, 10); +export const rawInputTo4Bytes = (rawInput: string) => rawInput.slice(0, 10); /** * Extract 4bytes DB info From e4fa639e691d329f182116da91ccf73943a4e9e0 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 14:45:12 -0300 Subject: [PATCH 04/14] Add warning for input decoded from 4bytes signatures wuthout param names --- src/transaction/DecodedParamRow.tsx | 5 +++-- src/transaction/DecodedParamsTable.tsx | 9 ++++++++- src/transaction/Details.tsx | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/transaction/DecodedParamRow.tsx b/src/transaction/DecodedParamRow.tsx index 9d9bcf8..5678ac8 100644 --- a/src/transaction/DecodedParamRow.tsx +++ b/src/transaction/DecodedParamRow.tsx @@ -25,7 +25,7 @@ const DecodedParamRow: React.FC = ({ {prefix && {prefix}} - {paramType.name}{" "} + {paramType.name ?? param_{i}}{" "} {i !== undefined && ( ({i}) )} @@ -65,7 +65,8 @@ const DecodedParamRow: React.FC = ({ {paramType.baseType === "tuple" && r.map((e: any, idx: number) => ( - = ({ args, paramTypes, txData, + hasParamNames = true, }) => ( - +
+ {!hasParamNames && ( + + + + )} {args.map((r, i) => ( diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 57a0233..27bd9b1 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -352,6 +352,7 @@ const Details: React.FC = ({ args={resolvedTxDesc.args} paramTypes={resolvedTxDesc.functionFragment.inputs} txData={txData} + hasParamNames={resolvedTxDesc === txDesc} /> )} From f612e4791a1ec589acd9f247bc0b62d518b4a353 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 15:11:57 -0300 Subject: [PATCH 05/14] Better display of array tx input parameters --- src/transaction/DecodedParamRow.tsx | 34 ++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/transaction/DecodedParamRow.tsx b/src/transaction/DecodedParamRow.tsx index 5678ac8..aec65ce 100644 --- a/src/transaction/DecodedParamRow.tsx +++ b/src/transaction/DecodedParamRow.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { ReactNode } from "react"; import AddressHighlighter from "../components/AddressHighlighter"; import DecoratedAddressLink from "../components/DecoratedAddressLink"; import Copy from "../components/Copy"; @@ -6,11 +6,12 @@ import { ParamType } from "@ethersproject/abi"; import { TransactionData } from "../types"; type DecodedParamRowProps = { - prefix?: string; + prefix?: ReactNode; i?: number | undefined; r: any; paramType: ParamType; txData: TransactionData; + arrayElem?: number | undefined; }; const DecodedParamRow: React.FC = ({ @@ -19,15 +20,24 @@ const DecodedParamRow: React.FC = ({ r, paramType, txData, + arrayElem, }) => { return ( <> @@ -56,7 +66,8 @@ const DecodedParamRow: React.FC = ({ {r.toString().length / 2 - 1 === 1 ? "byte" : "bytes"} - ) : paramType.baseType === "tuple" ? ( + ) : paramType.baseType === "tuple" || + paramType.baseType === "array" ? ( <> ) : ( r.toString() @@ -73,6 +84,17 @@ const DecodedParamRow: React.FC = ({ txData={txData} /> ))} + {paramType.baseType === "array" && + r.map((e: any, idx: number) => ( + param_{i}} + r={e} + paramType={paramType.arrayChildren} + txData={txData} + arrayElem={idx} + /> + ))} ); }; From 1b127b84a248dc92fa63b111e32fb285cc0662d6 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 17:19:49 -0300 Subject: [PATCH 06/14] Move into subpackage --- src/transaction/Details.tsx | 2 +- src/transaction/LogEntry.tsx | 4 ++-- src/transaction/{ => decoder}/DecodedLogSignature.tsx | 0 src/transaction/{ => decoder}/DecodedParamRow.tsx | 11 ++++++----- src/transaction/{ => decoder}/DecodedParamsTable.tsx | 6 ++++-- 5 files changed, 13 insertions(+), 10 deletions(-) rename src/transaction/{ => decoder}/DecodedLogSignature.tsx (100%) rename src/transaction/{ => decoder}/DecodedParamRow.tsx (91%) rename src/transaction/{ => decoder}/DecodedParamsTable.tsx (88%) diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 27bd9b1..5fb100a 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -35,7 +35,7 @@ import ExternalLink from "../components/ExternalLink"; import RelativePosition from "../components/RelativePosition"; import PercentagePosition from "../components/PercentagePosition"; import ModeTab from "../components/ModeTab"; -import DecodedParamsTable from "./DecodedParamsTable"; +import DecodedParamsTable from "./decoder/DecodedParamsTable"; import { rawInputTo4Bytes, use4BytesFull } from "../use4Bytes"; type DetailsProps = { diff --git a/src/transaction/LogEntry.tsx b/src/transaction/LogEntry.tsx index 1d4e85f..e06c558 100644 --- a/src/transaction/LogEntry.tsx +++ b/src/transaction/LogEntry.tsx @@ -6,8 +6,8 @@ import AddressHighlighter from "../components/AddressHighlighter"; import DecoratedAddressLink from "../components/DecoratedAddressLink"; import Copy from "../components/Copy"; import ModeTab from "../components/ModeTab"; -import DecodedParamsTable from "./DecodedParamsTable"; -import DecodedLogSignature from "./DecodedLogSignature"; +import DecodedParamsTable from "./decoder/DecodedParamsTable"; +import DecodedLogSignature from "./decoder/DecodedLogSignature"; import { TransactionData } from "../types"; type LogEntryProps = { diff --git a/src/transaction/DecodedLogSignature.tsx b/src/transaction/decoder/DecodedLogSignature.tsx similarity index 100% rename from src/transaction/DecodedLogSignature.tsx rename to src/transaction/decoder/DecodedLogSignature.tsx diff --git a/src/transaction/DecodedParamRow.tsx b/src/transaction/decoder/DecodedParamRow.tsx similarity index 91% rename from src/transaction/DecodedParamRow.tsx rename to src/transaction/decoder/DecodedParamRow.tsx index aec65ce..79bc6d5 100644 --- a/src/transaction/DecodedParamRow.tsx +++ b/src/transaction/decoder/DecodedParamRow.tsx @@ -1,9 +1,9 @@ import React, { ReactNode } from "react"; -import AddressHighlighter from "../components/AddressHighlighter"; -import DecoratedAddressLink from "../components/DecoratedAddressLink"; -import Copy from "../components/Copy"; import { ParamType } from "@ethersproject/abi"; -import { TransactionData } from "../types"; +import AddressHighlighter from "../../components/AddressHighlighter"; +import DecoratedAddressLink from "../../components/DecoratedAddressLink"; +import Copy from "../../components/Copy"; +import { TransactionData } from "../../types"; type DecodedParamRowProps = { prefix?: ReactNode; @@ -28,7 +28,8 @@ const DecodedParamRow: React.FC = ({ {!hasParamNames && ( - + )} From ab7ac37ca07157aa196433d2e1239dcf93f18528 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 17:28:49 -0300 Subject: [PATCH 07/14] Extract decoders --- src/transaction/decoder/AddressDecoder.tsx | 26 ++++ src/transaction/decoder/BooleanDecoder.tsx | 13 ++ src/transaction/decoder/BytesDecoder.tsx | 17 +++ src/transaction/decoder/DecodedParamRow.tsx | 141 +++++++++----------- 4 files changed, 116 insertions(+), 81 deletions(-) create mode 100644 src/transaction/decoder/AddressDecoder.tsx create mode 100644 src/transaction/decoder/BooleanDecoder.tsx create mode 100644 src/transaction/decoder/BytesDecoder.tsx diff --git a/src/transaction/decoder/AddressDecoder.tsx b/src/transaction/decoder/AddressDecoder.tsx new file mode 100644 index 0000000..ee6369a --- /dev/null +++ b/src/transaction/decoder/AddressDecoder.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import AddressHighlighter from "../../components/AddressHighlighter"; +import DecoratedAddressLink from "../../components/DecoratedAddressLink"; +import Copy from "../../components/Copy"; +import { TransactionData } from "../../types"; + +type AddressDecoderProps = { + r: any; + txData: TransactionData; +}; + +const AddressDecoder: React.FC = ({ r, txData }) => ( +
+ + + + +
+); + +export default React.memo(AddressDecoder); diff --git a/src/transaction/decoder/BooleanDecoder.tsx b/src/transaction/decoder/BooleanDecoder.tsx new file mode 100644 index 0000000..259f4a9 --- /dev/null +++ b/src/transaction/decoder/BooleanDecoder.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +type BooleanDecoderProps = { + r: any; +}; + +const BooleanDecoder: React.FC = ({ r }) => ( + + {r.toString()} + +); + +export default React.memo(BooleanDecoder); diff --git a/src/transaction/decoder/BytesDecoder.tsx b/src/transaction/decoder/BytesDecoder.tsx new file mode 100644 index 0000000..f7de95f --- /dev/null +++ b/src/transaction/decoder/BytesDecoder.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +type BytesDecoderProps = { + r: any; +}; + +const BytesDecoder: React.FC = ({ r }) => ( + + {r.toString()}{" "} + + {r.toString().length / 2 - 1}{" "} + {r.toString().length / 2 - 1 === 1 ? "byte" : "bytes"} + + +); + +export default React.memo(BytesDecoder); diff --git a/src/transaction/decoder/DecodedParamRow.tsx b/src/transaction/decoder/DecodedParamRow.tsx index 79bc6d5..493f6b3 100644 --- a/src/transaction/decoder/DecodedParamRow.tsx +++ b/src/transaction/decoder/DecodedParamRow.tsx @@ -1,8 +1,8 @@ import React, { ReactNode } from "react"; import { ParamType } from "@ethersproject/abi"; -import AddressHighlighter from "../../components/AddressHighlighter"; -import DecoratedAddressLink from "../../components/DecoratedAddressLink"; -import Copy from "../../components/Copy"; +import AddressDecoder from "./AddressDecoder"; +import BooleanDecoder from "./BooleanDecoder"; +import BytesDecoder from "./BytesDecoder"; import { TransactionData } from "../../types"; type DecodedParamRowProps = { @@ -21,83 +21,62 @@ const DecodedParamRow: React.FC = ({ paramType, txData, arrayElem, -}) => { - return ( - <> -
- - - - - {paramType.baseType === "tuple" && - r.map((e: any, idx: number) => ( - - ))} - {paramType.baseType === "array" && - r.map((e: any, idx: number) => ( - param_{i}} - r={e} - paramType={paramType.arrayChildren} - txData={txData} - arrayElem={idx} - /> - ))} - - ); -}; +}) => ( + <> + + + + + + {paramType.baseType === "tuple" && + r.map((e: any, idx: number) => ( + + ))} + {paramType.baseType === "array" && + r.map((e: any, idx: number) => ( + param_{i}} + r={e} + paramType={paramType.arrayChildren} + txData={txData} + arrayElem={idx} + /> + ))} + +); export default React.memo(DecodedParamRow); From a1da1d532b8e54d82e38ed2d3a1593bc851e4413 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 18:40:00 -0300 Subject: [PATCH 08/14] Add uint256 decoder with display mode toggler --- src/transaction/decoder/DecodedParamRow.tsx | 5 +- src/transaction/decoder/Uint256Decoder.tsx | 58 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/transaction/decoder/Uint256Decoder.tsx diff --git a/src/transaction/decoder/DecodedParamRow.tsx b/src/transaction/decoder/DecodedParamRow.tsx index 493f6b3..f0f0013 100644 --- a/src/transaction/decoder/DecodedParamRow.tsx +++ b/src/transaction/decoder/DecodedParamRow.tsx @@ -1,5 +1,6 @@ import React, { ReactNode } from "react"; import { ParamType } from "@ethersproject/abi"; +import Uint256Decoder from "./Uint256Decoder"; import AddressDecoder from "./AddressDecoder"; import BooleanDecoder from "./BooleanDecoder"; import BytesDecoder from "./BytesDecoder"; @@ -42,7 +43,9 @@ const DecodedParamRow: React.FC = ({ )} diff --git a/src/use4Bytes.ts b/src/use4Bytes.ts index 622f327..9fe9252 100644 --- a/src/use4Bytes.ts +++ b/src/use4Bytes.ts @@ -4,14 +4,12 @@ import { fourBytesURL } from "./url"; export type FourBytesEntry = { name: string; - signatureWithoutParamNames: string | undefined; - signatures: string[] | undefined; + signature: string | undefined; }; const simpleTransfer: FourBytesEntry = { name: "Transfer", - signatureWithoutParamNames: undefined, - signatures: undefined, + signature: undefined, }; const fullCache = new Map(); @@ -68,8 +66,7 @@ export const use4Bytes = ( const entry: FourBytesEntry = { name: method, - signatureWithoutParamNames: sig, - signatures: undefined, + signature: sig, }; setEntry(entry); fullCache.set(fourBytes, entry);
@@ -23,6 +25,11 @@ const DecodedParamsTable: React.FC = ({ type value
Parameter names are not available.
{prefix && {prefix}} - {paramType.name ?? param_{i}}{" "} - {i !== undefined && ( - ({i}) + {arrayElem !== undefined ? ( + {" "} + [{arrayElem}] + + ) : ( + <> + {paramType.name ?? param_{i}}{" "} + {i !== undefined && ( + ({i}) + )} + )} {paramType.type} {prefix && {prefix}} {arrayElem !== undefined ? ( - {" "} + + {" "} [{arrayElem}] ) : ( diff --git a/src/transaction/DecodedParamsTable.tsx b/src/transaction/decoder/DecodedParamsTable.tsx similarity index 88% rename from src/transaction/DecodedParamsTable.tsx rename to src/transaction/decoder/DecodedParamsTable.tsx index 7cc0170..6f550f4 100644 --- a/src/transaction/DecodedParamsTable.tsx +++ b/src/transaction/decoder/DecodedParamsTable.tsx @@ -1,7 +1,7 @@ import React from "react"; import { ParamType, Result } from "@ethersproject/abi"; import DecodedParamRow from "./DecodedParamRow"; -import { TransactionData } from "../types"; +import { TransactionData } from "../../types"; type DecodedParamsTableProps = { args: Result; @@ -27,7 +27,9 @@ const DecodedParamsTable: React.FC = ({
Parameter names are not available. + Parameter names are not available. +
- {prefix && {prefix}} - {arrayElem !== undefined ? ( - - {" "} - [{arrayElem}] - - ) : ( - <> - {paramType.name ?? param_{i}}{" "} - {i !== undefined && ( - ({i}) - )} - - )} - {paramType.type} - {paramType.baseType === "address" ? ( -
- - - - -
- ) : paramType.baseType === "bool" ? ( - - {r.toString()} - - ) : paramType.baseType === "bytes" ? ( - - {r.toString()}{" "} - - {r.toString().length / 2 - 1}{" "} - {r.toString().length / 2 - 1 === 1 ? "byte" : "bytes"} - - - ) : paramType.baseType === "tuple" || - paramType.baseType === "array" ? ( - <> - ) : ( - r.toString() - )} -
+ {prefix && {prefix}} + {arrayElem !== undefined ? ( + + {" "} + [{arrayElem}] + + ) : ( + <> + {paramType.name ?? param_{i}}{" "} + {i !== undefined && ( + ({i}) + )} + + )} + {paramType.type} + {paramType.baseType === "address" ? ( + + ) : paramType.baseType === "bool" ? ( + + ) : paramType.baseType === "bytes" ? ( + + ) : paramType.baseType === "tuple" || paramType.baseType === "array" ? ( + <> + ) : ( + r.toString() + )} +
{paramType.type} - {paramType.baseType === "address" ? ( + {paramType.baseType === "uint256" ? ( + + ) : paramType.baseType === "address" ? ( ) : paramType.baseType === "bool" ? ( diff --git a/src/transaction/decoder/Uint256Decoder.tsx b/src/transaction/decoder/Uint256Decoder.tsx new file mode 100644 index 0000000..65b50c1 --- /dev/null +++ b/src/transaction/decoder/Uint256Decoder.tsx @@ -0,0 +1,58 @@ +import React, { useState } from "react"; +import { hexlify } from "@ethersproject/bytes"; +import { commify, formatEther } from "@ethersproject/units"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSync } from "@fortawesome/free-solid-svg-icons/faSync"; + +type Uint256DecoderProps = { + r: any; +}; + +enum DisplayMode { + RAW, + HEX, + EIGHTEEN_DECIMALS, +} + +const Uint256Decoder: React.FC = ({ r }) => { + const [displayMode, setDisplayMode] = useState( + DisplayMode.EIGHTEEN_DECIMALS + ); + + const toggleModes = () => { + setDisplayMode( + displayMode === DisplayMode.EIGHTEEN_DECIMALS ? 0 : displayMode + 1 + ); + }; + + return ( +
+ + + {displayMode === DisplayMode.RAW ? ( + <>{commify(r.toString())} + ) : displayMode === DisplayMode.HEX ? ( + <>{hexlify(r)} + ) : ( + <>{commify(formatEther(r))} + )} + +
+ ); +}; + +export default React.memo(Uint256Decoder); From 22b9c5b687a9f0ddf7977dac90eeb19f594a2aae Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 18:57:07 -0300 Subject: [PATCH 09/14] Small adjustments to toggling and formatting --- src/transaction/decoder/Uint256Decoder.tsx | 31 +++++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/transaction/decoder/Uint256Decoder.tsx b/src/transaction/decoder/Uint256Decoder.tsx index 65b50c1..7d4a003 100644 --- a/src/transaction/decoder/Uint256Decoder.tsx +++ b/src/transaction/decoder/Uint256Decoder.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; -import { hexlify } from "@ethersproject/bytes"; +import { BigNumber } from "@ethersproject/bignumber"; +import { hexlify, hexZeroPad } from "@ethersproject/bytes"; import { commify, formatEther } from "@ethersproject/units"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSync } from "@fortawesome/free-solid-svg-icons/faSync"; @@ -10,19 +11,29 @@ type Uint256DecoderProps = { enum DisplayMode { RAW, - HEX, EIGHTEEN_DECIMALS, + HEX, + _LAST, } +const VERY_BIG_NUMBER = BigNumber.from(10).pow(BigNumber.from(36)); + +const initDisplayMode = (r: any): DisplayMode => { + const n = BigNumber.from(r); + if (n.gte(VERY_BIG_NUMBER)) { + return DisplayMode.HEX; + } + return DisplayMode.RAW; +}; + const Uint256Decoder: React.FC = ({ r }) => { const [displayMode, setDisplayMode] = useState( - DisplayMode.EIGHTEEN_DECIMALS + initDisplayMode(r) ); const toggleModes = () => { - setDisplayMode( - displayMode === DisplayMode.EIGHTEEN_DECIMALS ? 0 : displayMode + 1 - ); + const next = displayMode + 1; + setDisplayMode(next === DisplayMode._LAST ? 0 : next); }; return ( @@ -36,17 +47,17 @@ const Uint256Decoder: React.FC = ({ r }) => { {displayMode === DisplayMode.RAW - ? "Raw number" + ? "Raw" : displayMode === DisplayMode.HEX - ? "Hex number" - : "18 decimals"} + ? "Hex" + : "18 dec"} {displayMode === DisplayMode.RAW ? ( <>{commify(r.toString())} ) : displayMode === DisplayMode.HEX ? ( - <>{hexlify(r)} + <>{hexZeroPad(hexlify(r), 32)} ) : ( <>{commify(formatEther(r))} )} From 9236bfcb2f98706fbb4c7b0c7c784d71580d3b96 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 25 Sep 2021 19:37:14 -0300 Subject: [PATCH 10/14] Fix anonymous tuples --- src/transaction/decoder/DecodedParamRow.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/transaction/decoder/DecodedParamRow.tsx b/src/transaction/decoder/DecodedParamRow.tsx index f0f0013..988e536 100644 --- a/src/transaction/decoder/DecodedParamRow.tsx +++ b/src/transaction/decoder/DecodedParamRow.tsx @@ -62,7 +62,14 @@ const DecodedParamRow: React.FC = ({ r.map((e: any, idx: number) => ( param_{i}. + ) + } + i={idx} r={e} paramType={paramType.components[idx]} txData={txData} From 9285d4dedff9b4aa7687b001866bb015fa84daf5 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Sep 2021 00:49:23 -0300 Subject: [PATCH 11/14] Fix not known metadata scenario --- src/TokenTransferItem.tsx | 14 ++++++-------- src/transaction/Details.tsx | 2 +- src/types.ts | 4 +--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/TokenTransferItem.tsx b/src/TokenTransferItem.tsx index c9f40a2..083f7f0 100644 --- a/src/TokenTransferItem.tsx +++ b/src/TokenTransferItem.tsx @@ -7,7 +7,7 @@ import DecoratedAddressLink from "./components/DecoratedAddressLink"; import FormattedBalance from "./components/FormattedBalance"; import { AddressContext, - TokenMetas, + TokenMeta, TokenTransfer, TransactionData, } from "./types"; @@ -15,14 +15,14 @@ import { type TokenTransferItemProps = { t: TokenTransfer; txData: TransactionData; - tokenMetas: TokenMetas; + tokenMeta?: TokenMeta | undefined; }; // TODO: handle partial const TokenTransferItem: React.FC = ({ t, txData, - tokenMetas, + tokenMeta, }) => (
@@ -57,7 +57,7 @@ const TokenTransferItem: React.FC = ({ @@ -65,11 +65,9 @@ const TokenTransferItem: React.FC = ({
diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 5fb100a..dddfd75 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -206,7 +206,7 @@ const Details: React.FC = ({ key={i} t={t} txData={txData} - tokenMetas={txData.tokenMetas} + tokenMeta={txData.tokenMetas[t.token]} /> ))} diff --git a/src/types.ts b/src/types.ts index 5a5dad9..5904b31 100644 --- a/src/types.ts +++ b/src/types.ts @@ -108,6 +108,4 @@ export type TokenMeta = { decimals: number; }; -export type TokenMetas = { - [tokenAddress: string]: TokenMeta; -}; +export type TokenMetas = Record; From 78d3c0e1b7a3028bfc7bfdba3d6710b581fca38f Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Sep 2021 01:09:56 -0300 Subject: [PATCH 12/14] Remove deprecated use4Bytes hook --- src/components/MethodName.tsx | 5 ++- src/transaction/Details.tsx | 4 +- src/use4Bytes.ts | 70 +---------------------------------- 3 files changed, 6 insertions(+), 73 deletions(-) diff --git a/src/components/MethodName.tsx b/src/components/MethodName.tsx index ff45cae..c965a81 100644 --- a/src/components/MethodName.tsx +++ b/src/components/MethodName.tsx @@ -7,8 +7,9 @@ type MethodNameProps = { const MethodName: React.FC = ({ data }) => { const rawFourBytes = rawInputTo4Bytes(data); - const methodName = use4Bytes(rawFourBytes); - const isSimpleTransfer = data === "0x"; + const fourBytesEntry = use4Bytes(rawFourBytes); + const methodName = fourBytesEntry?.name ?? rawFourBytes; + const isSimpleTransfer = rawFourBytes === "0x"; const methodTitle = isSimpleTransfer ? "ETH Transfer" : methodName === rawFourBytes diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index dddfd75..23f463a 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -36,7 +36,7 @@ import RelativePosition from "../components/RelativePosition"; import PercentagePosition from "../components/PercentagePosition"; import ModeTab from "../components/ModeTab"; import DecodedParamsTable from "./decoder/DecodedParamsTable"; -import { rawInputTo4Bytes, use4BytesFull } from "../use4Bytes"; +import { rawInputTo4Bytes, use4Bytes } from "../use4Bytes"; type DetailsProps = { txData: TransactionData; @@ -68,7 +68,7 @@ const Details: React.FC = ({ }, [txData]); const fourBytes = rawInputTo4Bytes(txData.data); - const fourBytesEntry = use4BytesFull(fourBytes); + const fourBytesEntry = use4Bytes(fourBytes); const fourBytesTxDesc = useMemo(() => { if (!txData || !fourBytesEntry?.signatureWithoutParamNames) { return undefined; diff --git a/src/use4Bytes.ts b/src/use4Bytes.ts index a1ab465..a494809 100644 --- a/src/use4Bytes.ts +++ b/src/use4Bytes.ts @@ -2,8 +2,6 @@ import { useState, useEffect, useContext } from "react"; import { RuntimeContext } from "./useRuntime"; import { fourBytesURL } from "./url"; -const cache = new Map(); - export type FourBytesEntry = { name: string; signatureWithoutParamNames: string | undefined; @@ -18,72 +16,6 @@ const simpleTransfer: FourBytesEntry = { const fullCache = new Map(); -// TODO: deprecate and remove -export const use4Bytes = (rawFourBytes: string) => { - const runtime = useContext(RuntimeContext); - const assetsURLPrefix = runtime.config?.assetsURLPrefix; - - const [name, setName] = useState(); - const [fourBytes, setFourBytes] = useState(); - useEffect(() => { - if (assetsURLPrefix === undefined || fourBytes === undefined) { - return; - } - - const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes); - fetch(signatureURL) - .then(async (res) => { - if (!res.ok) { - console.error(`Signature does not exist in 4bytes DB: ${fourBytes}`); - - // Use the default 4 bytes as name - setName(rawFourBytes); - cache.set(fourBytes, null); - return; - } - - const sig = await res.text(); - const cut = sig.indexOf("("); - let method = sig.slice(0, cut); - method = method.charAt(0).toUpperCase() + method.slice(1); - setName(method); - cache.set(fourBytes, method); - return; - }) - .catch((err) => { - console.error(`Couldn't fetch signature URL ${signatureURL}`, err); - - // Use the default 4 bytes as name - setName(rawFourBytes); - }); - }, [rawFourBytes, assetsURLPrefix, fourBytes]); - - if (rawFourBytes === "0x") { - return "Transfer"; - } - if (assetsURLPrefix === undefined) { - return rawFourBytes; - } - - // Try to resolve 4bytes name - const entry = cache.get(rawFourBytes.slice(2)); - if (entry === null) { - return rawFourBytes; - } - if (entry !== undefined) { - // Simulates LRU - cache.delete(entry); - cache.set(rawFourBytes.slice(2), entry); - return entry; - } - if (name === undefined && fourBytes === undefined) { - setFourBytes(rawFourBytes.slice(2)); - return ""; - } - - return name; -}; - export const rawInputTo4Bytes = (rawInput: string) => rawInput.slice(0, 10); /** @@ -91,7 +23,7 @@ export const rawInputTo4Bytes = (rawInput: string) => rawInput.slice(0, 10); * * @param rawFourBytes an hex string containing the 4bytes signature in the "0xXXXXXXXX" format. */ -export const use4BytesFull = ( +export const use4Bytes = ( rawFourBytes: string ): FourBytesEntry | null | undefined => { if (rawFourBytes !== "0x") { From af95c04c9cf5abff04e5028d17b8f6f4d122cce3 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Sep 2021 15:40:01 -0300 Subject: [PATCH 13/14] Flatten 4bytes signatures with param names --- Dockerfile | 8 ++++++-- nginx.conf | 34 ---------------------------------- package.json | 1 + src/use4Bytes.ts | 4 +++- 4 files changed, 10 insertions(+), 37 deletions(-) diff --git a/Dockerfile b/Dockerfile index 223578a..af322ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,10 +14,14 @@ WORKDIR /assets COPY trustwallet/blockchains/ethereum/assets /assets/ RUN find . -name logo.png | parallel magick convert {} -filter Lanczos -resize 32x32 {} +FROM alpine:3.14.0 AS fourbytesbuilder +WORKDIR /signatures +COPY 4bytes/signatures /signatures/ +COPY 4bytes/with_parameter_names /signatures/ + FROM nginx:1.21.1-alpine RUN apk add jq -COPY 4bytes/signatures /usr/share/nginx/html/signatures/ -COPY 4bytes/with_parameter_names /usr/share/nginx/html/with_parameter_names/ +COPY --from=fourbytesbuilder /signatures /usr/share/nginx/html/signatures/ COPY --from=logobuilder /assets /usr/share/nginx/html/assets/ COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /otterscan-build/build /usr/share/nginx/html/ diff --git a/nginx.conf b/nginx.conf index 8e55be6..55189ba 100644 --- a/nginx.conf +++ b/nginx.conf @@ -43,40 +43,6 @@ server { } } - location /with_parameter_names { - root /usr/share/nginx/html; - expires 30d; - - # Base on: https://michielkalkman.com/snippets/nginx-cors-open-configuration/ - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - # - # Om nom nom cookies - # - add_header 'Access-Control-Allow-Credentials' 'true'; - add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; - - # - # Custom headers and headers various browsers *should* be OK with but aren't - # - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; - - # - # Tell client that this pre-flight info is valid for 20 days - # - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - if ($request_method = 'GET') { - add_header 'Access-Control-Allow-Origin' '*' always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' always; - } - } - location /assets { root /usr/share/nginx/html; expires 30d; diff --git a/package.json b/package.json index 96ba0b7..1cf8dfc 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "eject": "react-scripts eject", "source-map-explorer": "source-map-explorer build/static/js/*.js", "assets-start": "docker run --rm -p 3001:80 --name otterscan-assets -d -v$(pwd)/4bytes/signatures:/usr/share/nginx/html/signatures/ -v$(pwd)/trustwallet/blockchains/ethereum/assets:/usr/share/nginx/html/assets -v$(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf nginx:1.21.1-alpine", + "assets-start-with-param-names": "docker run --rm -p 3001:80 --name otterscan-assets -d -v$(pwd)/4bytes/with_parameter_names:/usr/share/nginx/html/signatures/ -v$(pwd)/trustwallet/blockchains/ethereum/assets:/usr/share/nginx/html/assets -v$(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf nginx:1.21.1-alpine", "assets-stop": "docker stop otterscan-assets", "docker-build": "DOCKER_BUILDKIT=1 docker build -t otterscan -f Dockerfile .", "docker-start": "docker run --rm -p 5000:80 --name otterscan -d otterscan", diff --git a/src/use4Bytes.ts b/src/use4Bytes.ts index a494809..622f327 100644 --- a/src/use4Bytes.ts +++ b/src/use4Bytes.ts @@ -59,7 +59,9 @@ export const use4Bytes = ( return; } - const sig = await res.text(); + // Get only the first occurrence, for now ignore alternative param names + const sigs = await res.text(); + const sig = sigs.split(";")[0]; const cut = sig.indexOf("("); let method = sig.slice(0, cut); method = method.charAt(0).toUpperCase() + method.slice(1); From debc046d83ad5bf9a789216939dc55d6e8cbeb3f Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Sep 2021 15:47:55 -0300 Subject: [PATCH 14/14] Remove unused attribute; custom param presence msg depending on signature with or without param names --- src/transaction/Details.tsx | 4 ++-- src/transaction/decoder/DecodedParamsTable.tsx | 4 +++- src/use4Bytes.ts | 9 +++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 23f463a..bfd8747 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -70,10 +70,10 @@ const Details: React.FC = ({ const fourBytes = rawInputTo4Bytes(txData.data); const fourBytesEntry = use4Bytes(fourBytes); const fourBytesTxDesc = useMemo(() => { - if (!txData || !fourBytesEntry?.signatureWithoutParamNames) { + if (!txData || !fourBytesEntry?.signature) { return undefined; } - const sig = fourBytesEntry?.signatureWithoutParamNames; + const sig = fourBytesEntry?.signature; const functionFragment = Fragment.fromString(`function ${sig}`); const intf = new Interface([functionFragment]); return intf.parseTransaction({ data: txData.data, value: txData.value }); diff --git a/src/transaction/decoder/DecodedParamsTable.tsx b/src/transaction/decoder/DecodedParamsTable.tsx index 6f550f4..636dd67 100644 --- a/src/transaction/decoder/DecodedParamsTable.tsx +++ b/src/transaction/decoder/DecodedParamsTable.tsx @@ -28,7 +28,9 @@ const DecodedParamsTable: React.FC = ({ {!hasParamNames && (
- Parameter names are not available. + {paramTypes.length > 0 && paramTypes[0].name !== null + ? "Parameter names are estimated." + : "Parameter names are not available."}