-
Notifications
You must be signed in to change notification settings - Fork 325
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds a row to account headers for Program Accounts like so:  --------- Co-authored-by: Noah Gundotra <[email protected]>
- Loading branch information
Showing
9 changed files
with
6,341 additions
and
4,612 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
'use client'; | ||
|
||
import { ParsedAccountRenderer } from '@components/account/ParsedAccountRenderer'; | ||
import React from 'react'; | ||
|
||
import { VerifiedBuildCard } from '@/app/components/account/VerifiedBuildCard'; | ||
|
||
type Props = Readonly<{ | ||
params: { | ||
address: string; | ||
}; | ||
}>; | ||
|
||
function VerifiedBuildCardRenderer({ | ||
account, | ||
onNotFound, | ||
}: React.ComponentProps<React.ComponentProps<typeof ParsedAccountRenderer>['renderComponent']>) { | ||
const parsedData = account?.data?.parsed; | ||
if (!parsedData || parsedData?.program !== 'bpf-upgradeable-loader') { | ||
return onNotFound(); | ||
} | ||
return <VerifiedBuildCard data={parsedData} pubkey={account.pubkey} />; | ||
} | ||
|
||
export default function SecurityPageClient({ params: { address } }: Props) { | ||
return <ParsedAccountRenderer address={address} renderComponent={VerifiedBuildCardRenderer} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import getReadableTitleFromAddress, { AddressPageMetadataProps } from '@utils/get-readable-title-from-address'; | ||
import { Metadata } from 'next/types'; | ||
|
||
import VerifiedBuildClient from './page-client'; | ||
|
||
export async function generateMetadata(props: AddressPageMetadataProps): Promise<Metadata> { | ||
return { | ||
description: `Contents of the verified build info for the program with address ${props.params.address} on Solana`, | ||
title: `Verified Build | ${await getReadableTitleFromAddress(props)} | Solana`, | ||
}; | ||
} | ||
|
||
type Props = Readonly<{ | ||
params: { | ||
address: string; | ||
}; | ||
}>; | ||
|
||
export default function VerifiedBuildPage(props: Props) { | ||
return <VerifiedBuildClient {...props} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { ErrorCard } from '@components/common/ErrorCard'; | ||
import { TableCardBody } from '@components/common/TableCardBody'; | ||
import { UpgradeableLoaderAccountData } from '@providers/accounts'; | ||
import { PublicKey } from '@solana/web3.js'; | ||
import { ExternalLink } from 'react-feather'; | ||
|
||
import { OsecRegistryInfo, useVerifiedProgramRegistry } from '@/app/utils/verified-builds'; | ||
|
||
import { LoadingCard } from '../common/LoadingCard'; | ||
|
||
export function VerifiedBuildCard({ data, pubkey }: { data: UpgradeableLoaderAccountData; pubkey: PublicKey }) { | ||
const { data: registryInfo, isLoading } = useVerifiedProgramRegistry({ | ||
options: { suspense: true }, | ||
programId: pubkey, | ||
}); | ||
if (!data.programData) { | ||
return <ErrorCard text="Account has no data" />; | ||
} | ||
|
||
if (isLoading) { | ||
return <LoadingCard message="Fetching last verified build hash" />; | ||
} | ||
|
||
if (!registryInfo) { | ||
return <ErrorCard text="No verified build found" />; | ||
} | ||
|
||
return ( | ||
<div className="card security-txt"> | ||
<div className="card-header"> | ||
<h3 className="card-header-title mb-0 d-flex align-items-center">Verified Build</h3> | ||
<small>Information provided by osec.io</small> | ||
</div> | ||
<TableCardBody> | ||
{ROWS.filter(x => x.key in registryInfo).map((x, idx) => { | ||
return ( | ||
<tr key={idx}> | ||
<td className="w-100">{x.display}</td> | ||
<RenderEntry value={registryInfo[x.key]} type={x.type} /> | ||
</tr> | ||
); | ||
})} | ||
</TableCardBody> | ||
</div> | ||
); | ||
} | ||
|
||
enum DisplayType { | ||
Boolean, | ||
String, | ||
URL, | ||
Date, | ||
} | ||
|
||
type TableRow = { | ||
display: string; | ||
key: keyof OsecRegistryInfo; | ||
type: DisplayType; | ||
}; | ||
|
||
const ROWS: TableRow[] = [ | ||
{ | ||
display: 'Verified', | ||
key: 'is_verified', | ||
type: DisplayType.Boolean, | ||
}, | ||
{ | ||
display: 'Message', | ||
key: 'message', | ||
type: DisplayType.String, | ||
}, | ||
{ | ||
display: 'On Chain Hash', | ||
key: 'on_chain_hash', | ||
type: DisplayType.String, | ||
}, | ||
{ | ||
display: 'Executable Hash', | ||
key: 'executable_hash', | ||
type: DisplayType.String, | ||
}, | ||
{ | ||
display: 'Last Verified At', | ||
key: 'last_verified_at', | ||
type: DisplayType.Date, | ||
}, | ||
{ | ||
display: 'Repository URL', | ||
key: 'repo_url', | ||
type: DisplayType.URL, | ||
}, | ||
]; | ||
|
||
function RenderEntry({ value, type }: { value: OsecRegistryInfo[keyof OsecRegistryInfo]; type: DisplayType }) { | ||
switch (type) { | ||
case DisplayType.Boolean: | ||
return ( | ||
<td className={'text-lg-end font-monospace'}> | ||
<span className={`badge bg-${value ? 'success' : 'warning'}-soft`}>{new String(value)}</span> | ||
</td> | ||
); | ||
case DisplayType.String: | ||
return <td className="text-lg-end font-monospace" style={{whiteSpace: 'pre'}}>{value && (value as string).length > 1 ? value : '-'}</td>; | ||
case DisplayType.URL: | ||
if (isValidLink(value as string)) { | ||
return ( | ||
<td className="text-lg-end"> | ||
<span className="font-monospace"> | ||
<a rel="noopener noreferrer" target="_blank" href={value as string}> | ||
{value} | ||
<ExternalLink className="align-text-top ms-2" size={13} /> | ||
</a> | ||
</span> | ||
</td> | ||
); | ||
} | ||
return ( | ||
<td className="text-lg-end font-monospace"> | ||
{value && (value as string).length > 1 ? (value as string).trim() : '-'} | ||
</td> | ||
); | ||
case DisplayType.Date: | ||
return <td className="text-lg-end font-monospace">{value && (value as string).length > 1 ? new Date(value as string).toUTCString() : '-'}</td>; | ||
default: | ||
break; | ||
} | ||
return <></>; | ||
} | ||
|
||
function isValidLink(value: string) { | ||
try { | ||
const url = new URL(value); | ||
return ['http:', 'https:'].includes(url.protocol); | ||
} catch (err) { | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { PublicKey } from '@solana/web3.js'; | ||
import Link from 'next/link'; | ||
|
||
import { useClusterPath } from '@/app/utils/url'; | ||
import { hashProgramData, useVerifiedProgramRegistry } from '@/app/utils/verified-builds'; | ||
import { ProgramDataAccountInfo } from '@/app/validators/accounts/upgradeable-program'; | ||
|
||
export function VerifiedProgramBadge({ | ||
programData, | ||
pubkey, | ||
}: { | ||
programData: ProgramDataAccountInfo; | ||
pubkey: PublicKey; | ||
}) { | ||
const { isLoading, data: registryInfo } = useVerifiedProgramRegistry({ programId: pubkey }); | ||
const verifiedBuildTabPath = useClusterPath({ pathname: `/address/${pubkey.toBase58()}/verified-build` }); | ||
|
||
const hash = hashProgramData(programData); | ||
|
||
if (isLoading) { | ||
return ( | ||
<h3 className="mb-0"> | ||
<span className="badge">Loading...</span> | ||
</h3> | ||
); | ||
} else if (registryInfo && hash === registryInfo['on_chain_hash'] && registryInfo['is_verified']) { | ||
return ( | ||
<h3 className="mb-0"> | ||
<Link className="badge bg-success-soft rank" href={verifiedBuildTabPath}> | ||
Program Source Program Verified | ||
</Link> | ||
</h3> | ||
); | ||
} else { | ||
const message = | ||
!registryInfo || !registryInfo['repo_url'] ? 'Source Code Not Provided' : 'Program Not Verified'; | ||
return ( | ||
<h3 className="mb-0"> | ||
<span className="badge bg-warning-soft rank">{message}</span> | ||
</h3> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
import { PublicKey } from '@solana/web3.js'; | ||
import useSWRImmutable from 'swr/immutable'; | ||
|
||
import { ProgramDataAccountInfo } from '../validators/accounts/upgradeable-program'; | ||
|
||
const OSEC_REGISTRY_URL = 'https://verify.osec.io'; | ||
|
||
export type OsecRegistryInfo = { | ||
is_verified: boolean; | ||
message: string; | ||
on_chain_hash: string; | ||
executable_hash: string; | ||
last_verified_at: string | null; | ||
repo_url: string; | ||
}; | ||
|
||
export type CheckedOsecRegistryInfo = { | ||
explorer_hash: string; | ||
}; | ||
|
||
export function useVerifiedProgramRegistry({ | ||
programId, | ||
options, | ||
}: { | ||
programId: PublicKey; | ||
options?: { suspense: boolean }; | ||
}) { | ||
const { data, error, isLoading } = useSWRImmutable( | ||
`${programId.toBase58()}`, | ||
async (programId: string) => { | ||
return fetch(`${OSEC_REGISTRY_URL}/status/${programId}`).then(response => response.json()); | ||
}, | ||
{ suspense: options?.suspense } | ||
); | ||
return { data: error ? null : (data as OsecRegistryInfo), isLoading }; | ||
} | ||
|
||
export function hashProgramData(programData: ProgramDataAccountInfo): string { | ||
const buffer = Buffer.from(programData.data[0], 'base64'); | ||
// Truncate null bytes at the end of the buffer | ||
let truncatedBytes = 0; | ||
while (buffer[buffer.length - 1 - truncatedBytes] === 0) { | ||
truncatedBytes++; | ||
} | ||
// Hash the binary | ||
const c = Buffer.from(buffer.slice(0, buffer.length - truncatedBytes)); | ||
return Buffer.from(sha256(c)).toString('hex'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.