diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9b05226731..015a8b10a49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -265,12 +265,27 @@ jobs: profile: minimal toolchain: stable - uses: Swatinem/rust-cache@v1 - - name: Cargo Run E2E Tests + - name: Cargo Run E2E Tests (Fuel VM) uses: actions-rs/cargo@v1 with: command: run args: --locked --release --bin test -- --locked + cargo-run-e2e-test-evm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + - uses: Swatinem/rust-cache@v1 + - name: Cargo Run E2E Tests (EVM) + uses: actions-rs/cargo@v1 + with: + command: run + args: --locked --release --bin test -- --target evm --locked + # TODO: Remove this upon merging std tests with the rest of the E2E tests. cargo-test-lib-std: runs-on: ubuntu-latest @@ -353,6 +368,7 @@ jobs: cargo-clippy, cargo-fmt-check, cargo-run-e2e-test, + cargo-run-e2e-test-evm, cargo-test-lib-std, cargo-test-workspace, cargo-unused-deps-check, @@ -384,6 +400,7 @@ jobs: cargo-clippy, cargo-fmt-check, cargo-run-e2e-test, + cargo-run-e2e-test-evm, cargo-test-lib-std, cargo-test-workspace, cargo-unused-deps-check, @@ -454,6 +471,7 @@ jobs: cargo-clippy, cargo-fmt-check, cargo-run-e2e-test, + cargo-run-e2e-test-evm, cargo-test-lib-std, cargo-test-workspace, cargo-unused-deps-check, diff --git a/Cargo.lock b/Cargo.lock index 1f561b2fdfd..c9dce313831 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -390,6 +405,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "008b57b368e638ed60664350ea4f2f3647a0192173478df2736cc255a025a796" +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bs58" version = "0.4.0" @@ -1172,6 +1208,18 @@ dependencies = [ "signature", ] +[[package]] +name = "educe" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.8.0" @@ -1240,6 +1288,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "enum-ordinalize" +version = "3.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -1323,21 +1385,6 @@ dependencies = [ "uint", ] -[[package]] -name = "etk-asm" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f930978593dfe8eb61901b68b083f5b08898f45ed7387ca627ede2f036b426a" -dependencies = [ - "hex", - "num-bigint", - "pest", - "pest_derive", - "rand", - "sha3 0.10.6", - "snafu", -] - [[package]] name = "eventsource-client" version = "0.10.2" @@ -1788,6 +1835,60 @@ dependencies = [ "uint", ] +[[package]] +name = "fuel-etk-4byte" +version = "0.3.0-dev" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38224fd7fd560d6940f1940f12227194302feb1382fc2d5fb1e72b66d0858803" +dependencies = [ + "bincode", + "brotli", + "lazy_static", + "serde", +] + +[[package]] +name = "fuel-etk-asm" +version = "0.3.0-dev" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82f8a8705e958dadfc332f152d0367e5b5661d2a098467306ec18dcc10ce62d" +dependencies = [ + "fuel-etk-ops", + "hex", + "num-bigint", + "pest", + "pest_derive", + "rand", + "sha3 0.10.6", + "snafu", +] + +[[package]] +name = "fuel-etk-dasm" +version = "0.3.0-dev" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1018ec288a5a28e4b7f4ea2599a0a5b46880b6e118f30211980f03e0625fe798" +dependencies = [ + "fuel-etk-4byte", + "fuel-etk-asm", + "fuel-etk-ops", + "hex", +] + +[[package]] +name = "fuel-etk-ops" +version = "0.3.0-dev" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bdf975af67b4baad4d2f5d59c7ec82c73511b6e87aa60acab7326418e8ef3" +dependencies = [ + "educe", + "indexmap", + "quote", + "serde", + "snafu", + "toml", +] + [[package]] name = "fuel-gql-client" version = "0.15.1" @@ -2539,6 +2640,7 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -4542,15 +4644,19 @@ dependencies = [ "derivative", "dirs 3.0.2", "either", - "etk-asm", "fuel-abi-types", "fuel-ethabi", + "fuel-etk-asm", + "fuel-etk-dasm", + "fuel-etk-ops", "fuel-vm", "hashbrown 0.13.1", "hex", "im", "itertools", "lazy_static", + "pest", + "pest_derive", "petgraph", "rustc-hash", "serde", diff --git a/forc-pkg/src/manifest.rs b/forc-pkg/src/manifest.rs index ca9ead4e685..153036bbb74 100644 --- a/forc-pkg/src/manifest.rs +++ b/forc-pkg/src/manifest.rs @@ -9,7 +9,7 @@ use std::{ sync::Arc, }; -use sway_core::{fuel_prelude::fuel_tx, language::parsed::TreeType, parse_tree_type}; +use sway_core::{fuel_prelude::fuel_tx, language::parsed::TreeType, parse_tree_type, BuildTarget}; pub use sway_types::ConfigTimeConstant; use sway_utils::constants; @@ -143,6 +143,7 @@ pub struct PackageManifest { pub patch: Option>, /// A list of [configuration-time constants](https://github.com/FuelLabs/sway/issues/1498). pub constants: Option>, + pub build_target: Option>, build_profile: Option>, pub contract_dependencies: Option>, } diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 76366656f6d..7f053be14d3 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -27,7 +27,7 @@ use std::{ str::FromStr, }; use sway_core::{ - abi_generation::generate_json_abi_program, + abi_generation::{evm_json_abi, fuel_json_abi}, asm_generation::ProgramABI, decl_engine::{DeclEngine, DeclId}, fuel_prelude::{ @@ -2524,13 +2524,29 @@ pub fn compile( let mut types = vec![]; ProgramABI::Fuel(time_expr!( "generate JSON ABI program", - generate_json_abi_program(typed_program, engines.te(), &mut types) + fuel_json_abi::generate_json_abi_program(typed_program, engines.te(), &mut types) )) } - BuildTarget::EVM => match &asm_res.value { - Some(ref v) => v.0.abi.as_ref().unwrap().clone(), - None => todo!(), - }, + BuildTarget::EVM => { + // Merge the ABI output of ASM gen with ABI gen to handle internal constructors + // generated by the ASM backend. + let mut ops = match &asm_res.value { + Some(ref asm) => match &asm.0.abi { + Some(ProgramABI::Evm(ops)) => ops.clone(), + _ => vec![], + }, + _ => vec![], + }; + + let abi = time_expr!( + "generate JSON ABI program", + evm_json_abi::generate_json_abi_program(typed_program, engines.te()) + ); + + ops.extend(abi.into_iter()); + + ProgramABI::Evm(ops) + } }; let entries = asm_res diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index 191235145aa..f55d159522c 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -13,15 +13,19 @@ clap = { version = "3.1", features = ["derive"] } derivative = "2.2.0" dirs = "3.0" either = "1.6" -etk-asm = { version = "0.2.1", features = ["backtraces"] } +ethabi = { package = "fuel-ethabi", version = "18.0.0" } +etk-asm = { package = "fuel-etk-asm", version = "0.3.0-dev", features = ["backtraces"] } +etk-dasm = { package = "fuel-etk-dasm", version = "0.3.0-dev" } +etk-ops = { package = "fuel-etk-ops", version = "0.3.0-dev" } fuel-abi-types = "0.1" -fuel-ethabi = { version = "18.0.0" } fuel-vm = { version = "0.22", features = ["serde"] } hashbrown = "0.13.1" hex = { version = "0.4", optional = true } im = "15.0" itertools = "0.10" lazy_static = "1.4" +pest = "2.1.3" +pest_derive = "2.1" petgraph = "0.6" rustc-hash = "1.1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/sway-core/src/abi_generation/evm_json_abi.rs b/sway-core/src/abi_generation/evm_json_abi.rs new file mode 100644 index 00000000000..3c073993062 --- /dev/null +++ b/sway-core/src/abi_generation/evm_json_abi.rs @@ -0,0 +1,194 @@ +use sway_types::integer_bits::IntegerBits; + +use crate::{ + asm_generation::EvmAbiResult, + language::ty::{TyFunctionDeclaration, TyProgram, TyProgramKind}, + TypeArgument, TypeEngine, TypeId, TypeInfo, +}; + +pub fn generate_json_abi_program(program: &TyProgram, type_engine: &TypeEngine) -> EvmAbiResult { + match &program.kind { + TyProgramKind::Contract { abi_entries, .. } => abi_entries + .iter() + .map(|x| generate_json_abi_function(x, type_engine)) + .collect(), + TyProgramKind::Script { main_function, .. } + | TyProgramKind::Predicate { main_function, .. } => { + vec![generate_json_abi_function(main_function, type_engine)] + } + _ => vec![], + } +} + +/// Gives back a string that represents the type, considering what it resolves to +fn get_json_type_str( + type_id: &TypeId, + type_engine: &TypeEngine, + resolved_type_id: TypeId, +) -> String { + if type_id.is_generic_parameter(type_engine, resolved_type_id) { + format!( + "generic {}", + json_abi_str(&type_engine.get(*type_id), type_engine) + ) + } else { + match (type_engine.get(*type_id), type_engine.get(resolved_type_id)) { + (TypeInfo::Custom { .. }, TypeInfo::Struct { .. }) => { + format!( + "struct {}", + json_abi_str(&type_engine.get(*type_id), type_engine) + ) + } + (TypeInfo::Custom { .. }, TypeInfo::Enum { .. }) => { + format!( + "enum {}", + json_abi_str(&type_engine.get(*type_id), type_engine) + ) + } + (TypeInfo::Tuple(fields), TypeInfo::Tuple(resolved_fields)) => { + assert_eq!(fields.len(), resolved_fields.len()); + let field_strs = fields + .iter() + .map(|_| "_".to_string()) + .collect::>(); + format!("({})", field_strs.join(", ")) + } + (TypeInfo::Array(_, count), TypeInfo::Array(_, resolved_count)) => { + assert_eq!(count.val(), resolved_count.val()); + format!("[_; {}]", count.val()) + } + (TypeInfo::Custom { .. }, _) => { + format!( + "generic {}", + json_abi_str(&type_engine.get(*type_id), type_engine) + ) + } + _ => json_abi_str(&type_engine.get(*type_id), type_engine), + } + } +} + +pub fn json_abi_str(type_info: &TypeInfo, type_engine: &TypeEngine) -> String { + use TypeInfo::*; + match type_info { + Unknown => "unknown".into(), + UnknownGeneric { name, .. } => name.to_string(), + TypeInfo::Placeholder(_) => "_".to_string(), + Str(x) => format!("str[{}]", x.val()), + UnsignedInteger(x) => match x { + IntegerBits::Eight => "uint8", + IntegerBits::Sixteen => "uint16", + IntegerBits::ThirtyTwo => "uint32", + IntegerBits::SixtyFour => "uint64", + } + .into(), + Boolean => "bool".into(), + Custom { name, .. } => name.to_string(), + Tuple(fields) => { + let field_strs = fields + .iter() + .map(|field| json_abi_str_type_arg(field, type_engine)) + .collect::>(); + format!("({})", field_strs.join(", ")) + } + SelfType => "Self".into(), + B256 => "uint256".into(), + Numeric => "u64".into(), // u64 is the default + Contract => "contract".into(), + ErrorRecovery => "unknown due to error".into(), + Enum { name, .. } => { + format!("enum {name}") + } + Struct { name, .. } => { + format!("struct {name}") + } + ContractCaller { abi_name, .. } => { + format!("contract caller {abi_name}") + } + Array(elem_ty, length) => { + format!( + "{}[{}]", + json_abi_str_type_arg(elem_ty, type_engine), + length.val() + ) + } + Storage { .. } => "contract storage".into(), + RawUntypedPtr => "raw untyped ptr".into(), + RawUntypedSlice => "raw untyped slice".into(), + } +} + +pub fn json_abi_param_type(type_info: &TypeInfo, type_engine: &TypeEngine) -> ethabi::ParamType { + use TypeInfo::*; + match type_info { + Str(x) => ethabi::ParamType::FixedArray(Box::new(ethabi::ParamType::String), x.val()), + UnsignedInteger(x) => match x { + IntegerBits::Eight => ethabi::ParamType::Uint(8), + IntegerBits::Sixteen => ethabi::ParamType::Uint(16), + IntegerBits::ThirtyTwo => ethabi::ParamType::Uint(32), + IntegerBits::SixtyFour => ethabi::ParamType::Uint(64), + }, + Boolean => ethabi::ParamType::Bool, + B256 => ethabi::ParamType::Uint(256), + Contract => ethabi::ParamType::Address, + Enum { .. } => ethabi::ParamType::Uint(8), + Tuple(fields) => ethabi::ParamType::Tuple( + fields + .iter() + .map(|f| json_abi_param_type(&type_engine.get(f.type_id), type_engine)) + .collect::>(), + ), + Struct { fields, .. } => ethabi::ParamType::Tuple( + fields + .iter() + .map(|f| json_abi_param_type(&type_engine.get(f.type_id), type_engine)) + .collect::>(), + ), + Array(elem_ty, ..) => ethabi::ParamType::Array(Box::new(json_abi_param_type( + &type_engine.get(elem_ty.type_id), + type_engine, + ))), + _ => panic!("cannot convert type to Solidity ABI param type: {type_info:?}",), + } +} + +pub(self) fn generate_json_abi_function( + fn_decl: &TyFunctionDeclaration, + type_engine: &TypeEngine, +) -> ethabi::operation::Operation { + // A list of all `ethabi::Param`s needed for inputs + let input_types = fn_decl + .parameters + .iter() + .map(|x| ethabi::Param { + name: x.name.to_string(), + kind: ethabi::ParamType::Address, + internal_type: Some(get_json_type_str(&x.type_id, type_engine, x.type_id)), + }) + .collect::>(); + + // The single `ethabi::Param` needed for the output + let output_type = ethabi::Param { + name: String::default(), + kind: ethabi::ParamType::Address, + internal_type: Some(get_json_type_str( + &fn_decl.return_type, + type_engine, + fn_decl.return_type, + )), + }; + + // Generate the ABI data for the function + #[allow(deprecated)] + ethabi::operation::Operation::Function(ethabi::Function { + name: fn_decl.name.as_str().to_string(), + inputs: input_types, + outputs: vec![output_type], + constant: None, + state_mutability: ethabi::StateMutability::Payable, + }) +} + +pub(self) fn json_abi_str_type_arg(type_arg: &TypeArgument, type_engine: &TypeEngine) -> String { + json_abi_str(&type_engine.get(type_arg.type_id), type_engine) +} diff --git a/sway-core/src/abi_generation/mod.rs b/sway-core/src/abi_generation/mod.rs index d0bd9cb6942..b10a318da36 100644 --- a/sway-core/src/abi_generation/mod.rs +++ b/sway-core/src/abi_generation/mod.rs @@ -1,2 +1,2 @@ -mod fuel_json_abi; -pub use fuel_json_abi::*; +pub mod evm_json_abi; +pub mod fuel_json_abi; diff --git a/sway-core/src/asm_generation/evm/evm_asm_builder.rs b/sway-core/src/asm_generation/evm/evm_asm_builder.rs index fabb02577da..6119ba18e62 100644 --- a/sway-core/src/asm_generation/evm/evm_asm_builder.rs +++ b/sway-core/src/asm_generation/evm/evm_asm_builder.rs @@ -10,17 +10,13 @@ use crate::{ error::*, metadata::MetadataManager, }; - +use etk_ops::london::*; use sway_error::error::CompileError; use sway_ir::{Context, *}; use sway_types::Span; use etk_asm::{asm::Assembler, ops::*}; -mod ethabi { - pub use fuel_ethabi::*; -} - /// A smart contract is created by sending a transaction with an empty "to" field. /// When this is done, the Ethereum virtual machine (EVM) runs the bytecode which is /// set in the init byte array which is a field that can contain EVM bytecode @@ -64,6 +60,9 @@ pub struct EvmAsmBuilder<'ir> { // Monotonically increasing unique identifier for label generation. label_idx: usize, + + // In progress EVM asm section. + pub(super) cur_section: Option, } #[derive(Default, Debug)] @@ -77,12 +76,12 @@ impl EvmAsmSection { Self::default() } - pub fn size(&self) -> u32 { + pub fn size(&self) -> usize { let mut asm = Assembler::new(); if asm.push_all(self.ops.clone()).is_err() { panic!("Could not size EVM assembly section"); } - asm.take().len() as u32 + asm.take().len() } } @@ -112,7 +111,7 @@ impl<'ir> AsmBuilder for EvmAsmBuilder<'ir> { #[allow(dead_code)] impl<'ir> EvmAsmBuilder<'ir> { pub fn new(program_kind: ProgramKind, context: &'ir Context) -> Self { - let mut b = EvmAsmBuilder { + Self { program_kind, sections: Vec::new(), func_label_map: HashMap::new(), @@ -120,10 +119,8 @@ impl<'ir> EvmAsmBuilder<'ir> { context, md_mgr: MetadataManager::default(), label_idx: 0, - }; - let s = b.generate_function(); - b.sections.push(s); - b + cur_section: None, + } } pub fn finalize(&self) -> AsmBuilderResult { @@ -138,8 +135,10 @@ impl<'ir> EvmAsmBuilder<'ir> { global_abi.append(&mut section.abi.clone()); if it.peek().is_some() { - size += AbstractOp::Op(Op::Invalid).size().unwrap(); - global_ops.push(AbstractOp::Op(Op::Invalid)); + size += AbstractOp::Op(Op::Invalid(etk_ops::london::Invalid)) + .size() + .unwrap(); + global_ops.push(AbstractOp::Op(Op::Invalid(etk_ops::london::Invalid))); } } @@ -161,8 +160,8 @@ impl<'ir> EvmAsmBuilder<'ir> { fn generate_constructor( &self, is_payable: bool, - data_size: u32, - data_offset: u32, + data_size: usize, + data_offset: usize, ) -> EvmAsmSection { // For more details and explanations see: // https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-5-the-smart-contract-creation-process-cb7b6133b855. @@ -186,33 +185,40 @@ impl<'ir> EvmAsmBuilder<'ir> { // jumpdest // pop - s.ops.push(AbstractOp::new(Op::CallValue).unwrap()); - s.ops.push(AbstractOp::new(Op::Dup1).unwrap()); - s.ops.push(AbstractOp::new(Op::IsZero).unwrap()); + s.ops.push(AbstractOp::new(Op::CallValue(CallValue))); + s.ops.push(AbstractOp::new(Op::Dup1(Dup1))); + s.ops.push(AbstractOp::new(Op::IsZero(IsZero))); let tag_label = "tag_1"; + s.ops.push(AbstractOp::new(Op::Push1(Push1(Imm::with_label( + tag_label, + ))))); + s.ops.push(AbstractOp::new(Op::JumpI(JumpI))); s.ops - .push(AbstractOp::Op(Op::with_label(Op::Push1(()), tag_label))); - s.ops.push(AbstractOp::new(Op::JumpI).unwrap()); - s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x00]).unwrap()); - s.ops.push(AbstractOp::new(Op::Dup1).unwrap()); - s.ops.push(AbstractOp::new(Op::Revert).unwrap()); + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x00.into()), + ))))); + s.ops.push(AbstractOp::new(Op::Dup1(Dup1))); + s.ops.push(AbstractOp::new(Op::Revert(Revert))); s.ops.push(AbstractOp::Label("tag_1".into())); - s.ops.push(AbstractOp::new(Op::JumpDest).unwrap()); - s.ops.push(AbstractOp::Op(Op::Pop)); + s.ops.push(AbstractOp::new(Op::JumpDest(JumpDest))); + s.ops.push(AbstractOp::Op(Op::Pop(Pop))); } self.copy_contract_code_to_memory(&mut s, data_size, data_offset); + s.abi.push(ethabi::operation::Operation::Constructor( + ethabi::Constructor { inputs: vec![] }, + )); + s } fn copy_contract_code_to_memory( &self, s: &mut EvmAsmSection, - data_size: u32, - data_offset: u32, + data_size: usize, + data_offset: usize, ) { // Copy contract code into memory, and return. // push1 dataSize @@ -225,42 +231,21 @@ impl<'ir> EvmAsmBuilder<'ir> { s.ops.push(AbstractOp::Push(Imm::from(Terminal::Number( data_size.into(), )))); - s.ops.push(AbstractOp::new(Op::Dup1).unwrap()); + s.ops.push(AbstractOp::new(Op::Dup1(Dup1))); s.ops.push(AbstractOp::Push(Imm::from(Terminal::Number( data_offset.into(), )))); s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x00]).unwrap()); - s.ops.push(AbstractOp::Op(Op::CodeCopy)); - s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x00]).unwrap()); - s.ops.push(AbstractOp::Op(Op::Return)); - } - - fn generate_function(&mut self) -> EvmAsmSection { - let mut s = EvmAsmSection::new(); + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x00.into()), + ))))); + s.ops.push(AbstractOp::Op(Op::CodeCopy(CodeCopy))); - // push1 0x80 # selector("conduct_auto(uint256,uint256,uint256)") - // push1 0x40 - // mstore - // push1 0x00 - // dup1 - // revert - s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x80]).unwrap()); s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x40]).unwrap()); - s.ops.push(AbstractOp::new(Op::MStore).unwrap()); - s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x00]).unwrap()); - s.ops.push(AbstractOp::new(Op::Dup1).unwrap()); - s.ops.push(AbstractOp::new(Op::Revert).unwrap()); - - s.abi.push(ethabi::operation::Operation::Constructor( - ethabi::Constructor { inputs: vec![] }, - )); - - s + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x00.into()), + ))))); + s.ops.push(AbstractOp::Op(Op::Return(Return))); } fn setup_free_memory_pointer(&self, s: &mut EvmAsmSection) { @@ -276,11 +261,16 @@ impl<'ir> EvmAsmBuilder<'ir> { // push1 0x80 // push1 0x40 // mstore + s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x80]).unwrap()); + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x80.into()), + ))))); s.ops - .push(AbstractOp::with_immediate(Op::Push1(()), &[0x40]).unwrap()); - s.ops.push(AbstractOp::new(Op::MStore).unwrap()); + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x40.into()), + ))))); + s.ops.push(AbstractOp::new(Op::MStore(MStore))); } fn empty_span() -> Span { @@ -555,7 +545,18 @@ impl<'ir> EvmAsmBuilder<'ir> { } fn compile_ret_from_entry(&mut self, instr_val: &Value, ret_val: &Value, ret_type: &Type) { - todo!(); + if ret_type.is_unit(self.context) { + // Unit returns should always be zero, although because they can be omitted from + // functions, the register is sometimes uninitialized. Manually return zero in this + // case. + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::Op(Op::Return(Return))); + } else { + todo!(); + } } fn compile_revert(&mut self, instr_val: &Value, revert_val: &Value) { @@ -615,6 +616,87 @@ impl<'ir> EvmAsmBuilder<'ir> { } pub fn compile_function(&mut self, function: Function) -> CompileResult<()> { + self.cur_section = Some(EvmAsmSection::new()); + + // push1 0x80 + // push1 0x40 + // mstore + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x80.into()), + ))))); + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x40.into()), + ))))); + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::new(Op::MStore(MStore))); + + //self.init_locals(function); + let func_is_entry = function.is_entry(self.context); + + // Compile instructions. + let mut warnings = Vec::new(); + let mut errors = Vec::new(); + for block in function.block_iter(self.context) { + self.insert_block_label(block); + for instr_val in block.instruction_iter(self.context) { + check!( + self.compile_instruction(&instr_val, func_is_entry), + return err(warnings, errors), + warnings, + errors + ); + } + } + + // push1 0x00 + // dup1 + // revert + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::new(Op::Push1(Push1(Imm::with_expression( + Expression::Terminal(0x00.into()), + ))))); + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::new(Op::Dup1(Dup1))); + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::new(Op::Revert(Revert))); + + // Generate the ABI. + #[allow(deprecated)] + self.cur_section + .as_mut() + .unwrap() + .abi + .push(ethabi::operation::Operation::Function(ethabi::Function { + name: function.get_name(self.context).to_string(), + inputs: vec![], + outputs: vec![], + constant: None, + state_mutability: ethabi::StateMutability::NonPayable, + })); + + self.sections.push(self.cur_section.take().unwrap()); + self.cur_section = None; + ok((), vec![], vec![]) } @@ -625,4 +707,23 @@ impl<'ir> EvmAsmBuilder<'ir> { pub(super) fn compile_ret_from_call(&mut self, instr_val: &Value, ret_val: &Value) { todo!(); } + + pub(super) fn insert_block_label(&mut self, block: Block) { + if &block.get_label(self.context) != "entry" { + let label = self.block_to_label(&block); + self.cur_section + .as_mut() + .unwrap() + .ops + .push(AbstractOp::Label(label.to_string())); + } + } + + fn block_to_label(&mut self, block: &Block) -> Label { + self.block_label_map.get(block).cloned().unwrap_or_else(|| { + let label = self.get_label(); + self.block_label_map.insert(*block, label); + label + }) + } } diff --git a/sway-core/src/asm_generation/programs.rs b/sway-core/src/asm_generation/programs.rs index a81ad149d7e..5b38f937b77 100644 --- a/sway-core/src/asm_generation/programs.rs +++ b/sway-core/src/asm_generation/programs.rs @@ -13,10 +13,6 @@ use crate::{ decl_engine::DeclId, }; -mod ethabi { - pub use fuel_ethabi::*; -} - type SelectorOpt = Option<[u8; 4]>; type FnName = String; type ImmOffset = u64; diff --git a/sway-core/src/asm_generation/programs/final.rs b/sway-core/src/asm_generation/programs/final.rs index 2feee8db3ff..4326b6704ae 100644 --- a/sway-core/src/asm_generation/programs/final.rs +++ b/sway-core/src/asm_generation/programs/final.rs @@ -49,7 +49,37 @@ impl std::fmt::Display for FinalProgram { FinalProgram::Fuel { data_section, ops, .. } => write!(f, "{ops:?}\n{data_section}"), - FinalProgram::Evm { ops, .. } => write!(f, "{ops:?}"), + FinalProgram::Evm { ops, .. } => { + let mut separator = etk_dasm::blocks::basic::Separator::new(); + + let ctx = etk_asm::ops::Context::new(); + let concretized_ops = ops + .iter() + .map(|op| etk_asm::disasm::Offset { + item: op.clone().concretize(ctx).unwrap(), + offset: 0, + }) + .collect::>(); + separator.push_all(concretized_ops); + + let basic_blocks = separator + .take() + .into_iter() + .chain(separator.finish().into_iter()); + + for block in basic_blocks { + let mut offset = block.offset; + for op in block.ops { + let len = op.size(); + let off = etk_asm::disasm::Offset::new(offset, etk_dasm::DisplayOp(op)); + offset += len; + + writeln!(f, "{}", off.item)?; + } + } + + Ok(()) + } } } } diff --git a/sway-core/src/build_config.rs b/sway-core/src/build_config.rs index a1aec148535..5e80915b121 100644 --- a/sway-core/src/build_config.rs +++ b/sway-core/src/build_config.rs @@ -2,7 +2,9 @@ use std::{path::PathBuf, sync::Arc}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, clap::ValueEnum)] +#[derive( + Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, clap::ValueEnum, +)] pub enum BuildTarget { #[default] Fuel, diff --git a/test/src/e2e_vm_tests/harness.rs b/test/src/e2e_vm_tests/harness.rs index cc689a32281..4427c9028e8 100644 --- a/test/src/e2e_vm_tests/harness.rs +++ b/test/src/e2e_vm_tests/harness.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use colored::Colorize; use forc_client::ops::{ deploy::{cmd::DeployCommand, op::deploy}, @@ -155,15 +155,32 @@ pub(crate) fn runs_in_vm( )) } BuildTarget::EVM => { - let mut database = revm::InMemoryDB::default(); - let mut env = revm::Env::default(); - env.tx.data = bytes::Bytes::from(script.bytecode.into_boxed_slice()); let mut evm = revm::new(); - evm.database(&mut database); - evm.env = env; + evm.database(revm::InMemoryDB::default()); + evm.env = revm::Env::default(); + // Transaction to create the smart contract + evm.env.tx.transact_to = revm::TransactTo::create(); + evm.env.tx.data = bytes::Bytes::from(script.bytecode.into_boxed_slice()); let result = evm.transact_commit(); - Ok(VMExecutionResult::Evm(result)) + + match result.out { + revm::TransactOut::None => Err(anyhow!("Could not create smart contract")), + revm::TransactOut::Call(_) => todo!(), + revm::TransactOut::Create(ref _bytes, account_opt) => { + match account_opt { + Some(account) => { + evm.env.tx.transact_to = revm::TransactTo::Call(account); + + // Now issue a call. + //evm.env.tx. = bytes::Bytes::from(script.bytecode.into_boxed_slice()); + let result = evm.transact_commit(); + Ok(VMExecutionResult::Evm(result)) + } + None => todo!(), + } + } + } } } } diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index 09f3191de59..023a1b8cc68 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -13,6 +13,7 @@ use core::fmt; use fuel_vm::fuel_tx; use fuel_vm::prelude::*; use regex::Regex; +use std::collections::HashSet; use std::io::stdout; use std::io::Write; use std::{ @@ -20,6 +21,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use sway_core::BuildTarget; use tokio::sync::Mutex; use tracing::Instrument; @@ -62,6 +64,7 @@ struct TestDescription { contract_paths: Vec, validate_abi: bool, validate_storage_slots: bool, + supported_targets: HashSet, checker: filecheck::Checker, } @@ -96,6 +99,7 @@ impl TestContext { validate_abi, validate_storage_slots, checker, + .. } = test; match category { @@ -380,6 +384,12 @@ pub async fn run(filter_config: &FilterConfig, run_config: &RunConfig) -> Result stdout().flush().unwrap(); let mut output = String::new(); + + // Skip the test if its not compatible with the current build target. + if !test.supported_targets.contains(&run_config.build_target) { + continue; + } + let result = if !filter_config.first_only { context .run(test, &mut output) @@ -632,6 +642,23 @@ fn parse_test_toml(path: &Path) -> Result { .map(|s| s.to_owned()) .unwrap(); + // Check for supported build target for each test. For now we assume that the + // the default is that only Fuel VM target is supported. Once the other targets + // get to a fully usable state, we should update this. + let supported_targets = toml_content + .get("supported_targets") + .map(|v| v.as_array().cloned().unwrap_or(vec![])) + .unwrap_or(vec![]) + .iter() + .map(get_test_abi_from_value) + .collect::>>()?; + + let supported_targets = HashSet::from_iter(if supported_targets.is_empty() { + vec![BuildTarget::Fuel] + } else { + supported_targets + }); + Ok(TestDescription { name, category, @@ -640,10 +667,22 @@ fn parse_test_toml(path: &Path) -> Result { contract_paths, validate_abi, validate_storage_slots, + supported_targets, checker, }) } +fn get_test_abi_from_value(value: &toml::Value) -> Result { + match value.as_str() { + Some(target) => match target { + "fuel" => Ok(BuildTarget::Fuel), + "evm" => Ok(BuildTarget::EVM), + _ => Err(anyhow!(format!("Unknown build target: {target}"))), + }, + None => Err(anyhow!("Invalid TOML value")), + } +} + fn get_expected_result(toml_content: &toml::Value) -> Result { fn get_action_value(action: &toml::Value, expected_value: &toml::Value) -> Result { match (action.as_str(), expected_value) { diff --git a/test/src/e2e_vm_tests/test_programs/README.md b/test/src/e2e_vm_tests/test_programs/README.md index 63d42af3cff..a9677cd5f12 100644 --- a/test/src/e2e_vm_tests/test_programs/README.md +++ b/test/src/e2e_vm_tests/test_programs/README.md @@ -51,6 +51,10 @@ be compiled and deployed. It is important that these paths remain relative to t Some tests also require their ABI is verified. To indicate this the `validate_abi` field may be specified, as a boolean value. +## supported_targets + +Some tests are only compatible with some build targets. To indicate this the `supported_targets` field may be specified, as an array value. + ## FileCheck for 'fail' tests The tests in the `fail` category _must_ employ verification using pattern matching via the [FileCheck](https://docs.rs/filecheck/latest/filecheck/) diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/Forc.lock new file mode 100644 index 00000000000..d30eb8735dd --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'evm_basic' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/Forc.toml new file mode 100644 index 00000000000..863e38be972 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +implicit-std = false +license = "Apache-2.0" +name = "evm_basic" +target = "evm" diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/src/main.sw new file mode 100644 index 00000000000..d8b8bc64c3b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/src/main.sw @@ -0,0 +1,4 @@ +script; + +fn main() { +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/test.toml new file mode 100644 index 00000000000..60efb1f5c51 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/evm/evm_basic/test.toml @@ -0,0 +1,2 @@ +category = "compile" +supported-targets = ["evm"]