Skip to content
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

Support verification of Stylus contracts #2450

Merged
merged 10 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy-review-l2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:
- none
- arbitrum
- arbitrum_nova
- arbitrum_sepolia
- base
- celo_alfajores
- garnet
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:
- none
- arbitrum
- arbitrum_nova
- arbitrum_sepolia
- base
- celo_alfajores
- garnet
Expand Down
1 change: 1 addition & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@
"main",
"localhost",
"arbitrum",
"arbitrum_sepolia",
"base",
"celo_alfajores",
"garnet",
Expand Down
47 changes: 47 additions & 0 deletions configs/envs/.env.arbitrum_sepolia
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Set of ENVs for Arbitrum Sepolia network explorer
# https://arbitrum-sepolia.blockscout.com
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=arbitrum_sepolia"

# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws

# Instance ENVs
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=arbitrum-sepolia.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/arbitrum-sepolia.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xb730960249381c72588024f5e213abd8e032d968aeb9629103e70677b0850bfa
NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(27, 74, 221, 1)']}
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_LOGOUT_URL=https://blockscout-arbitrum.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-sepolia.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-sepolia-dark.svg
NEXT_PUBLIC_NETWORK_ID=421614
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/arbitrum-sepolia.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/arbitrum-sepolia-dark.svg
NEXT_PUBLIC_NETWORK_NAME=Arbitrum Sepolia
NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
NEXT_PUBLIC_NETWORK_SHORT_NAME=Arbitrum Sepolia
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/arbitrum-sepolia.png
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com
NEXT_PUBLIC_ROLLUP_TYPE=arbitrum
NEXT_PUBLIC_SENTRY_DSN=https://fdcd971162e04694bf03564c5be3d291@o1222505.ingest.sentry.io/4503902500421632
NEXT_PUBLIC_STATS_API_HOST=https://stats-arbitrum-sepolia.k8s-prod-2.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
3 changes: 3 additions & 0 deletions lib/contracts/formatLanguageName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function formatLanguageName(language: string) {
return language.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase());
}
13 changes: 13 additions & 0 deletions mocks/contract/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ export const zkSync: SmartContract = {
optimization_runs: 's',
};

export const stylusRust: SmartContract = {
...verified,
language: 'stylus_rust',
github_repository_metadata: {
commit: 'af5029f822815e32def0015bf8e591e769c62f34',
path_prefix: 'examples/erc20',
repository_url: 'https://github.com/blockscout/cargo-stylus-test-examples',
},
compiler_version: 'v0.5.6',
package_name: 'erc20',
evm_version: null,
};

