-
Notifications
You must be signed in to change notification settings - Fork 33
[Feat] Integrate Bytecode Verification Status Display #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5aaf46b
7a693c6
1ccf2e9
bcdf6c5
e504661
324c5ff
e535e16
5ab5c34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,9 @@ | |
| # IDEs and editors | ||
| /.idea | ||
| /.vscode | ||
| /.claude | ||
| /.cursor | ||
| /.taskmaster | ||
|
|
||
| # misc | ||
| .DS_Store | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import {useQuery, UseQueryResult} from "@tanstack/react-query"; | ||
| import { | ||
| getModuleVerificationStatus, | ||
| ModuleVerificationStatusResponse, | ||
| ResponseError, | ||
| ResponseErrorType, | ||
| } from "../client"; | ||
| import {useGlobalState} from "../../global-config/GlobalConfig"; | ||
|
|
||
| /** Hook to query module bytecode verification status for a contract version (upgrade_number). */ | ||
| export function useGetModuleVerificationStatus( | ||
| address: string, | ||
| moduleName: string, | ||
| options?: {enabled?: boolean; upgradeNumber?: number}, | ||
| ): UseQueryResult<ModuleVerificationStatusResponse, ResponseError> { | ||
| const [state] = useGlobalState(); | ||
| const {upgradeNumber, ...rest} = options ?? {}; | ||
|
|
||
| return useQuery<ModuleVerificationStatusResponse, ResponseError>({ | ||
| queryKey: [ | ||
| "moduleVerificationStatus", | ||
| {address, moduleName, upgradeNumber}, | ||
| state.network_value, | ||
| ], | ||
| queryFn: () => | ||
| getModuleVerificationStatus( | ||
| state.network_value, | ||
| address, | ||
| moduleName, | ||
| upgradeNumber, | ||
| ), | ||
| refetchOnWindowFocus: false, | ||
| retry: (failureCount, error) => { | ||
| // Don't retry for expected error types | ||
| if ( | ||
| error.type === ResponseErrorType.NOT_FOUND || | ||
| error.type === ResponseErrorType.SERVICE_UNAVAILABLE || | ||
| error.type === ResponseErrorType.COMPILATION_ERROR | ||
| ) { | ||
| return false; | ||
| } | ||
| return failureCount < 2; | ||
| }, | ||
| ...rest, | ||
| }); | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,15 +13,15 @@ export const bardockTestnetUrl = | |
| import.meta.env.MOVEMENT_TESTNET_URL || | ||
| `https://testnet.movementnetwork.xyz/v1`; | ||
|
|
||
| export const networks = { | ||
| mainnet: mainnetUrl, | ||
| testnet: "", | ||
| "bardock testnet": bardockTestnetUrl, | ||
| export const networks = { | ||
| mainnet: mainnetUrl, | ||
| testnet: "", | ||
| "bardock testnet": bardockTestnetUrl, | ||
| devnet: "", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apenzk could you clean up the indentation:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| local: "http://localhost:30731", | ||
| mevmdevnet: "", | ||
| custom: "", | ||
| }; | ||
| local: "http://127.0.0.1:8080", | ||
| mevmdevnet: "", | ||
| custom: "", | ||
| }; | ||
|
|
||
| export const availableNetworks = ["mainnet", "bardock testnet"]; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,13 @@ | ||||||||||||||||
| import {Box, Button, Modal, Stack, Typography, useTheme} from "@mui/material"; | ||||||||||||||||
| import { | ||||||||||||||||
| Alert, | ||||||||||||||||
| Box, | ||||||||||||||||
| Button, | ||||||||||||||||
| CircularProgress, | ||||||||||||||||
| Modal, | ||||||||||||||||
| Stack, | ||||||||||||||||
| Typography, | ||||||||||||||||
| useTheme, | ||||||||||||||||
| } from "@mui/material"; | ||||||||||||||||
| import {ContentCopy, OpenInFull} from "@mui/icons-material"; | ||||||||||||||||
| import SyntaxHighlighter from "react-syntax-highlighter"; | ||||||||||||||||
| import {getPublicFunctionLineNumber, transformCode} from "../../../utils"; | ||||||||||||||||
|
|
@@ -18,6 +27,8 @@ import { | |||||||||||||||
| } from "../../../themes/colors/aptosColorPalette"; | ||||||||||||||||
| import {useParams} from "react-router-dom"; | ||||||||||||||||
| import {useLogEventWithBasic} from "../hooks/useLogEventWithBasic"; | ||||||||||||||||
| import {useGetModuleVerificationStatus} from "../../../api/hooks/useGetModuleVerificationStatus"; | ||||||||||||||||
| import {ResponseErrorType} from "../../../api/client"; | ||||||||||||||||
|
|
||||||||||||||||
| function useStartingLineNumber(sourceCode?: string) { | ||||||||||||||||
| const functionToHighlight = useParams().selectedFnName; | ||||||||||||||||
|
|
@@ -105,15 +116,39 @@ function ExpandCode({sourceCode}: {sourceCode: string | undefined}) { | |||||||||||||||
| ); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export function Code({bytecode}: {bytecode: string}) { | ||||||||||||||||
| /** Displays source code with a warning if bytecode verification fails. */ | ||||||||||||||||
| export function Code({ | ||||||||||||||||
| bytecode, | ||||||||||||||||
| address, | ||||||||||||||||
| moduleName, | ||||||||||||||||
| upgradeNumber, | ||||||||||||||||
| }: { | ||||||||||||||||
| bytecode: string; | ||||||||||||||||
| address?: string; | ||||||||||||||||
| moduleName?: string; | ||||||||||||||||
| upgradeNumber?: number; | ||||||||||||||||
| }) { | ||||||||||||||||
| const {selectedModuleName} = useParams(); | ||||||||||||||||
| const logEvent = useLogEventWithBasic(); | ||||||||||||||||
| const theme = useTheme(); | ||||||||||||||||
|
|
||||||||||||||||
| // Use the module name from props or from URL params | ||||||||||||||||
| const moduleNameToVerify = moduleName || selectedModuleName || ""; | ||||||||||||||||
|
|
||||||||||||||||
| // Query verification for this contract version (upgrade_number) | ||||||||||||||||
| const { | ||||||||||||||||
| data: verificationStatus, | ||||||||||||||||
| isLoading: isVerificationLoading, | ||||||||||||||||
| error: verificationError, | ||||||||||||||||
| } = useGetModuleVerificationStatus(address || "", moduleNameToVerify, { | ||||||||||||||||
| enabled: !!address && !!moduleNameToVerify, | ||||||||||||||||
| upgradeNumber, | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| const TOOLTIP_TIME = 2000; // 2s | ||||||||||||||||
|
|
||||||||||||||||
| const sourceCode = bytecode === "0x" ? undefined : transformCode(bytecode); | ||||||||||||||||
|
|
||||||||||||||||
| const theme = useTheme(); | ||||||||||||||||
| const [tooltipOpen, setTooltipOpen] = useState<boolean>(false); | ||||||||||||||||
|
|
||||||||||||||||
| async function copyCode() { | ||||||||||||||||
|
|
@@ -136,6 +171,14 @@ export function Code({bytecode}: {bytecode: string}) { | |||||||||||||||
| } | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| // Always show code if sourceCode exists, with warnings when appropriate | ||||||||||||||||
| const isVerified = verificationStatus?.verified === true; | ||||||||||||||||
| const hasVerificationFailure = verificationStatus?.verified === false; | ||||||||||||||||
| const hasVerificationDisabled = verificationError?.type === ResponseErrorType.SERVICE_UNAVAILABLE; | ||||||||||||||||
| const hasVerificationUnavailable = verificationError?.type === ResponseErrorType.NOT_FOUND; | ||||||||||||||||
| const hasCompilationError = verificationError?.type === ResponseErrorType.COMPILATION_ERROR; | ||||||||||||||||
| const shouldShowCode = !!sourceCode; | ||||||||||||||||
|
|
||||||||||||||||
| return ( | ||||||||||||||||
| <Box> | ||||||||||||||||
| <Stack | ||||||||||||||||
|
|
@@ -154,7 +197,6 @@ export function Code({bytecode}: {bytecode: string}) { | |||||||||||||||
| <Typography fontSize={20} fontWeight={700}> | ||||||||||||||||
| Code | ||||||||||||||||
| </Typography> | ||||||||||||||||
| <StyledLearnMoreTooltip text="Please be aware that this code was provided by the owner and it could be different to the real code on blockchain. We cannot verify it." /> | ||||||||||||||||
| </Stack> | ||||||||||||||||
| {sourceCode && ( | ||||||||||||||||
| <Stack direction="row" spacing={2}> | ||||||||||||||||
|
|
@@ -196,44 +238,68 @@ export function Code({bytecode}: {bytecode: string}) { | |||||||||||||||
| </Stack> | ||||||||||||||||
| )} | ||||||||||||||||
| </Stack> | ||||||||||||||||
| {sourceCode && ( | ||||||||||||||||
| <Typography | ||||||||||||||||
| variant="body1" | ||||||||||||||||
| fontSize={14} | ||||||||||||||||
| fontWeight={400} | ||||||||||||||||
| marginBottom={"16px"} | ||||||||||||||||
| color={theme.palette.mode === "dark" ? grey[400] : grey[600]} | ||||||||||||||||
| {isVerificationLoading ? ( | ||||||||||||||||
| <Box | ||||||||||||||||
| display="flex" | ||||||||||||||||
| alignItems="center" | ||||||||||||||||
| justifyContent="center" | ||||||||||||||||
| padding={4} | ||||||||||||||||
| > | ||||||||||||||||
| The source code is plain text uploaded by the deployer, which can be | ||||||||||||||||
| different from the actual bytecode. | ||||||||||||||||
| </Typography> | ||||||||||||||||
| )} | ||||||||||||||||
| {!sourceCode ? ( | ||||||||||||||||
| <Box> | ||||||||||||||||
| Unfortunately, the source code cannot be shown because the package | ||||||||||||||||
| publisher has chosen not to make it available | ||||||||||||||||
| <CircularProgress size={24} /> | ||||||||||||||||
| <Typography marginLeft={2}>Verifying source code...</Typography> | ||||||||||||||||
| </Box> | ||||||||||||||||
| ) : shouldShowCode ? ( | ||||||||||||||||
| <Stack spacing={1}> | ||||||||||||||||
| {isVerified && ( | ||||||||||||||||
| <Alert severity="success"> | ||||||||||||||||
| Source code verified: the displayed code matches the deployed bytecode. | ||||||||||||||||
| </Alert> | ||||||||||||||||
| )} | ||||||||||||||||
| {hasVerificationFailure && ( | ||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add message when verification is successful:
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||||||||||||||||
| <Alert severity="error"> | ||||||||||||||||
| The deployer provided source code but it does not match the deployed bytecode. The displayed code may not accurately represent what is actually running on-chain. | ||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i was going for the less strong word, because i would assume if there is a differing comment it would also fail? which can be construed as not being different with respect to code... is this wrong? |
||||||||||||||||
| </Alert> | ||||||||||||||||
| )} | ||||||||||||||||
| {hasCompilationError && ( | ||||||||||||||||
| <Alert severity="warning"> | ||||||||||||||||
| This contract cannot be verified because it uses dependencies that are not part of the standard Aptos framework. The displayed code was provided by the deployer and may not match the deployed bytecode. | ||||||||||||||||
| </Alert> | ||||||||||||||||
| )} | ||||||||||||||||
| {(hasVerificationDisabled || hasVerificationUnavailable) && ( | ||||||||||||||||
| <Alert severity="info"> | ||||||||||||||||
| Source code verification is not available on this node. The displayed code was provided by the deployer and may not match the deployed bytecode. | ||||||||||||||||
| </Alert> | ||||||||||||||||
| )} | ||||||||||||||||
| <Box | ||||||||||||||||
| sx={{ | ||||||||||||||||
| maxHeight: "100vh", | ||||||||||||||||
| overflow: "auto", | ||||||||||||||||
| borderRadius: 0, | ||||||||||||||||
| backgroundColor: codeBlockColor, | ||||||||||||||||
| }} | ||||||||||||||||
| ref={codeBoxScrollRef} | ||||||||||||||||
| > | ||||||||||||||||
| <SyntaxHighlighter | ||||||||||||||||
| language="rust" | ||||||||||||||||
| key={theme.palette.mode} | ||||||||||||||||
| style={ | ||||||||||||||||
| theme.palette.mode === "light" ? solarizedLight : solarizedDark | ||||||||||||||||
| } | ||||||||||||||||
| customStyle={{margin: 0, backgroundColor: "unset"}} | ||||||||||||||||
| showLineNumbers | ||||||||||||||||
| > | ||||||||||||||||
| {sourceCode} | ||||||||||||||||
| </SyntaxHighlighter> | ||||||||||||||||
| </Box> | ||||||||||||||||
| </Stack> | ||||||||||||||||
| ) : ( | ||||||||||||||||
| <Box | ||||||||||||||||
| sx={{ | ||||||||||||||||
| maxHeight: "100vh", | ||||||||||||||||
| overflow: "auto", | ||||||||||||||||
| borderRadius: 0, | ||||||||||||||||
| backgroundColor: codeBlockColor, | ||||||||||||||||
| }} | ||||||||||||||||
| ref={codeBoxScrollRef} | ||||||||||||||||
| padding={2} | ||||||||||||||||
| bgcolor={theme.palette.mode === "dark" ? grey[800] : grey[100]} | ||||||||||||||||
| > | ||||||||||||||||
| <SyntaxHighlighter | ||||||||||||||||
| language="rust" | ||||||||||||||||
| key={theme.palette.mode} | ||||||||||||||||
| style={ | ||||||||||||||||
| theme.palette.mode === "light" ? solarizedLight : solarizedDark | ||||||||||||||||
| } | ||||||||||||||||
| customStyle={{margin: 0, backgroundColor: "unset"}} | ||||||||||||||||
| showLineNumbers | ||||||||||||||||
| > | ||||||||||||||||
| {sourceCode} | ||||||||||||||||
| </SyntaxHighlighter> | ||||||||||||||||
| <Typography color={grey[500]}> | ||||||||||||||||
| The source code cannot be shown because the package publisher has chosen not to make it available. | ||||||||||||||||
| </Typography> | ||||||||||||||||
| </Box> | ||||||||||||||||
| )} | ||||||||||||||||
| </Box> | ||||||||||||||||
|
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this change is intentional, flagging it in case it affects other existing workflows
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thx for pointing this out.
The new value for local network URL matches aptos-core's default REST API port. (defined in aptos-core/config/src/config/api_config.rs as DEFAULT_PORT: u16 = 8080).
i did not understand the reason why it should be 30731
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iirc it was 30731 with the old process-compose but yes 8080 should be correct default now... think @musitdev would know with greater certainty.