diff --git a/forc/src/cli/commands/deploy.rs b/forc/src/cli/commands/deploy.rs index 6c10e5b90b9..870c5779e69 100644 --- a/forc/src/cli/commands/deploy.rs +++ b/forc/src/cli/commands/deploy.rs @@ -1,8 +1,13 @@ use structopt::{self, StructOpt}; +use crate::ops::forc_deploy; + #[derive(Debug, StructOpt)] -pub(crate) struct Command {} +pub struct Command {} pub(crate) fn exec(command: Command) -> Result<(), String> { - todo!() + match forc_deploy::deploy(command) { + Err(e) => Err(e.message), + _ => Ok(()), + } } diff --git a/forc/src/cli/mod.rs b/forc/src/cli/mod.rs index 6efc4b2e347..d58b5252912 100644 --- a/forc/src/cli/mod.rs +++ b/forc/src/cli/mod.rs @@ -10,7 +10,7 @@ use analysis::Command as AnalysisCommand; use benchmark::Command as BenchmarkCommand; pub use build::Command as BuildCommand; use coverage::Command as CoverageCommand; -use deploy::Command as DeployCommand; +pub use deploy::Command as DeployCommand; pub use format::Command as FormatCommand; use init::Command as InitCommand; use mvprun::Command as MvprunCommand; diff --git a/forc/src/ops/forc_build.rs b/forc/src/ops/forc_build.rs index c4a52a6e220..c5bf44f4b18 100644 --- a/forc/src/ops/forc_build.rs +++ b/forc/src/ops/forc_build.rs @@ -1,4 +1,7 @@ -use crate::{cli::BuildCommand, utils::helpers::find_manifest_dir}; +use crate::{ + cli::BuildCommand, + utils::helpers::{find_manifest_dir, get_main_file, read_manifest}, +}; use line_col::LineColLookup; use source_span::{ fmt::{Color, Formatter, Style}, @@ -8,12 +11,12 @@ use std::fs::File; use std::io::{self, Write}; use termcolor::{BufferWriter, Color as TermColor, ColorChoice, ColorSpec, WriteColor}; -use crate::utils::manifest::{Dependency, DependencyDetails, Manifest}; +use crate::utils::manifest::{Dependency, DependencyDetails}; use core_lang::{ BuildConfig, BytecodeCompilationResult, CompilationResult, FinalizedAsm, LibraryExports, Namespace, }; -use std::{fs, path::PathBuf}; +use std::path::PathBuf; pub fn build(command: BuildCommand) -> Result, String> { let BuildCommand { @@ -138,28 +141,6 @@ fn compile_dependency_lib<'source, 'manifest>( Ok(()) } -fn read_manifest(manifest_dir: &PathBuf) -> Result { - let manifest_path = { - let mut man = manifest_dir.clone(); - man.push(crate::utils::constants::MANIFEST_FILE_NAME); - man - }; - let manifest_path_str = format!("{:?}", manifest_path); - let manifest = match std::fs::read_to_string(manifest_path) { - Ok(o) => o, - Err(e) => { - return Err(format!( - "failed to read manifest at {:?}: {}", - manifest_path_str, e - )) - } - }; - match toml::from_str(&manifest) { - Ok(o) => Ok(o), - Err(e) => Err(format!("Error parsing manifest: {}.", e)), - } -} - fn compile_library<'source, 'manifest>( source: &'source str, proj_name: &str, @@ -378,23 +359,6 @@ fn write_yellow(txt: &str) -> io::Result<()> { Ok(()) } -fn get_main_file( - manifest_of_dep: &Manifest, - manifest_dir: &PathBuf, -) -> Result<&'static mut String, String> { - let main_path = { - let mut code_dir = manifest_dir.clone(); - code_dir.push("src"); - code_dir.push(&manifest_of_dep.project.entry); - code_dir - }; - - // some hackery to get around lifetimes for now, until the AST returns a non-lifetime-bound AST - let main_file = fs::read_to_string(&main_path).map_err(|e| e.to_string())?; - let main_file = Box::new(main_file); - let main_file: &'static mut String = Box::leak(main_file); - return Ok(main_file); -} fn compile_to_asm<'source, 'manifest>( source: &'source str, proj_name: &str, diff --git a/forc/src/ops/forc_deploy.rs b/forc/src/ops/forc_deploy.rs new file mode 100644 index 00000000000..26c673fe4ca --- /dev/null +++ b/forc/src/ops/forc_deploy.rs @@ -0,0 +1,157 @@ +use core_lang::{parse, CompileError}; +use fuel_tx::{crypto::hash, ContractAddress, Output, Salt, Transaction}; + +use crate::cli::DeployCommand; + +use crate::ops::forc_build; +use crate::utils::{constants, helpers}; +use constants::{MANIFEST_FILE_NAME, SWAY_LIBRARY, SWAY_PREDICATE, SWAY_SCRIPT}; +use helpers::{find_manifest_dir, get_main_file, read_manifest}; +use std::{fmt, io, path::PathBuf}; + +use crate::cli::BuildCommand; + +pub fn deploy(_: DeployCommand) -> Result<(), DeployError> { + let curr_dir = std::env::current_dir()?; + + match find_manifest_dir(&curr_dir) { + Some(manifest_dir) => { + let manifest = read_manifest(&manifest_dir)?; + let project_name = &manifest.project.name; + let main_file = get_main_file(&manifest, &manifest_dir)?; + + // parse the main file and check is it a contract + match parse(main_file) { + core_lang::CompileResult::Ok { + value: parse_tree, + warnings: _, + errors: _, + } => { + if let Some(_) = &parse_tree.contract_ast { + let build_command = BuildCommand { + path: None, + print_asm: false, + binary_outfile: None, + }; + + let compiled_contract = forc_build::build(build_command)?; + let tx = create_contract_tx(compiled_contract); + + // todo: pass the transaction to the running node + println!("{:?}", tx); + Ok(()) + } else { + let parse_type = { + if parse_tree.script_ast.is_some() { + SWAY_SCRIPT + } else if parse_tree.predicate_ast.is_some() { + SWAY_PREDICATE + } else { + SWAY_LIBRARY + } + }; + + Err(DeployError::not_a_contract(project_name, parse_type)) + } + } + core_lang::CompileResult::Err { + warnings: _, + errors, + } => Err(DeployError::parsing_failed(project_name, errors)), + } + } + None => Err(DeployError::manifest_file_missing(curr_dir)), + } +} + +fn create_contract_tx(compiled_contract: Vec) -> Transaction { + let gas_price = 0; + let gas_limit = 10000000; + let maturity = 0; + let bytecode_witness_index = 0; + let witnesses = vec![compiled_contract.into()]; + + let salt = Salt::new([0; 32]); + let static_contracts = vec![]; + let inputs = vec![]; + + let zero_hash = hash("0".as_bytes()); + + let outputs = vec![Output::ContractCreated { + contract_id: ContractAddress::new(zero_hash.into()), + }]; + + Transaction::create( + gas_price, + gas_limit, + maturity, + bytecode_witness_index, + salt, + static_contracts, + inputs, + outputs, + witnesses, + ) +} + +pub struct DeployError { + pub message: String, +} + +impl DeployError { + fn manifest_file_missing(curr_dir: PathBuf) -> Self { + let message = format!( + "Manifest file not found at {:?}. Project root should contain '{}'", + curr_dir, MANIFEST_FILE_NAME + ); + Self { message } + } + + fn parsing_failed(project_name: &str, errors: Vec) -> Self { + let message = errors + .iter() + .map(|e| e.to_friendly_error_string()) + .collect::>() + .join("\n"); + + Self { + message: format!("Parsing {} failed: \n{}", project_name, message), + } + } + + fn not_a_contract(project_name: &str, parse_type: &str) -> Self { + let message = format!( + "{} is not a 'contract' it is a '{}'\nContracts should start with 'contract;'", + project_name, parse_type + ); + Self { message } + } +} + +impl fmt::Display for DeployError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self) + } +} + +impl From<&str> for DeployError { + fn from(s: &str) -> Self { + DeployError { + message: s.to_string(), + } + } +} + +impl From for DeployError { + fn from(s: String) -> Self { + DeployError { message: s } + } +} + +impl From for DeployError { + fn from(e: io::Error) -> Self { + DeployError { + message: e.to_string(), + } + } +} diff --git a/forc/src/ops/mod.rs b/forc/src/ops/mod.rs index fbd861fd1bb..baaaf4314d0 100644 --- a/forc/src/ops/mod.rs +++ b/forc/src/ops/mod.rs @@ -1,3 +1,4 @@ pub mod forc_build; +pub mod forc_deploy; pub mod forc_fmt; pub mod forc_init; diff --git a/forc/src/utils/constants.rs b/forc/src/utils/constants.rs index c4ad394320f..37843906274 100644 --- a/forc/src/utils/constants.rs +++ b/forc/src/utils/constants.rs @@ -1,2 +1,7 @@ pub const MANIFEST_FILE_NAME: &'static str = "Forc.toml"; pub const SWAY_EXTENSION: &'static str = "sw"; +pub const SRC_DIR: &'static str = "src"; + +pub const SWAY_PREDICATE: &'static str = "predicate"; +pub const SWAY_LIBRARY: &'static str = "library"; +pub const SWAY_SCRIPT: &'static str = "script"; diff --git a/forc/src/utils/helpers.rs b/forc/src/utils/helpers.rs index eb7cdb86c68..3026d6ed673 100644 --- a/forc/src/utils/helpers.rs +++ b/forc/src/utils/helpers.rs @@ -1,3 +1,5 @@ +use super::constants::SRC_DIR; +use super::manifest::Manifest; use std::path::PathBuf; // Continually go up in the file tree until a manifest (Forc.toml) is found. @@ -16,3 +18,43 @@ pub fn find_manifest_dir(starter_path: &PathBuf) -> Option { } None } + +pub fn read_manifest(manifest_dir: &PathBuf) -> Result { + let manifest_path = { + let mut man = manifest_dir.clone(); + man.push(crate::utils::constants::MANIFEST_FILE_NAME); + man + }; + let manifest_path_str = format!("{:?}", manifest_path); + let manifest = match std::fs::read_to_string(manifest_path) { + Ok(o) => o, + Err(e) => { + return Err(format!( + "failed to read manifest at {:?}: {}", + manifest_path_str, e + )) + } + }; + match toml::from_str(&manifest) { + Ok(o) => Ok(o), + Err(e) => Err(format!("Error parsing manifest: {}.", e)), + } +} + +pub fn get_main_file( + manifest_of_dep: &Manifest, + manifest_dir: &PathBuf, +) -> Result<&'static mut String, String> { + let main_path = { + let mut code_dir = manifest_dir.clone(); + code_dir.push(SRC_DIR); + code_dir.push(&manifest_of_dep.project.entry); + code_dir + }; + + // some hackery to get around lifetimes for now, until the AST returns a non-lifetime-bound AST + let main_file = std::fs::read_to_string(&main_path).map_err(|e| e.to_string())?; + let main_file = Box::new(main_file); + let main_file: &'static mut String = Box::leak(main_file); + return Ok(main_file); +}