diff --git a/README.md b/README.md index 51ff7659..75e0d65c 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,11 @@ The easiest way to install Rust and Cargo is by using [rustup](https://rustup.rs Then: ```bash -cd api; -cargo build; +cd api +cd hardhat_env +yarn +cd .. +cargo build ``` #### Plugin @@ -78,8 +81,11 @@ pnpm run serve; ``` ```bash -cd api; -cargo run; +cd api +cd hardhat_env +yarn +cd .. +VITE_URL=http://localhost:3000 cargo run ``` or alternatively, you can run the server in watch mode (with `cargo watch`): diff --git a/api/README.md b/api/README.md index 85fe4cfd..c10158dd 100644 --- a/api/README.md +++ b/api/README.md @@ -2,13 +2,21 @@ - [Rust](https://www.rust-lang.org/tools/install) +## Prepare + +```bash +cd api +cd hardhat_env +yarn +cd .. +``` ## Run ```bash -cargo run +VITE_URL=http://localhost:3000 cargo run ``` ## To run while watching files ```bash -cargo watch -x run -``` \ No newline at end of file +VITE_URL=http://localhost:3000 cargo watch -x run +``` diff --git a/api/hardhat_env/hardhat.config.ts b/api/hardhat_env/hardhat.config.ts index 0090380e..7309eabc 100644 --- a/api/hardhat_env/hardhat.config.ts +++ b/api/hardhat_env/hardhat.config.ts @@ -37,6 +37,10 @@ const config: HardhatUserConfig = { solidity: { version: "0.8.17", }, + // path to the directory with contracts + paths: { + sources: "./contracts", + }, }; export default config; diff --git a/api/hardhat_env/hardhat2.config.ts b/api/hardhat_env/hardhat2.config.ts new file mode 100644 index 00000000..d307a4b6 --- /dev/null +++ b/api/hardhat_env/hardhat2.config.ts @@ -0,0 +1,32 @@ +// import { HardhatUserConfig } from "hardhat/config"; +// import "@matterlabs/hardhat-zksync-deploy"; +// import "@matterlabs/hardhat-zksync-solc"; +// import "@matterlabs/hardhat-zksync-verify"; +// +// const config: HardhatUserConfig = { +// zksolc: { +// version: "1.3.14", +// settings: {}, +// }, +// solidity: { +// version: "0.8.17", +// }, +// paths: { +// sources: "./contracts/abc", +// } +// }; +// +// export default config; + +module.exports = { + zksolc: { + version: "1.3.12", + settings: {}, + }, + solidity: { + version: "0.8.17", + }, + paths: { + sources: "./contracts/abc", + } +}; diff --git a/api/src/handlers/compile.rs b/api/src/handlers/compile.rs index 0a5322d1..a1227b61 100644 --- a/api/src/handlers/compile.rs +++ b/api/src/handlers/compile.rs @@ -2,9 +2,10 @@ use crate::handlers::process::{do_process_command, fetch_process_result}; use crate::handlers::types::{ApiCommand, ApiCommandResult, CompileResponse, SolFile}; use crate::rate_limiter::RateLimited; use crate::types::{ApiError, Result}; +use crate::utils::hardhat_config::HardhatConfigBuilder; use crate::utils::lib::{ check_file_ext, get_file_path, path_buf_to_string, status_code_to_message, - to_human_error_batch, ARTIFACTS_ROOT, SOL_ROOT, + to_human_error_batch, ALLOWED_VERSIONS, ARTIFACTS_ROOT, SOL_ROOT, }; use crate::worker::WorkerEngine; use rocket::serde::json; @@ -18,30 +19,40 @@ use tracing::info; use tracing::instrument; #[instrument] -#[get("/compile/")] +#[get("/compile//")] pub async fn compile( + version: String, remix_file_path: PathBuf, _rate_limited: RateLimited, ) -> Json { - info!("/compile/{:?}", remix_file_path); - do_compile(remix_file_path).await.unwrap_or_else(|e| { - Json(CompileResponse { - file_content: vec![], - message: e.to_string(), - status: "Error".to_string(), + info!("/compile/{:?}/{:?}", version, remix_file_path); + do_compile(version, remix_file_path) + .await + .unwrap_or_else(|e| { + Json(CompileResponse { + file_content: vec![], + message: e.to_string(), + status: "Error".to_string(), + }) }) - }) } #[instrument] -#[get("/compile-async/")] +#[get("/compile-async//")] pub async fn compile_async( + version: String, remix_file_path: PathBuf, _rate_limited: RateLimited, engine: &State, ) -> String { - info!("/compile-async/{:?}", remix_file_path); - do_process_command(ApiCommand::Compile(remix_file_path), engine) + info!("/compile-async/{:?}/{:?}", version, remix_file_path); + do_process_command( + ApiCommand::Compile { + path: remix_file_path, + version, + }, + engine, + ) } #[instrument] @@ -56,15 +67,44 @@ pub async fn get_compile_result(process_id: String, engine: &State }) } -pub async fn do_compile(remix_file_path: PathBuf) -> Result> { +pub async fn do_compile( + version: String, + remix_file_path: PathBuf, +) -> Result> { + if !ALLOWED_VERSIONS.contains(&version.as_str()) { + return Err(ApiError::VersionNotSupported(version)); + } + let remix_file_path = path_buf_to_string(remix_file_path.clone())?; check_file_ext(&remix_file_path, "sol")?; - let file_path = get_file_path(&remix_file_path); + let file_path = get_file_path(&version, &remix_file_path); + + let sources_path = path_buf_to_string(Path::new(SOL_ROOT).join(&version))?; + let artifacts_path = ARTIFACTS_ROOT.to_string(); + + let hardhat_config = HardhatConfigBuilder::new() + .zksolc_version(&version) + .sources_path(&sources_path) + .artifacts_path(&artifacts_path) + .build(); + + // save temporary hardhat config to file + let hardhat_config_path = Path::new(SOL_ROOT).join(hardhat_config.name.clone()); + + fs::write( + hardhat_config_path.clone(), + hardhat_config.to_string_config(), + ) + .await + .map_err(ApiError::FailedToWriteFile)?; - let compile_result = Command::new("yarn") + let compile_result = Command::new("npx") + .arg("hardhat") .arg("compile") + .arg("--config") + .arg(hardhat_config_path.clone()) .current_dir(SOL_ROOT) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -75,16 +115,28 @@ pub async fn do_compile(remix_file_path: PathBuf) -> Result String { } #[instrument] -#[get("/compiler_version_async")] -pub async fn compiler_version_async(engine: &State) -> String { - info!("/compiler_version_async"); - do_process_command(ApiCommand::CompilerVersion, engine) -} - -#[instrument] -#[get("/compiler_version_result/")] -pub async fn get_compiler_version_result( - process_id: String, - engine: &State, -) -> String { - info!("/compiler_version_result/{:?}", process_id); - fetch_process_result(process_id, engine, |result| match result { - ApiCommandResult::CompilerVersion(version) => version.to_string(), - _ => String::from("Result not available"), - }) +#[get("/allowed_versions")] +pub async fn allowed_versions() -> String { + info!("/allowed_versions"); + do_allowed_versions().unwrap_or_else(|e| format!("Error: {:?}", e)) } /// Run ./zksolc --version to return compiler version string @@ -37,3 +22,7 @@ pub async fn get_compiler_version_result( pub fn do_compiler_version() -> Result { Ok("zksolc-latest".to_string()) } + +pub fn do_allowed_versions() -> Result { + Ok(serde_json::to_string(&ALLOWED_VERSIONS).unwrap()) +} diff --git a/api/src/handlers/mod.rs b/api/src/handlers/mod.rs index df8a4c6a..155c2fd1 100644 --- a/api/src/handlers/mod.rs +++ b/api/src/handlers/mod.rs @@ -31,7 +31,10 @@ pub async fn dispatch_command(command: ApiCommand) -> Result Ok(ApiCommandResult::CompilerVersion(result)), Err(e) => Err(e), }, - ApiCommand::Compile(remix_file_path) => match do_compile(remix_file_path).await { + ApiCommand::Compile { + path: remix_file_path, + version, + } => match do_compile(version, remix_file_path).await { Ok(compile_response) => Ok(ApiCommandResult::Compile(compile_response.into_inner())), Err(e) => Err(e), }, diff --git a/api/src/handlers/save_code.rs b/api/src/handlers/save_code.rs index 9eed8785..b66ccf1b 100644 --- a/api/src/handlers/save_code.rs +++ b/api/src/handlers/save_code.rs @@ -1,24 +1,34 @@ use crate::types::{ApiError, Result}; -use crate::utils::lib::{get_file_path, init_parent_directories, path_buf_to_string}; +use crate::utils::lib::{ + get_file_path, init_parent_directories, path_buf_to_string, ALLOWED_VERSIONS, +}; use rocket::data::ToByteUnit; use rocket::Data; use std::path::PathBuf; use tracing::info; -#[post("/save_code/", data = "")] -pub async fn save_code(file: Data<'_>, remix_file_path: PathBuf) -> String { +#[post("/save_code//", data = "")] +pub async fn save_code(file: Data<'_>, version: String, remix_file_path: PathBuf) -> String { info!("/save_code/{:?}", remix_file_path); - do_save_code(file, remix_file_path) + do_save_code(file, version, remix_file_path) .await .unwrap_or_else(|e| e.to_string()) } /// Upload a data file /// -pub async fn do_save_code(file: Data<'_>, remix_file_path: PathBuf) -> Result { +pub async fn do_save_code( + file: Data<'_>, + version: String, + remix_file_path: PathBuf, +) -> Result { + if !ALLOWED_VERSIONS.contains(&version.as_str()) { + return Err(ApiError::VersionNotSupported(version)); + } + let remix_file_path = path_buf_to_string(remix_file_path.clone())?; - let file_path = get_file_path(&remix_file_path); + let file_path = get_file_path(&version, &remix_file_path); // create file directory from file path init_parent_directories(file_path.clone()).await; diff --git a/api/src/handlers/types.rs b/api/src/handlers/types.rs index 9e0cb084..df398661 100644 --- a/api/src/handlers/types.rs +++ b/api/src/handlers/types.rs @@ -32,7 +32,10 @@ pub struct ScarbCompileResponse { #[derive(Debug)] pub enum ApiCommand { CompilerVersion, - Compile(PathBuf), + Compile { + version: String, + path: PathBuf, + }, #[allow(dead_code)] Shutdown, } diff --git a/api/src/main.rs b/api/src/main.rs index d164fa74..442ab82c 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -14,9 +14,7 @@ use crate::rate_limiter::RateLimiter; use crate::tracing_log::init_logger; use crate::worker::WorkerEngine; use handlers::compile::{compile, compile_async, get_compile_result}; -use handlers::compiler_version::{ - compiler_version, compiler_version_async, get_compiler_version_result, -}; +use handlers::compiler_version::{allowed_versions, compiler_version}; use handlers::process::get_process_status; use handlers::save_code::save_code; use handlers::{health, who_is_this}; @@ -60,9 +58,8 @@ async fn rocket() -> _ { get_compile_result, save_code, compiler_version, - compiler_version_async, - get_compiler_version_result, get_process_status, + allowed_versions, health, who_is_this, ], diff --git a/api/src/types.rs b/api/src/types.rs index a7227320..f29766d0 100644 --- a/api/src/types.rs +++ b/api/src/types.rs @@ -36,4 +36,8 @@ pub enum ApiError { MutexUnlockError, #[error("Error('s) raised while trying to parse sol file: \n{0}")] FailedToParseSol(String), + #[error("Failed to write file: {0}")] + FailedToWriteFile(IoError), + #[error("Unsupported version: {0}")] + VersionNotSupported(String), } diff --git a/api/src/utils/hardhat_config.rs b/api/src/utils/hardhat_config.rs new file mode 100644 index 00000000..3aea091c --- /dev/null +++ b/api/src/utils/hardhat_config.rs @@ -0,0 +1,172 @@ +use crate::utils::lib::timestamp; +use rand::Rng; +use rocket::serde::json::serde_json; + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +pub struct HardhatConfig { + #[serde(skip)] + pub name: String, + pub zksolc: ZksolcConfig, + pub solidity: SolidityConfig, + pub paths: PathsConfig, +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default)] +pub struct ZksolcConfig { + pub version: String, + pub settings: serde_json::Value, +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default)] +pub struct SolidityConfig { + pub version: String, +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default)] +pub struct PathsConfig { + pub sources: String, + pub artifacts: String, +} + +#[derive(Default)] +pub struct HardhatConfigBuilder { + config: HardhatConfig, +} + +impl Default for HardhatConfig { + fn default() -> Self { + Self { + name: Self::generate_random_name(), + zksolc: ZksolcConfig { + version: "latest".to_string(), + settings: serde_json::json!({}), + }, + solidity: SolidityConfig { + version: "0.8.17".to_string(), + }, + paths: PathsConfig { + sources: "./contracts".to_string(), + artifacts: "./artifacts".to_string(), + }, + } + } +} + +impl HardhatConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn to_string_config(&self) -> String { + let config_prefix_js = r#" +import { HardhatUserConfig } from "hardhat/config"; + +import "@matterlabs/hardhat-zksync-deploy"; +import "@matterlabs/hardhat-zksync-solc"; + +import "@matterlabs/hardhat-zksync-verify"; + +export const zkSyncTestnet = process.env.NODE_ENV == "test" +? { + url: "http://127.0.0.1:8011", + ethNetwork: "http://127.0.0.1:8045", + zksync: true, + } +: { + url: "https://testnet.era.zksync.dev", + ethNetwork: "goerli", + zksync: true, + // contract verification endpoint + verifyURL: "https://zksync2-testnet-explorer.zksync.dev/contract_verification", + }; +"#; + + let config = format!( + r#"{} +const config: HardhatUserConfig = {{ + zksolc: {{ + version: "{}", + settings: {{}}, + }}, + defaultNetwork: "zkSyncTestnet", + networks: {{ + hardhat: {{ + zksync: false, + }}, + zkSyncTestnet, + }}, + solidity: {{ + version: "0.8.17", + }}, + // path to the directory with contracts + paths: {{ + sources: "{}", + artifacts: "{}", + }}, +}}; + +export default config; +"#, + config_prefix_js, self.zksolc.version, self.paths.sources, self.paths.artifacts + ); + + config + } + + pub fn generate_random_name() -> String { + let mut rng = rand::thread_rng(); + let rand_string: Vec = std::iter::repeat(()) + .map(|()| rng.sample(rand::distributions::Alphanumeric)) + .take(10) + .collect(); + format!( + "hardhat-{}-{}.config.ts", + timestamp(), + String::from_utf8(rand_string).unwrap_or("".to_string()) + ) + } +} + +impl HardhatConfigBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn zksolc_version(&mut self, version: &str) -> &mut Self { + self.config.zksolc.version = version.to_string(); + self + } + + pub fn solidity_version(&mut self, version: &str) -> &mut Self { + self.config.solidity.version = version.to_string(); + self + } + + pub fn sources_path(&mut self, path: &str) -> &mut Self { + self.config.paths.sources = path.to_string(); + self + } + + pub fn artifacts_path(&mut self, path: &str) -> &mut Self { + self.config.paths.artifacts = path.to_string(); + self + } + + pub fn build(&self) -> HardhatConfig { + self.config.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_random_name() { + for _ in 1..100 { + let name = HardhatConfig::generate_random_name(); + + println!("Random name: {}", name); + } + } +} diff --git a/api/src/utils/lib.rs b/api/src/utils/lib.rs index 1d9a0d25..e3fe1e34 100644 --- a/api/src/utils/lib.rs +++ b/api/src/utils/lib.rs @@ -6,14 +6,13 @@ use std::path::{Path, PathBuf}; pub const SOL_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/", "hardhat_env/contracts/"); pub const HARDHAT_ENV_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/", "hardhat_env/"); -pub const ARTIFACTS_ROOT: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/", - "hardhat_env/artifacts-zk/contracts/" -); +pub const ARTIFACTS_ROOT: &str = + concat!(env!("CARGO_MANIFEST_DIR"), "/", "hardhat_env/artifacts-zk"); pub const DURATION_TO_PURGE: u64 = 60 * 5; // 5 minutes +pub const ALLOWED_VERSIONS: [&str; 2] = ["latest", "1.3.13"]; + #[allow(dead_code)] pub const TEMP_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/", "temp/"); @@ -68,11 +67,11 @@ pub fn status_code_to_message(status: Option) -> String { .to_string() } -pub fn get_file_path(file_path: &String) -> PathBuf { +pub fn get_file_path(version: &str, file_path: &str) -> PathBuf { match get_file_ext(file_path).to_string() { // Leaving this here for potential use with vyper - ext if ext == "sol" => Path::new(SOL_ROOT).join(file_path), - _ => Path::new(SOL_ROOT).join(file_path), + ext if ext == "sol" => Path::new(SOL_ROOT).join(version).join(file_path), + _ => Path::new(SOL_ROOT).join(version).join(file_path), } } @@ -129,7 +128,7 @@ pub fn to_human_error( pub fn to_human_error_batch(diagnostics: Vec) -> String { diagnostics .into_iter() - .map(|diagnostic| to_human_error(diagnostic)) + .map(to_human_error) .collect::>() .join("\n") } diff --git a/api/src/utils/mod.rs b/api/src/utils/mod.rs index 965f28e9..650cc561 100644 --- a/api/src/utils/mod.rs +++ b/api/src/utils/mod.rs @@ -1 +1,2 @@ +pub mod hardhat_config; pub mod lib; diff --git a/plugin/src/components/BackgroundNotices/index.tsx b/plugin/src/components/BackgroundNotices/index.tsx index 10adffca..d039d640 100644 --- a/plugin/src/components/BackgroundNotices/index.tsx +++ b/plugin/src/components/BackgroundNotices/index.tsx @@ -1,7 +1,7 @@ import React from 'react' const Notices = [ - 'The zksync Remix Plugin is in Alpha', + 'The zkSync Remix Plugin is in Alpha', 'Solidity contracts are compiled on a server hosted by Nethermind' ] diff --git a/plugin/src/contexts/VersionContext.ts b/plugin/src/contexts/VersionContext.ts new file mode 100644 index 00000000..91e7eaf7 --- /dev/null +++ b/plugin/src/contexts/VersionContext.ts @@ -0,0 +1,11 @@ +import type React from 'react' +import { createContext } from 'react' + +const VersionContext = createContext({ + solidityVersion: '' as string, + setSolidityVersion: ((_: string) => {}) as React.Dispatch>, + versions: [] as string[], + setVersions: ((_: string[]) => {}) as React.Dispatch>, +}) + +export default VersionContext diff --git a/plugin/src/features/Compilation/index.tsx b/plugin/src/features/Compilation/index.tsx index 5f23afa1..e64e9b8e 100644 --- a/plugin/src/features/Compilation/index.tsx +++ b/plugin/src/features/Compilation/index.tsx @@ -17,6 +17,7 @@ import CompilationContext from '../../contexts/CompilationContext' import { type AccordianTabs } from '../Plugin' import { type Contract } from '../../types/contracts' import { asyncFetch } from '../../utils/async_fetch' +import VersionContext from '../../contexts/VersionContext' // eslint-disable-next-line @typescript-eslint/no-empty-interface interface CompilationProps { @@ -49,6 +50,13 @@ const Compilation: React.FC = ({ setAccordian }) => { setActiveTomlPath } = useContext(CompilationContext) + const { + solidityVersion, + setSolidityVersion, + versions, + setVersions + } = useContext(VersionContext); + const [currWorkspacePath, setCurrWorkspacePath] = React.useState('') useEffect(() => { @@ -334,7 +342,7 @@ const Compilation: React.FC = ({ setAccordian }) => { setStatus('Parsing solidity code...') let response = await fetch( - `${apiUrl}/save_code/${hashDir}/${currentFilePath}`, + `${apiUrl}/save_code/${solidityVersion}/${hashDir}/${currentFilePath}`, { method: 'POST', body: currentFileContent, @@ -356,7 +364,7 @@ const Compilation: React.FC = ({ setAccordian }) => { setStatus('Compiling...') - response = await asyncFetch(`compile-async/${hashDir}/${currentFilePath}`, 'compile-result') + response = await asyncFetch(`compile-async/${solidityVersion}/${hashDir}/${currentFilePath}`, 'compile-result') if (!response.ok) { await remixClient.call( diff --git a/plugin/src/features/CompilerVersion/index.tsx b/plugin/src/features/CompilerVersion/index.tsx index a3a09e7d..cab08995 100644 --- a/plugin/src/features/CompilerVersion/index.tsx +++ b/plugin/src/features/CompilerVersion/index.tsx @@ -5,46 +5,57 @@ import { RemixClientContext } from '../../contexts/RemixClientContext' import Nethermind from '../../components/NM' import './style.css' import { BsChevronDown } from 'react-icons/bs' +import VersionContext from '../../contexts/VersionContext' const SolidityVersion: React.FC = () => { - const [solidityVersion, setSolidityVersion] = useState('solidity-compile 2.2.0') const remixClient = useContext(RemixClientContext) - - const [versions, setVersions] = useState([]) const pluginVersion = process.env.REACT_APP_VERSION !== undefined ? `v${process.env.REACT_APP_VERSION}` : 'v0.2.0' - useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - const id = setTimeout(async () => { - try { - if (apiUrl !== undefined) { - await remixClient.call( - 'notification' as any, - 'toast', - `🟢 Fetching solidity version from the compilation server at ${apiUrl}` - ) - - const response = await fetch(`${apiUrl}/compiler_version`, { - method: 'GET', - redirect: 'follow', - headers: { - 'Content-Type': 'application/octet-stream' - } - }) - const version = await response.text() + const { + solidityVersion, + setSolidityVersion, + versions, + setVersions + } = useContext(VersionContext); - setSolidityVersion(version) - setVersions([version]) - } - } catch (e) { + const fetchVersions = async () => { + try { + if (apiUrl !== undefined) { await remixClient.call( 'notification' as any, 'toast', - '🔴 Failed to fetch solidity version from the compilation server' + `🟢 Fetching solidity versions from the compilation server at ${apiUrl}` ) - console.error(e) + + const response = await fetch(`${apiUrl}/allowed_versions`, { + method: 'GET', + redirect: 'follow', + headers: { + 'Content-Type': 'application/octet-stream' + } + }) + const allowedVersions = await response.json() + + setVersions(allowedVersions) + + if (allowedVersions.length > 0) { + setSolidityVersion(allowedVersions[0]) + } } - }, 100) + } catch (e) { + await remixClient.call( + 'notification' as any, + 'toast', + '🔴 Failed to fetch solidity versions from the compilation server' + ) + console.error(e) + } + } + + // fetch versions + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + const id = setTimeout(fetchVersions, 100) return () => { clearInterval(id) } @@ -56,7 +67,7 @@ const SolidityVersion: React.FC = () => { diff --git a/plugin/src/features/Plugin/index.tsx b/plugin/src/features/Plugin/index.tsx index 0a837eac..ba6b81a8 100644 --- a/plugin/src/features/Plugin/index.tsx +++ b/plugin/src/features/Plugin/index.tsx @@ -33,6 +33,7 @@ import ExplorerSelector, { } from '../../components/ExplorerSelector' import { DeployedContractsContext } from '../../contexts/DeployedContractsContext' import { type Provider, type Wallet } from 'zksync-web3' +import VersionContext from '../../contexts/VersionContext' export type AccordianTabs = | 'compile' @@ -103,6 +104,10 @@ const Plugin: React.FC = () => { const [currentAccordian, setCurrentAccordian] = useState('compile') + const [solidityVersion, setSolidityVersion] = useState('') + const [versions, setVersions] = useState([]) + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const handleTabView = (clicked: AccordianTabs) => { if (currentAccordian === clicked) { @@ -163,150 +168,157 @@ const Plugin: React.FC = () => { }} >
- - - {/* Compilation part */} - - - { - handleTabView('compile') - }} - > - -

Compile

- -
-
- - - -
-
- {/* Deployment part */} - + + - - { - handleTabView('deploy') - }} - > - + + { + handleTabView('compile') + }} > -

Deploy

- +

Compile

+ -
-
- - - -
- + : status === 'failed' ? 'error' : '' + } + /> + + + + + + + + + + { + handleTabView('deploy') + }} + > + +

Deploy

+ +
+
+ + + +
+ + { + handleTabView('interaction') + }} + > + +

Interact

+ +
+
+ + + +
+
+ {/* Transactions start */} + { - handleTabView('interaction') + handleTabView('transactions') }} > -

Interact

- Transactions

+ {/* Select explorer */} +
- +
-
- {/* Transactions start */} - - { - handleTabView('transactions') - }} - > - -

Transactions

- {/* Select explorer */} - -
-
- - - -
-
-
- -
+ +
+ +
+
{ if (selectedContract != null) { return contracts.findIndex( - (contract) => contract.sourceName === selectedContract.sourceName + (contract) => + `${contract.sourceName}:${contract.contractName}` === `${selectedContract.sourceName}:${selectedContract.contractName}` ) } return 0