export const nonVerified: SmartContract = {
is_verified: false,
is_blueprint: false,
Expand Down
26 changes: 26 additions & 0 deletions mocks/contracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,36 @@ export const contract2: VerifiedContract = {
license_type: 'bsd_3_clause',
};

export const contract3: VerifiedContract = {
address: {
ens_domain_name: null,
hash: '0xf145e3A26c6706F64d95Dc8d9d45022D8b3D676B',
implementations: [],
is_contract: true,
is_verified: true,
metadata: null,
name: 'StylusTestToken',
private_tags: [],
public_tags: [],
watchlist_names: [],
},
certified: false,
coin_balance: '0',
compiler_version: 'v0.5.6',
has_constructor_args: false,
language: 'stylus_rust',
license_type: 'none',
market_cap: null,
optimization_enabled: false,
transaction_count: 0,
verified_at: '2024-12-03T14:05:42.796224Z',
};

export const baseResponse: VerifiedContractsResponse = {
items: [
contract1,
contract2,
contract3,
],
next_page_params: {
items_count: '50',
Expand Down
3 changes: 3 additions & 0 deletions nextjs/csp/policies/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export function app(): CspDev.DirectiveDescriptor {

// github (spec for api-docs page)
'raw.githubusercontent.com',

// github api (used for Stylus contract verification)
'api.github.com',
].filter(Boolean),

'script-src': [
Expand Down
1 change: 1 addition & 0 deletions nextjs/csp/policies/monaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function monaco(): CspDev.DirectiveDescriptor {
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/elixir/elixir.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/javascript/javascript.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/typescript/typescript.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/rust/rust.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/language/json/jsonMode.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/language/json/jsonWorker.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/language/typescript/tsMode.js',
Expand Down
1 change: 1 addition & 0 deletions tools/preset-sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'path';
/* eslint-disable no-console */
const PRESETS = {
arbitrum: 'https://arbitrum.blockscout.com',
arbitrum_sepolia: 'https://arbitrum-sepolia.blockscout.com',
base: 'https://base.blockscout.com',
blackfort_testnet: 'https://blackfort-testnet.blockscout.com',
celo_alfajores: 'https://celo-alfajores.blockscout.com',
Expand Down
9 changes: 8 additions & 1 deletion types/api/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ export interface SmartContract {
license_type: SmartContractLicenseType | null;
certified?: boolean;
zk_compiler_version?: string;
github_repository_metadata?: {
commit?: string;
path_prefix?: string;
repository_url?: string;
};
package_name?: string;
}

export type SmartContractDecodedConstructorArg = [
Expand All @@ -92,13 +98,14 @@ export interface SmartContractExternalLibrary {
// VERIFICATION

export type SmartContractVerificationMethodApi = 'flattened-code' | 'standard-input' | 'sourcify' | 'multi-part'
| 'vyper-code' | 'vyper-multi-part' | 'vyper-standard-input';
| 'vyper-code' | 'vyper-multi-part' | 'vyper-standard-input' | 'stylus-github-repository';

export interface SmartContractVerificationConfigRaw {
solidity_compiler_versions: Array<string>;
solidity_evm_versions: Array<string>;
verification_options: Array<string>;
vyper_compiler_versions: Array<string>;
stylus_compiler_versions?: Array<string>;
vyper_evm_versions: Array<string>;
is_rust_verifier_microservice_enabled: boolean;
license_types: Record<SmartContractLicenseType, number>;
Expand Down
2 changes: 1 addition & 1 deletion types/api/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface VerifiedContract {
certified?: boolean;
coin_balance: string;
compiler_version: string | null;
language: 'vyper' | 'yul' | 'solidity';
language: 'vyper' | 'yul' | 'solidity' | 'stylus_rust';
has_constructor_args: boolean;
optimization_enabled: boolean;
transaction_count: number | null;
Expand Down
11 changes: 9 additions & 2 deletions ui/address/contract/ContractSourceCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { SmartContract } from 'types/api/contract';

import { route } from 'nextjs-routes';

import formatLanguageName from 'lib/contracts/formatLanguageName';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/links/LinkInternal';
import CodeEditor from 'ui/shared/monaco/CodeEditor';
Expand All @@ -24,6 +25,10 @@ function getEditorData(contractInfo: SmartContract | undefined) {
return 'vy';
case 'yul':
return 'yul';
case 'scilla':
return 'scilla';
case 'stylus_rust':
return 'rs';
default:
return 'sol';
}
Expand Down Expand Up @@ -51,7 +56,7 @@ export const ContractSourceCode = ({ data, isLoading, sourceAddress }: Props) =>
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<span>Contract source code</span>
{ data?.language &&
<Text whiteSpace="pre" as="span" variant="secondary" textTransform="capitalize"> ({ data.language })</Text> }
<Text whiteSpace="pre" as="span" variant="secondary"> ({ formatLanguageName(data.language) })</Text> }
</Skeleton>
);

Expand All @@ -73,7 +78,9 @@ export const ContractSourceCode = ({ data, isLoading, sourceAddress }: Props) =>
</Tooltip>
) : null;

const ides = <ContractCodeIdes hash={ sourceAddress } isLoading={ isLoading }/>;
const ides = data?.language && [ 'solidity', 'vyper', 'yul' ].includes(data.language) ?
<ContractCodeIdes hash={ sourceAddress } isLoading={ isLoading }/> :
null;

const copyToClipboard = data && editorData?.length === 1 ? (
<CopyToClipboard
Expand Down
12 changes: 12 additions & 0 deletions ui/address/contract/info/ContractDetailsInfo.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ test('zkSync contract', async({ render, mockEnvs }) => {
await expect(component).toHaveScreenshot();
});

test('stylus rust contract', async({ render, mockEnvs }) => {
await mockEnvs(ENVS_MAP.zkSyncRollup);
const props = {
data: contractMock.stylusRust,
isLoading: false,
addressHash: addressMock.contract.hash,
};
const component = await render(<ContractDetailsInfo { ...props }/>);

await expect(component).toHaveScreenshot();
});

test.describe('with audits feature', () => {

test.beforeEach(async({ mockEnvs }) => {
Expand Down
40 changes: 37 additions & 3 deletions ui/address/contract/info/ContractDetailsInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract';
import config from 'configs/app';
import { CONTRACT_LICENSES } from 'lib/contracts/licenses';
import dayjs from 'lib/date/dayjs';
import { getGitHubOwnerAndRepo } from 'ui/contractVerification/utils';
import ContractCertifiedLabel from 'ui/shared/ContractCertifiedLabel';
import LinkExternal from 'ui/shared/links/LinkExternal';

Expand Down Expand Up @@ -45,6 +46,25 @@ const ContractDetailsInfo = ({ data, isLoading, addressHash }: Props) => {
);
})();

const sourceCodeLink = (() => {
if (!data.github_repository_metadata?.repository_url || !data.github_repository_metadata?.commit) {
return null;
}

const { owner, repo } = getGitHubOwnerAndRepo(data.github_repository_metadata.repository_url) || {};

const repoUrl = data.github_repository_metadata.repository_url;
const commit = data.github_repository_metadata.commit;
const pathPrefix = data.github_repository_metadata.path_prefix;
return (
<LinkExternal href={ `${ repoUrl }/tree/${ commit }${ pathPrefix ? `/${ pathPrefix }` : '' }` }>
{ owner && repo ? `${ owner }/${ repo }` : data.github_repository_metadata.repository_url }
</LinkExternal>
);
})();

const isStylusContract = data.language === 'stylus_rust';

return (
<Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 6 } mb={ 8 }>
{ data.name && (
Expand Down Expand Up @@ -84,20 +104,27 @@ const ContractDetailsInfo = ({ data, isLoading, addressHash }: Props) => {
isLoading={ isLoading }
/>
) }
{ typeof data.optimization_enabled === 'boolean' && (
{ typeof data.optimization_enabled === 'boolean' && !isStylusContract && (
<ContractDetailsInfoItem
label="Optimization enabled"
content={ data.optimization_enabled ? 'true' : 'false' }
isLoading={ isLoading }
/>
) }
{ data.optimization_runs !== null && (
{ data.optimization_runs !== null && !isStylusContract && (
<ContractDetailsInfoItem
label={ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' ? 'Optimization mode' : 'Optimization runs' }
content={ String(data.optimization_runs) }
isLoading={ isLoading }
/>
) }
{ data.package_name && (
<ContractDetailsInfoItem
label="Package name"
content={ data.package_name }
isLoading={ isLoading }
/>
) }
{ data.verified_at && (
<ContractDetailsInfoItem
label="Verified at"
Expand All @@ -106,14 +133,21 @@ const ContractDetailsInfo = ({ data, isLoading, addressHash }: Props) => {
isLoading={ isLoading }
/>
) }
{ data.file_path && (
{ data.file_path && !isStylusContract && (
<ContractDetailsInfoItem
label="Contract file path"
content={ data.file_path }
wordBreak="break-word"
isLoading={ isLoading }
/>
) }
{ sourceCodeLink && (
<ContractDetailsInfoItem
label="Source code"
content={ sourceCodeLink }
isLoading={ isLoading }
/>
) }
{ config.UI.hasContractAuditReports && (
<ContractDetailsInfoItem
label="Security audit"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading