From b97e4f817ace03b9f16fec33f366f183c99d82ed Mon Sep 17 00:00:00 2001 From: Sean Billig Date: Mon, 29 Nov 2021 16:41:43 -0800 Subject: [PATCH] Use item dependency graph for yul generation --- Cargo.lock | 31 +- crates/analyzer/Cargo.toml | 1 + crates/analyzer/src/builtins.rs | 8 +- crates/analyzer/src/context.rs | 24 +- crates/analyzer/src/db.rs | 15 +- crates/analyzer/src/db/queries/contracts.rs | 82 +++++- crates/analyzer/src/db/queries/functions.rs | 124 +++++++- crates/analyzer/src/db/queries/structs.rs | 39 ++- crates/analyzer/src/namespace/items.rs | 183 +++++++++++- crates/analyzer/src/namespace/scopes.rs | 4 +- crates/analyzer/src/namespace/types.rs | 12 +- crates/analyzer/src/traversal/expressions.rs | 69 +++-- crates/analyzer/tests/analysis.rs | 21 +- .../analysis__abi_encoding_stress.snap | 18 +- .../analysis__address_bytes10_map.snap | 4 +- .../tests/snapshots/analysis__assert.snap | 4 +- .../snapshots/analysis__basic_ingot.snap | 275 +++++++++++++++--- .../analysis__data_copying_stress.snap | 12 +- .../snapshots/analysis__erc20_token.snap | 6 +- .../analysis__external_contract.snap | 6 +- .../tests/snapshots/analysis__guest_book.snap | 4 +- .../tests/snapshots/analysis__revert.snap | 4 +- .../analysis__sized_vals_in_sto.snap | 10 +- .../tests/snapshots/analysis__structs.snap | 6 +- .../snapshots/analysis__tuple_stress.snap | 6 +- .../snapshots/analysis__two_contracts.snap | 242 +++++++++------ .../snapshots/analysis__type_aliases.snap | 4 +- .../tests/snapshots/analysis__uniswap.snap | 44 +-- crates/driver/src/lib.rs | 3 +- .../fixtures/features/two_contracts.fe | 22 +- .../fixtures/ingots/basic_ingot/src/bing.fe | 7 +- .../ingots/basic_ingot/src/ding/dong.fe | 2 +- .../fixtures/ingots/basic_ingot/src/main.fe | 19 +- crates/test-utils/src/lib.rs | 7 +- crates/tests/src/features.rs | 4 - crates/tests/src/ingots.rs | 16 + crates/tests/src/runtime.rs | 69 ----- crates/yulgen/Cargo.toml | 1 + crates/yulgen/src/constructor.rs | 31 +- crates/yulgen/src/context.rs | 38 +-- crates/yulgen/src/db.rs | 48 ++- crates/yulgen/src/db/queries.rs | 8 +- crates/yulgen/src/db/queries/contracts.rs | 194 ++++++++++++ crates/yulgen/src/db/queries/events.rs | 22 ++ crates/yulgen/src/db/queries/functions.rs | 202 +++++++++++++ crates/yulgen/src/db/queries/structs.rs | 135 +++++++++ crates/yulgen/src/mappers/contracts.rs | 111 ------- crates/yulgen/src/mappers/expressions.rs | 68 ++--- crates/yulgen/src/mappers/functions.rs | 45 +-- crates/yulgen/src/mappers/mod.rs | 1 - crates/yulgen/src/mappers/module.rs | 14 +- crates/yulgen/src/names/mod.rs | 39 --- crates/yulgen/src/operations/structs.rs | 38 +-- crates/yulgen/src/runtime/abi_dispatcher.rs | 30 +- crates/yulgen/src/runtime/functions/abi.rs | 83 ++---- .../yulgen/src/runtime/functions/contracts.rs | 77 ----- crates/yulgen/src/runtime/functions/mod.rs | 1 - .../yulgen/src/runtime/functions/structs.rs | 93 ------ crates/yulgen/src/runtime/mod.rs | 266 ----------------- crates/yulgen/src/types.rs | 4 +- ...decode_data_address_bool_mem_function.snap | 34 ++- ..._bool_address_bytes_calldata_function.snap | 97 ++++-- .../snapshots/yulgen__abi_dispatcher.snap | 6 +- crates/yulgen/tests/yulgen.rs | 44 +-- newsfragments/562.feature.md | 2 - newsfragments/596.bugfix.md | 2 + newsfragments/596.internal.md | 9 + 67 files changed, 1871 insertions(+), 1279 deletions(-) create mode 100644 crates/yulgen/src/db/queries/contracts.rs create mode 100644 crates/yulgen/src/db/queries/events.rs create mode 100644 crates/yulgen/src/db/queries/functions.rs create mode 100644 crates/yulgen/src/db/queries/structs.rs delete mode 100644 crates/yulgen/src/mappers/contracts.rs delete mode 100644 crates/yulgen/src/runtime/functions/structs.rs create mode 100644 newsfragments/596.bugfix.md create mode 100644 newsfragments/596.internal.md diff --git a/Cargo.lock b/Cargo.lock index f8447e90c8..ba7a702548 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,6 +514,7 @@ dependencies = [ "num-bigint 0.3.2", "num-traits", "parking_lot_core", + "petgraph", "pretty_assertions", "rstest", "salsa", @@ -672,6 +673,7 @@ dependencies = [ "num-bigint 0.4.0", "pretty_assertions", "salsa", + "smol_str", "wasm-bindgen-test", "yultsur", ] @@ -688,6 +690,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" + [[package]] name = "fnv" version = "1.0.7" @@ -802,9 +810,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "if_chain" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7280c75fb2e2fc47080ec80ccc481376923acb04501957fc38f935c3de5088" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "impl-codec" @@ -1147,6 +1155,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -1573,6 +1591,15 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +[[package]] +name = "smol_str" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61d15c83e300cce35b7c8cd39ff567c1ef42dde6d4a1a38dbdbf9a59902261bd" +dependencies = [ + "serde", +] + [[package]] name = "solc" version = "0.1.0" diff --git a/crates/analyzer/Cargo.toml b/crates/analyzer/Cargo.toml index 805bb8db03..a4617d130a 100644 --- a/crates/analyzer/Cargo.toml +++ b/crates/analyzer/Cargo.toml @@ -20,6 +20,7 @@ parking_lot_core = { version = "=0.8.0" } # used by salsa; version pinned for wa indexmap = "1.6.2" if_chain = "1.0.1" smallvec = { version = "1.6.1", features = ["union"] } +petgraph = "0.6.0" [dev-dependencies] insta = "1.7.1" diff --git a/crates/analyzer/src/builtins.rs b/crates/analyzer/src/builtins.rs index b92e911be3..2fe63df477 100644 --- a/crates/analyzer/src/builtins.rs +++ b/crates/analyzer/src/builtins.rs @@ -8,7 +8,9 @@ pub enum ValueMethod { AbiEncode, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumString, AsRefStr, Hash, EnumIter)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, AsRefStr, EnumIter, +)] #[strum(serialize_all = "snake_case")] pub enum GlobalFunction { Keccak256, @@ -33,7 +35,9 @@ impl ContractTypeMethod { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumString, EnumIter, AsRefStr)] +#[derive( + Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, EnumString, EnumIter, AsRefStr, +)] #[strum(serialize_all = "lowercase")] pub enum GlobalObject { Block, diff --git a/crates/analyzer/src/context.rs b/crates/analyzer/src/context.rs index 303535901c..9f2a470d61 100644 --- a/crates/analyzer/src/context.rs +++ b/crates/analyzer/src/context.rs @@ -8,7 +8,7 @@ pub use fe_common::diagnostics::Label; use fe_common::Span; use fe_parser::ast; use fe_parser::node::NodeId; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; use std::collections::HashMap; use std::fmt; use std::fmt::{Debug, Display}; @@ -195,6 +195,7 @@ impl Location { pub struct FunctionBody { pub expressions: IndexMap, pub emits: IndexMap, + pub string_literals: IndexSet, // for yulgen // This is the id of the VarDecl TypeDesc node pub var_decl_types: IndexMap, @@ -262,7 +263,10 @@ impl fmt::Display for ExpressionAttributes { #[derive(Clone, Debug, PartialEq, Eq)] pub enum CallType { BuiltinFunction(GlobalFunction), - BuiltinValueMethod(ValueMethod), + BuiltinValueMethod { + method: ValueMethod, + typ: Type, + }, // create, create2 (will be methods of the context struct soon) BuiltinAssociatedFunction { @@ -280,6 +284,10 @@ pub enum CallType { class: Class, method: FunctionId, }, + External { + contract: ContractId, + function: FunctionId, + }, Pure(FunctionId), TypeConstructor(Type), } @@ -289,23 +297,25 @@ impl CallType { use CallType::*; match self { BuiltinFunction(_) - | BuiltinValueMethod(_) + | BuiltinValueMethod { .. } | TypeConstructor(_) | BuiltinAssociatedFunction { .. } => None, - AssociatedFunction { function: id, .. } | ValueMethod { method: id, .. } | Pure(id) => { - Some(*id) - } + AssociatedFunction { function: id, .. } + | ValueMethod { method: id, .. } + | External { function: id, .. } + | Pure(id) => Some(*id), } } pub fn function_name(&self, db: &dyn AnalyzerDb) -> String { match self { CallType::BuiltinFunction(f) => f.as_ref().to_string(), - CallType::BuiltinValueMethod(f) => f.as_ref().to_string(), + CallType::BuiltinValueMethod { method, .. } => method.as_ref().to_string(), CallType::BuiltinAssociatedFunction { function, .. } => function.as_ref().to_string(), CallType::AssociatedFunction { function: id, .. } | CallType::ValueMethod { method: id, .. } + | CallType::External { function: id, .. } | CallType::Pure(id) => id.name(db), CallType::TypeConstructor(typ) => typ.to_string(), } diff --git a/crates/analyzer/src/db.rs b/crates/analyzer/src/db.rs index 511d769a3c..c28d3da1bb 100644 --- a/crates/analyzer/src/db.rs +++ b/crates/analyzer/src/db.rs @@ -1,8 +1,8 @@ use crate::context::{Analysis, FunctionBody}; use crate::errors::TypeError; use crate::namespace::items::{ - self, ContractFieldId, ContractId, EventId, FunctionId, GlobalId, IngotId, Item, - ModuleConstantId, ModuleId, StructFieldId, StructId, TypeAliasId, + self, ContractFieldId, ContractId, DepGraphWrapper, EventId, FunctionId, GlobalId, IngotId, + Item, ModuleConstantId, ModuleId, StructFieldId, StructId, TypeAliasId, }; use crate::namespace::types; use fe_common::Span; @@ -119,12 +119,21 @@ pub trait AnalyzerDb { &self, field: ContractFieldId, ) -> Analysis>; + #[salsa::cycle(queries::contracts::contract_dependency_graph_cycle)] + #[salsa::invoke(queries::contracts::contract_dependency_graph)] + fn contract_dependency_graph(&self, id: ContractId) -> DepGraphWrapper; + #[salsa::cycle(queries::contracts::contract_runtime_dependency_graph_cycle)] + #[salsa::invoke(queries::contracts::contract_runtime_dependency_graph)] + fn contract_runtime_dependency_graph(&self, id: ContractId) -> DepGraphWrapper; // Function #[salsa::invoke(queries::functions::function_signature)] fn function_signature(&self, id: FunctionId) -> Analysis>; #[salsa::invoke(queries::functions::function_body)] fn function_body(&self, id: FunctionId) -> Analysis>; + #[salsa::cycle(queries::functions::function_dependency_graph_cycle)] + #[salsa::invoke(queries::functions::function_dependency_graph)] + fn function_dependency_graph(&self, id: FunctionId) -> DepGraphWrapper; // Struct #[salsa::invoke(queries::structs::struct_type)] @@ -142,6 +151,8 @@ pub trait AnalyzerDb { fn struct_all_functions(&self, id: StructId) -> Rc>; #[salsa::invoke(queries::structs::struct_function_map)] fn struct_function_map(&self, id: StructId) -> Analysis>>; + #[salsa::invoke(queries::structs::struct_dependency_graph)] + fn struct_dependency_graph(&self, id: StructId) -> DepGraphWrapper; // Event #[salsa::invoke(queries::events::event_type)] diff --git a/crates/analyzer/src/db/queries/contracts.rs b/crates/analyzer/src/db/queries/contracts.rs index 780ba0623e..905b435a11 100644 --- a/crates/analyzer/src/db/queries/contracts.rs +++ b/crates/analyzer/src/db/queries/contracts.rs @@ -1,14 +1,16 @@ use crate::context::{AnalyzerContext, NamedThing}; use crate::db::{Analysis, AnalyzerDb}; use crate::errors; -use crate::namespace::items::{self, ContractFieldId, ContractId, EventId, FunctionId, Item}; +use crate::namespace::items::{ + self, ContractFieldId, ContractId, DepGraph, DepGraphWrapper, DepLocality, EventId, FunctionId, + Item, TypeDef, +}; use crate::namespace::scopes::ItemScope; -use crate::namespace::types; +use crate::namespace::types::{self, Contract, Struct, Type}; use crate::traversal::types::type_desc; use fe_common::diagnostics::Label; use fe_parser::ast; use indexmap::map::{Entry, IndexMap}; - use std::rc::Rc; /// A `Vec` of every function defined in the contract, including duplicates and the init function. @@ -277,3 +279,77 @@ pub fn contract_field_type( diagnostics: Rc::new(scope.diagnostics), } } + +pub fn contract_dependency_graph(db: &dyn AnalyzerDb, contract: ContractId) -> DepGraphWrapper { + // A contract depends on the types of its fields, and the things those types depend on. + // Note that this *does not* include the contract's public function graph. + // (See `contract_runtime_dependency_graph` below) + + let fields = contract.fields(db); + let field_types = fields + .values() + .filter_map(|field| match field.typ(db).ok()? { + // We don't want + Type::Contract(Contract { id, .. }) => Some(Item::Type(TypeDef::Contract(id))), + Type::Struct(Struct { id, .. }) => Some(Item::Type(TypeDef::Struct(id))), + // TODO: when tuples can contain non-primitive items, + // we'll have to depend on tuple element types + _ => None, + }) + .collect::>(); + + let root = Item::Type(TypeDef::Contract(contract)); + let mut graph = DepGraph::from_edges( + field_types + .iter() + .map(|item| (root, *item, DepLocality::Local)), + ); + + for item in field_types { + if let Some(subgraph) = item.dependency_graph(db) { + graph.extend(subgraph.all_edges()) + } + } + DepGraphWrapper(Rc::new(graph)) +} + +pub fn contract_dependency_graph_cycle( + _db: &dyn AnalyzerDb, + _cycle: &[String], + _contract: &ContractId, +) -> DepGraphWrapper { + DepGraphWrapper(Rc::new(DepGraph::new())) +} + +pub fn contract_runtime_dependency_graph( + db: &dyn AnalyzerDb, + contract: ContractId, +) -> DepGraphWrapper { + // This is the dependency graph of the (as yet imaginary) `__call__` function, which + // dispatches to the contract's public functions. This should be used when compiling + // the runtime object for a contract. + + let root = Item::Type(TypeDef::Contract(contract)); + let pub_fns = contract + .public_functions(db) + .values() + .map(|fun| (root, Item::Function(*fun), DepLocality::Local)) + .collect::>(); + + let mut graph = DepGraph::from_edges(pub_fns.iter()); + + for (_, item, _) in pub_fns { + if let Some(subgraph) = item.dependency_graph(db) { + graph.extend(subgraph.all_edges()) + } + } + DepGraphWrapper(Rc::new(graph)) +} + +pub fn contract_runtime_dependency_graph_cycle( + _db: &dyn AnalyzerDb, + _cycle: &[String], + _contract: &ContractId, +) -> DepGraphWrapper { + DepGraphWrapper(Rc::new(DepGraph::new())) +} diff --git a/crates/analyzer/src/db/queries/functions.rs b/crates/analyzer/src/db/queries/functions.rs index 1fa2cf7327..a431d26f10 100644 --- a/crates/analyzer/src/db/queries/functions.rs +++ b/crates/analyzer/src/db/queries/functions.rs @@ -1,9 +1,11 @@ -use crate::context::{AnalyzerContext, FunctionBody}; +use crate::context::{AnalyzerContext, CallType, FunctionBody}; use crate::db::{Analysis, AnalyzerDb}; use crate::errors::TypeError; -use crate::namespace::items::{Class, FunctionId}; +use crate::namespace::items::{ + Class, DepGraph, DepGraphWrapper, DepLocality, FunctionId, Item, TypeDef, +}; use crate::namespace::scopes::{BlockScope, BlockScopeType, FunctionScope, ItemScope}; -use crate::namespace::types::{self, FixedSize, SelfDecl}; +use crate::namespace::types::{self, Contract, FixedSize, SelfDecl, Struct, Type}; use crate::traversal::functions::traverse_statements; use crate::traversal::types::type_desc; use fe_common::diagnostics::Label; @@ -24,7 +26,7 @@ pub fn function_signature( let def = &node.kind; let mut scope = ItemScope::new(db, function.module(db)); - let fn_parent = function.parent(db); + let fn_parent = function.class(db); if_chain! { if let Some(Class::Contract(_)) = fn_parent; @@ -215,3 +217,117 @@ fn all_paths_return_or_revert(block: &[Node]) -> bool { false } + +pub fn function_dependency_graph(db: &dyn AnalyzerDb, function: FunctionId) -> DepGraphWrapper { + let root = Item::Function(function); + + // Edges to direct dependencies. + let mut directs = vec![]; + + let sig = function.signature(db); + directs.extend( + sig.return_type + .clone() + .into_iter() + .chain(sig.params.iter().filter_map(|param| param.typ.clone().ok())) + .filter_map(|typ| match typ { + FixedSize::Contract(Contract { id, .. }) => { + // Contract types that are taken as (non-self) args or returned are "external", + // meaning that they're addresses of other contracts, so we don't have direct + // access to their fields, etc. + Some(( + root, + Item::Type(TypeDef::Contract(id)), + DepLocality::External, + )) + } + FixedSize::Struct(Struct { id, .. }) => { + Some((root, Item::Type(TypeDef::Struct(id)), DepLocality::Local)) + } + _ => None, + }), + ); + // A function that takes `self` depends on the type of `self`, so that any + // relevant struct getters/setters are included when compiling. + if let Some(class) = function.class(db) { + directs.push((root, class.as_item(), DepLocality::Local)); + } + + let body = function.body(db); + for calltype in body.calls.values() { + match calltype { + CallType::Pure(function) | CallType::AssociatedFunction { function, .. } => { + directs.push((root, Item::Function(*function), DepLocality::Local)); + } + CallType::ValueMethod { class, method, .. } => { + // Including the "class" type here is probably redundant; the type will + // also be part of the fn sig, or some type decl, or some create/create2 call, or... + directs.push((root, class.as_item(), DepLocality::Local)); + directs.push((root, Item::Function(*method), DepLocality::Local)); + } + CallType::External { contract, function } => { + directs.push((root, Item::Function(*function), DepLocality::External)); + // Probably redundant: + directs.push(( + root, + Item::Type(TypeDef::Contract(*contract)), + DepLocality::External, + )); + } + CallType::TypeConstructor(Type::Struct(Struct { id, .. })) => { + directs.push((root, Item::Type(TypeDef::Struct(*id)), DepLocality::Local)); + } + CallType::TypeConstructor(Type::Contract(Contract { id, .. })) => { + directs.push(( + root, + Item::Type(TypeDef::Contract(*id)), + DepLocality::External, + )); + } + CallType::TypeConstructor(_) => {} + CallType::BuiltinAssociatedFunction { contract, .. } => { + // create/create2 call. The contract type is "external" for dependency graph purposes. + directs.push(( + root, + Item::Type(TypeDef::Contract(*contract)), + DepLocality::External, + )); + } + // Builtin functions aren't part of the dependency graph yet. + CallType::BuiltinFunction(_) | CallType::BuiltinValueMethod { .. } => {} + } + } + + directs.extend( + body.emits + .values() + .map(|event| (root, Item::Event(*event), DepLocality::Local)), + ); + directs.extend(body.var_decl_types.values().filter_map(|typ| match typ { + FixedSize::Contract(Contract { id, .. }) => Some(( + root, + Item::Type(TypeDef::Contract(*id)), + DepLocality::External, + )), + FixedSize::Struct(Struct { id, .. }) => { + Some((root, Item::Type(TypeDef::Struct(*id)), DepLocality::Local)) + } + _ => None, + })); + + let mut graph = DepGraph::from_edges(directs.iter()); + for (_, item, _) in directs { + if let Some(subgraph) = item.dependency_graph(db) { + graph.extend(subgraph.all_edges()) + } + } + DepGraphWrapper(Rc::new(graph)) +} + +pub fn function_dependency_graph_cycle( + _db: &dyn AnalyzerDb, + _cycle: &[String], + _function: &FunctionId, +) -> DepGraphWrapper { + DepGraphWrapper(Rc::new(DepGraph::new())) +} diff --git a/crates/analyzer/src/db/queries/structs.rs b/crates/analyzer/src/db/queries/structs.rs index b22ad3cec6..f7e686f912 100644 --- a/crates/analyzer/src/db/queries/structs.rs +++ b/crates/analyzer/src/db/queries/structs.rs @@ -2,9 +2,12 @@ use crate::builtins; use crate::context::AnalyzerContext; use crate::db::Analysis; use crate::errors::TypeError; -use crate::namespace::items::{self, Function, FunctionId, StructField, StructFieldId, StructId}; +use crate::namespace::items::{ + self, DepGraph, DepGraphWrapper, DepLocality, Function, FunctionId, Item, StructField, + StructFieldId, StructId, TypeDef, +}; use crate::namespace::scopes::ItemScope; -use crate::namespace::types; +use crate::namespace::types::{self, Contract, FixedSize, Struct}; use crate::traversal::types::type_desc; use crate::AnalyzerDb; use fe_parser::ast; @@ -183,3 +186,35 @@ pub fn struct_function_map( diagnostics: Rc::new(scope.diagnostics), } } + +pub fn struct_dependency_graph(db: &dyn AnalyzerDb, struct_: StructId) -> DepGraphWrapper { + // A struct depends on the types of its fields and on everything they depend on. It *does not* + // depend on its public functions; those will only be part of the broader dependency graph if + // they're in the call graph of some public contract function. + + let root = Item::Type(TypeDef::Struct(struct_)); + let fields = struct_ + .fields(db) + .values() + .filter_map(|field| match field.typ(db).ok()? { + FixedSize::Contract(Contract { id, .. }) => Some(( + root, + Item::Type(TypeDef::Contract(id)), + DepLocality::External, + )), + // Not possible yet, but it will be soon + FixedSize::Struct(Struct { id, .. }) => { + Some((root, Item::Type(TypeDef::Struct(id)), DepLocality::Local)) + } + _ => None, + }) + .collect::>(); + + let mut graph = DepGraph::from_edges(fields.iter()); + for (_, item, _) in fields { + if let Some(subgraph) = item.dependency_graph(db) { + graph.extend(subgraph.all_edges()) + } + } + DepGraphWrapper(Rc::new(graph)) +} diff --git a/crates/analyzer/src/namespace/items.rs b/crates/analyzer/src/namespace/items.rs index 89c43c2419..6720f8cfbb 100644 --- a/crates/analyzer/src/namespace/items.rs +++ b/crates/analyzer/src/namespace/items.rs @@ -18,11 +18,10 @@ use std::rc::Rc; /// A named item. This does not include things inside of /// a function body. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] pub enum Item { Ingot(IngotId), Module(ModuleId), - // Constant // TODO: when `const` is implemented Type(TypeDef), // GenericType probably shouldn't be a separate category. // Any of the items inside TypeDef (struct, alias, etc) @@ -39,7 +38,6 @@ pub enum Item { // This should go away soon. The globals (block, msg, etc) will be replaced // with a context struct that'll appear in the fn parameter list. - // `self` should just be removed from here and handled as a special parameter. Object(builtins::GlobalObject), } @@ -115,6 +113,21 @@ impl Item { } } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Option { + // Only ingots *should* be parentless, but built-in items currently don't + match self { + Item::Type(id) => id.parent(db), + Item::GenericType(_) => None, + Item::Event(id) => Some(id.parent(db)), + Item::Function(id) => Some(id.parent(db)), + Item::BuiltinFunction(_) => None, + Item::Object(_) => None, + Item::Constant(id) => Some(id.parent(db)), + Item::Ingot(_) => None, + Item::Module(id) => id.parent(db), + } + } + pub fn resolve_path(&self, db: &dyn AnalyzerDb, path: &ast::Path) -> Analysis> { let mut curr_item = *self; @@ -140,6 +153,40 @@ impl Item { } } + pub fn path(&self, db: &dyn AnalyzerDb) -> Rc> { + // The path is used to generate a yul identifier, + // eg `foo::Bar::new` becomes `$$foo$Bar$new`. + // Right now, the ingot name is the os path, so it could + // be "my project/src". + // For now, we'll just leave the ingot out of the path, + // because we can only compile a single ingot anyway. + match self.parent(db) { + Some(Item::Ingot(_)) | None => Rc::new(vec![self.name(db)]), + Some(parent) => { + let mut path = parent.path(db).as_ref().clone(); + path.push(self.name(db)); + Rc::new(path) + } + } + } + + pub fn dependency_graph(&self, db: &dyn AnalyzerDb) -> Option> { + match self { + Item::Type(TypeDef::Contract(id)) => Some(id.dependency_graph(db)), + Item::Type(TypeDef::Struct(id)) => Some(id.dependency_graph(db)), + Item::Function(id) => Some(id.dependency_graph(db)), + _ => None, + } + } + + /// Downcast utility function + pub fn as_contract(&self) -> Option { + match self { + Item::Type(TypeDef::Contract(id)) => Some(*id), + _ => None, + } + } + pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { match self { Item::Type(id) => id.sink_diagnostics(db, sink), @@ -174,7 +221,7 @@ pub struct Ingot { pub fe_files: BTreeMap, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct IngotId(pub(crate) u32); impl_intern_key!(IngotId); impl IngotId { @@ -238,7 +285,7 @@ pub struct Module { pub ast: ast::Module, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct ModuleId(pub(crate) u32); impl_intern_key!(ModuleId); impl ModuleId { @@ -308,6 +355,16 @@ impl ModuleId { db.module_adjacent_modules(*self) } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Option { + self.parent_module(db).map(Item::Module).or_else(|| { + if let ModuleContext::Ingot(ingot) = self.data(db).context { + Some(Item::Ingot(ingot)) + } else { + None + } + }) + } + pub fn parent_module(&self, db: &dyn AnalyzerDb) -> Option { db.module_parent_module(*self) } @@ -354,7 +411,7 @@ pub struct ModuleConstant { pub module: ModuleId, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct ModuleConstantId(pub(crate) u32); impl_intern_key!(ModuleConstantId); @@ -385,6 +442,10 @@ impl ModuleConstantId { self.data(db).ast.kind.value.kind.clone() } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + Item::Module(self.data(db).module) + } + pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { db.module_constant_type(*self) .diagnostics @@ -404,7 +465,7 @@ impl ModuleConstantId { } } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub enum TypeDef { Alias(TypeAliasId), Struct(StructId), @@ -446,6 +507,15 @@ impl TypeDef { } } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Option { + match self { + TypeDef::Alias(id) => Some(id.parent(db)), + TypeDef::Struct(id) => Some(id.parent(db)), + TypeDef::Contract(id) => Some(id.parent(db)), + TypeDef::Primitive(_) => None, + } + } + pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { match self { TypeDef::Alias(id) => id.sink_diagnostics(db, sink), @@ -462,7 +532,7 @@ pub struct TypeAlias { pub module: ModuleId, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct TypeAliasId(pub(crate) u32); impl_intern_key!(TypeAliasId); @@ -482,6 +552,9 @@ impl TypeAliasId { pub fn typ(&self, db: &dyn AnalyzerDb) -> Result { db.type_alias_type(*self).value } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + Item::Module(self.data(db).module) + } pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { db.type_alias_type(*self) .diagnostics @@ -497,7 +570,7 @@ pub struct Contract { pub module: ModuleId, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct ContractId(pub(crate) u32); impl_intern_key!(ContractId); impl ContractId { @@ -574,6 +647,24 @@ impl ContractId { db.contract_event_map(*self).value } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + Item::Module(self.data(db).module) + } + + /// Dependency graph of the contract type, which consists of the field types + /// and the dependencies of those types. + /// + /// NOTE: Contract items should *only* + pub fn dependency_graph(&self, db: &dyn AnalyzerDb) -> Rc { + db.contract_dependency_graph(*self).0 + } + + /// Dependency graph of the (imaginary) `__call__` function, which + /// dispatches to the contract's public functions. + pub fn runtime_dependency_graph(&self, db: &dyn AnalyzerDb) -> Rc { + db.contract_runtime_dependency_graph(*self).0 + } + pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { // fields db.contract_field_map(*self).sink_diagnostics(sink); @@ -602,7 +693,7 @@ pub struct ContractField { pub parent: ContractId, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct ContractFieldId(pub(crate) u32); impl_intern_key!(ContractFieldId); impl ContractFieldId { @@ -627,7 +718,7 @@ pub struct Function { pub parent: Option, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct FunctionId(pub(crate) u32); impl_intern_key!(FunctionId); impl FunctionId { @@ -644,9 +735,16 @@ impl FunctionId { self.data(db).ast.kind.name.span } - pub fn parent(&self, db: &dyn AnalyzerDb) -> Option { + // This should probably be scrapped in favor of `parent()` + pub fn class(&self, db: &dyn AnalyzerDb) -> Option { self.data(db).parent } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + let data = self.data(db); + data.parent + .map(|class| class.as_item()) + .unwrap_or(Item::Module(data.module)) + } pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { self.data(db).module @@ -671,6 +769,9 @@ impl FunctionId { pub fn is_public(&self, db: &dyn AnalyzerDb) -> bool { self.pub_span(db).is_some() } + pub fn is_constructor(&self, db: &dyn AnalyzerDb) -> bool { + self.name(db) == "__init__" + } pub fn pub_span(&self, db: &dyn AnalyzerDb) -> Option { self.data(db).ast.kind.pub_ } @@ -683,6 +784,9 @@ impl FunctionId { pub fn body(&self, db: &dyn AnalyzerDb) -> Rc { db.function_body(*self).value } + pub fn dependency_graph(&self, db: &dyn AnalyzerDb) -> Rc { + db.function_dependency_graph(*self).0 + } pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { sink.push_all(db.function_signature(*self).diagnostics.iter()); sink.push_all(db.function_body(*self).diagnostics.iter()); @@ -719,6 +823,12 @@ impl Class { Class::Struct(_) => "struct", } } + pub fn as_item(&self) -> Item { + match self { + Class::Contract(id) => Item::Type(TypeDef::Contract(*id)), + Class::Struct(id) => Item::Type(TypeDef::Struct(*id)), + } + } } #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] @@ -733,7 +843,7 @@ pub struct Struct { pub module: ModuleId, } -#[derive(Default, Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct StructId(pub(crate) u32); impl_intern_key!(StructId); impl StructId { @@ -779,6 +889,12 @@ impl StructId { pub fn self_function(&self, db: &dyn AnalyzerDb, name: &str) -> Option { self.function(db, name).filter(|f| f.takes_self(db)) } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + Item::Module(self.data(db).module) + } + pub fn dependency_graph(&self, db: &dyn AnalyzerDb) -> Rc { + db.struct_dependency_graph(*self).0 + } pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { sink.push_all(db.struct_field_map(*self).diagnostics.iter()); db.struct_all_fields(*self) @@ -797,7 +913,7 @@ pub struct StructField { pub parent: StructId, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct StructFieldId(pub(crate) u32); impl_intern_key!(StructFieldId); impl StructFieldId { @@ -821,7 +937,7 @@ pub struct Event { pub contract: ContractId, } -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct EventId(pub(crate) u32); impl_intern_key!(EventId); @@ -841,6 +957,9 @@ impl EventId { pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId { self.data(db).contract.module(db) } + pub fn parent(&self, db: &dyn AnalyzerDb) -> Item { + Item::Type(TypeDef::Contract(self.data(db).contract)) + } pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { sink.push_all(db.event_type(*self).diagnostics.iter()); } @@ -861,3 +980,37 @@ impl DiagnosticSink for Vec { self.extend(iter.cloned()) } } + +pub type DepGraph = petgraph::graphmap::DiGraphMap; +#[derive(Debug, Clone)] +pub struct DepGraphWrapper(pub Rc); +impl PartialEq for DepGraphWrapper { + fn eq(&self, other: &DepGraphWrapper) -> bool { + self.0.all_edges().eq(other.0.all_edges()) && self.0.nodes().eq(other.0.nodes()) + } +} +impl Eq for DepGraphWrapper {} + +/// [`DepGraph`] edge label. "Locality" refers to the deployed state; +/// `Local` dependencies are those that will be compiled together, while +/// `External` dependencies will only be reachable via an evm CALL* op. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DepLocality { + Local, + External, +} + +pub fn walk_local_dependencies(graph: &DepGraph, root: Item, mut fun: F) +where + F: FnMut(Item), +{ + use petgraph::visit::{Bfs, EdgeFiltered}; + + let mut bfs = Bfs::new( + &EdgeFiltered::from_fn(graph, |(_, _, loc)| *loc == DepLocality::Local), + root, + ); + while let Some(node) = bfs.next(&graph) { + fun(node) + } +} diff --git a/crates/analyzer/src/namespace/scopes.rs b/crates/analyzer/src/namespace/scopes.rs index 1f58b8c3e4..17564df69a 100644 --- a/crates/analyzer/src/namespace/scopes.rs +++ b/crates/analyzer/src/namespace/scopes.rs @@ -156,7 +156,7 @@ impl<'a> AnalyzerContext for FunctionScope<'a> { if name == "self" { return Some(NamedThing::SelfValue { decl: sig.self_decl, - class: self.function.parent(self.db), + class: self.function.class(self.db), span: self.function.self_span(self.db), }); } @@ -184,7 +184,7 @@ impl<'a> AnalyzerContext for FunctionScope<'a> { }) }) .or_else(|| { - if let Some(Class::Contract(contract)) = self.function.parent(self.db) { + if let Some(Class::Contract(contract)) = self.function.class(self.db) { contract.resolve_name(self.db, name) } else { self.function.module(self.db).resolve_name(self.db, name) diff --git a/crates/analyzer/src/namespace/types.rs b/crates/analyzer/src/namespace/types.rs index ea8ca754d7..48a239a8e3 100644 --- a/crates/analyzer/src/namespace/types.rs +++ b/crates/analyzer/src/namespace/types.rs @@ -57,7 +57,7 @@ pub enum FixedSize { Struct(Struct), } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Base { Numeric(Integer), Bool, @@ -65,7 +65,9 @@ pub enum Base { Unit, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, AsRefStr, EnumString, EnumIter)] +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString, EnumIter, +)] #[strum(serialize_all = "snake_case")] pub enum Integer { U256, @@ -131,7 +133,7 @@ impl Contract { } } -#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct FeString { pub max_size: usize, } @@ -154,7 +156,9 @@ pub struct FunctionParam { pub typ: Result, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, EnumString, AsRefStr, EnumIter)] +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, AsRefStr, EnumIter, +)] pub enum GenericType { Array, String, diff --git a/crates/analyzer/src/traversal/expressions.rs b/crates/analyzer/src/traversal/expressions.rs index 7e9a13a070..39d1e6d40b 100644 --- a/crates/analyzer/src/traversal/expressions.rs +++ b/crates/analyzer/src/traversal/expressions.rs @@ -401,6 +401,13 @@ fn expr_str( scope.error("String contains invalid byte sequence", exp.span, ""); }; + scope + .root + .body + .borrow_mut() + .string_literals + .insert(string.clone()); + return Ok(ExpressionAttributes::new( Type::String(FeString { max_size: string.len(), @@ -846,7 +853,7 @@ fn expr_call_name( if let Some(function) = scope .root .function - .parent(scope.db()) + .class(scope.db()) .and_then(|class| class.self_function(scope.db(), name)) { // TODO: this doesn't have to be fatal @@ -1382,9 +1389,25 @@ fn expr_call_method( ); } - match class { - Class::Contract(_) => { - if !is_self { + let sig = method.signature(scope.db()); + validate_named_args( + scope, + &field.kind, + field.span, + args, + &sig.params, + LabelPolicy::AllowAnyUnlabeled, + )?; + + let calltype = match class { + Class::Contract(contract) => { + if is_self { + CallType::ValueMethod { + is_self, + class, + method, + } + } else { // External contract address must be loaded onto the stack. scope.root.update_expression( target, @@ -1392,6 +1415,11 @@ fn expr_call_method( .into_loaded() .expect("should be able to move contract type to stack"), ); + + CallType::External { + contract, + function: method, + } } } Class::Struct(_) => { @@ -1408,29 +1436,19 @@ fn expr_call_method( vec![], ); } + CallType::ValueMethod { + is_self, + class, + method, + } } - } + }; - let sig = method.signature(scope.db()); let return_type = sig.return_type.clone()?; - - validate_named_args( - scope, - &field.kind, - field.span, - args, - &sig.params, - LabelPolicy::AllowAnyUnlabeled, - )?; - let location = Location::assign_location(&return_type); return Ok(( ExpressionAttributes::new(return_type.into(), location), - CallType::ValueMethod { - is_self, - class, - method, - }, + calltype, )); } } @@ -1462,7 +1480,10 @@ fn expr_call_builtin_value_method( "argument", ); - let calltype = CallType::BuiltinValueMethod(method); + let calltype = CallType::BuiltinValueMethod { + method, + typ: value_attrs.typ.clone(), + }; match method { ValueMethod::Clone => { match value_attrs.location { @@ -1609,7 +1630,7 @@ fn expr_call_type_attribute( if let Class::Contract(contract) = class { // Check for Foo.create/create2 (this will go away when the context object is ready) if let Ok(function) = ContractTypeMethod::from_str(&field.kind) { - if scope.root.function.parent(scope.db()) == Some(Class::Contract(contract)) { + if scope.root.function.class(scope.db()) == Some(Class::Contract(contract)) { scope.fancy_error( &format!("`{contract}.{}(...)` called within `{contract}` creates an illegal circular dependency", function.as_ref(), contract=&class_name), vec![Label::primary(field.span, "Contract creation")], @@ -1662,7 +1683,7 @@ fn expr_call_type_attribute( } if !function.is_public(scope.db()) - && scope.root.function.parent(scope.db()) != Some(class) + && scope.root.function.class(scope.db()) != Some(class) { scope.fancy_error( &format!( diff --git a/crates/analyzer/tests/analysis.rs b/crates/analyzer/tests/analysis.rs index d89c93655b..bb8546d14e 100644 --- a/crates/analyzer/tests/analysis.rs +++ b/crates/analyzer/tests/analysis.rs @@ -89,13 +89,20 @@ macro_rules! test_analysis_ingot { .values() .into_iter() .map(|file| { - ( - file.id, - ( - file.clone(), - fe_parser::parse_file(file.id, &file.content).unwrap().0, - ), - ) + let ast = match fe_parser::parse_file(file.id, &file.content) { + Ok((ast, diags)) => { + if !diags.is_empty() { + print_diagnostics(&diags, &files); + panic!("non-fatal parsing error"); + } + ast + } + Err(diags) => { + print_diagnostics(&diags, &files); + panic!("parsing failed"); + } + }; + (file.id, (file.clone(), ast)) }) .collect(), }; diff --git a/crates/analyzer/tests/snapshots/analysis__abi_encoding_stress.snap b/crates/analyzer/tests/snapshots/analysis__abi_encoding_stress.snap index 38100c3e9e..88328c47ae 100644 --- a/crates/analyzer/tests/snapshots/analysis__abi_encoding_stress.snap +++ b/crates/analyzer/tests/snapshots/analysis__abi_encoding_stress.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -136,7 +136,7 @@ note: ┌─ stress/abi_encoding_stress.fe:27:16 │ 27 │ return self.my_addrs.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 5, inner: Address }) } note: ┌─ stress/abi_encoding_stress.fe:29:5 @@ -302,7 +302,7 @@ note: ┌─ stress/abi_encoding_stress.fe:39:16 │ 39 │ return self.my_string.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 10 }) } note: ┌─ stress/abi_encoding_stress.fe:41:5 @@ -397,7 +397,7 @@ note: ┌─ stress/abi_encoding_stress.fe:45:16 │ 45 │ return self.my_u16s.to_mem() - │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 255, inner: Numeric(U16) }) } note: ┌─ stress/abi_encoding_stress.fe:47:5 @@ -565,7 +565,7 @@ note: ┌─ stress/abi_encoding_stress.fe:57:16 │ 57 │ return self.my_bytes.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 100, inner: Numeric(U8) }) } note: ┌─ stress/abi_encoding_stress.fe:59:5 @@ -948,14 +948,14 @@ note: ┌─ stress/abi_encoding_stress.fe:76:22 │ 76 │ my_addrs=self.my_addrs.to_mem(), - │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 5, inner: Address }) } 77 │ my_u128=self.my_u128, 78 │ my_string=self.my_string.to_mem(), - │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 10 }) } 79 │ my_u16s=self.my_u16s.to_mem(), - │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 255, inner: Numeric(U16) }) } 80 │ my_bool=self.my_bool, 81 │ my_bytes=self.my_bytes.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 100, inner: Numeric(U8) }) } diff --git a/crates/analyzer/tests/snapshots/analysis__address_bytes10_map.snap b/crates/analyzer/tests/snapshots/analysis__address_bytes10_map.snap index 08a95b14f7..21893c0d5f 100644 --- a/crates/analyzer/tests/snapshots/analysis__address_bytes10_map.snap +++ b/crates/analyzer/tests/snapshots/analysis__address_bytes10_map.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -72,7 +72,7 @@ note: ┌─ features/address_bytes10_map.fe:5:16 │ 5 │ return self.bar[key].to_mem() - │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 10, inner: Numeric(U8) }) } note: ┌─ features/address_bytes10_map.fe:7:5 diff --git a/crates/analyzer/tests/snapshots/analysis__assert.snap b/crates/analyzer/tests/snapshots/analysis__assert.snap index 4a42247d25..8f48d06e63 100644 --- a/crates/analyzer/tests/snapshots/analysis__assert.snap +++ b/crates/analyzer/tests/snapshots/analysis__assert.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(\"features/assert.fe\", &src, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -247,6 +247,6 @@ note: ┌─ features/assert.fe:20:23 │ 20 │ assert false, self.my_string.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 5 }) } diff --git a/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap b/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap index 99c88f32c5..ec069d9667 100644 --- a/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap +++ b/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap @@ -10,12 +10,118 @@ note: │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Array +note: + ┌─ ingots/basic_ingot/src/ding/dong.fe:2:3 + │ +2 │ my_address: address + │ ^^^^^^^^^^^^^^^^^^^ address +3 │ my_u256: u256 + │ ^^^^^^^^^^^^^ u256 +4 │ my_i8: i8 + │ ^^^^^^^^^ i8 + + +note: + ┌─ ingots/basic_ingot/src/bar/baz.fe:2:5 + │ +2 │ my_bool: bool + │ ^^^^^^^^^^^^^ bool +3 │ my_u256: u256 + │ ^^^^^^^^^^^^^ u256 + + +note: + ┌─ ingots/basic_ingot/src/bing.fe:2:5 + │ +2 │ my_address: address + │ ^^^^^^^^^^^^^^^^^^^ address + +note: + ┌─ ingots/basic_ingot/src/bing.fe:4:1 + │ +4 │ ╭ fn get_42_backend() -> u256: +5 │ │ return 42 + │ ╰─────────────^ attributes hash: 17979516652885443340 + │ + = FunctionSignature { + self_decl: None, + params: [], + return_type: Ok( + Base( + Numeric( + U256, + ), + ), + ), + } + +note: + ┌─ ingots/basic_ingot/src/bing.fe:5:12 + │ +5 │ return 42 + │ ^^ u256: Value + +note: + ┌─ ingots/basic_ingot/src/bing.fe:8:4 + │ +8 │ ╭ pub fn add(x: u256, y: u256) -> u256: +9 │ │ return x + y + │ ╰───────────────────^ attributes hash: 4022593831796629401 + │ + = FunctionSignature { + self_decl: None, + params: [ + FunctionParam { + name: "x", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + FunctionParam { + name: "y", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + ], + return_type: Ok( + Base( + Numeric( + U256, + ), + ), + ), + } + +note: + ┌─ ingots/basic_ingot/src/bing.fe:9:15 + │ +9 │ return x + y + │ ^ ^ u256: Value + │ │ + │ u256: Value + +note: + ┌─ ingots/basic_ingot/src/bing.fe:9:15 + │ +9 │ return x + y + │ ^^^^^ u256: Value + + note: ┌─ ingots/basic_ingot/src/main.fe:9:5 │ 9 │ ╭ pub fn get_my_baz() -> Baz: 10 │ │ return Baz(my_bool=true, my_u256=26) - │ ╰────────────────────────────────────────────^ attributes hash: 10853850528666400742 + │ ╰────────────────────────────────────────────^ attributes hash: 12775921899186886669 │ = FunctionSignature { self_decl: None, @@ -25,7 +131,7 @@ note: Struct { name: "Baz", id: StructId( - 0, + 1, ), field_count: 2, }, @@ -51,14 +157,14 @@ note: ┌─ ingots/basic_ingot/src/main.fe:10:16 │ 10 │ return Baz(my_bool=true, my_u256=26) - │ ^^^ TypeConstructor(Struct(Struct { name: "Baz", id: StructId(0), field_count: 2 })) + │ ^^^ TypeConstructor(Struct(Struct { name: "Baz", id: StructId(1), field_count: 2 })) note: ┌─ ingots/basic_ingot/src/main.fe:12:5 │ 12 │ ╭ pub fn get_my_bing() -> Bong: 13 │ │ return Bong(my_address=address(42)) - │ ╰───────────────────────────────────────────^ attributes hash: 14834639838018463348 + │ ╰───────────────────────────────────────────^ attributes hash: 9604670028259107253 │ = FunctionSignature { self_decl: None, @@ -68,7 +174,7 @@ note: Struct { name: "Bing", id: StructId( - 1, + 2, ), field_count: 1, }, @@ -104,7 +210,7 @@ note: ┌─ ingots/basic_ingot/src/main.fe:13:16 │ 13 │ return Bong(my_address=address(42)) - │ ^^^^ TypeConstructor(Struct(Struct { name: "Bing", id: StructId(1), field_count: 1 })) + │ ^^^^ TypeConstructor(Struct(Struct { name: "Bing", id: StructId(2), field_count: 1 })) note: ┌─ ingots/basic_ingot/src/main.fe:15:5 @@ -135,59 +241,136 @@ note: ┌─ ingots/basic_ingot/src/main.fe:16:16 │ 16 │ return get_42_backend() - │ ^^^^^^^^^^^^^^ Pure(FunctionId(3)) + │ ^^^^^^^^^^^^^^ Pure(FunctionId(0)) + +note: + ┌─ ingots/basic_ingot/src/main.fe:18:5 + │ +18 │ ╭ pub fn get_my_dyng() -> dong::Dyng: +19 │ │ return dong::Dyng( +20 │ │ my_address=address(8), +21 │ │ my_u256=42, +22 │ │ my_i8=-1 +23 │ │ ) + │ ╰─────────^ attributes hash: 12523642377619379671 + │ + = FunctionSignature { + self_decl: None, + params: [], + return_type: Ok( + Struct( + Struct { + name: "Dyng", + id: StructId( + 0, + ), + field_count: 3, + }, + ), + ), + } +note: + ┌─ ingots/basic_ingot/src/main.fe:20:32 + │ +20 │ my_address=address(8), + │ ^ u256: Value note: - ┌─ ingots/basic_ingot/src/ding/dong.fe:2:3 - │ -2 │ my_address: address - │ ^^^^^^^^^^^^^^^^^^^ address -3 │ my_u256: u256 - │ ^^^^^^^^^^^^^ u256 -4 │ my_i8: i8 - │ ^^^^^^^^^ i8 + ┌─ ingots/basic_ingot/src/main.fe:20:24 + │ +20 │ my_address=address(8), + │ ^^^^^^^^^^ address: Value +21 │ my_u256=42, + │ ^^ u256: Value +22 │ my_i8=-1 + │ ^ u256: Value +note: + ┌─ ingots/basic_ingot/src/main.fe:22:19 + │ +22 │ my_i8=-1 + │ ^^ i8: Value note: - ┌─ ingots/basic_ingot/src/bar/baz.fe:2:5 - │ -2 │ my_bool: bool - │ ^^^^^^^^^^^^^ bool -3 │ my_u256: u256 - │ ^^^^^^^^^^^^^ u256 + ┌─ ingots/basic_ingot/src/main.fe:19:16 + │ +19 │ return dong::Dyng( + │ ╭────────────────^ +20 │ │ my_address=address(8), +21 │ │ my_u256=42, +22 │ │ my_i8=-1 +23 │ │ ) + │ ╰─────────^ Dyng: Memory +note: + ┌─ ingots/basic_ingot/src/main.fe:20:24 + │ +20 │ my_address=address(8), + │ ^^^^^^^ TypeConstructor(Base(Address)) note: - ┌─ ingots/basic_ingot/src/bing.fe:2:5 - │ -2 │ my_address: address - │ ^^^^^^^^^^^^^^^^^^^ address + ┌─ ingots/basic_ingot/src/main.fe:19:16 + │ +19 │ return dong::Dyng( + │ ^^^^^^^^^^ TypeConstructor(Struct(Struct { name: "Dyng", id: StructId(0), field_count: 3 })) note: - ┌─ ingots/basic_ingot/src/bing.fe:4:1 - │ -4 │ ╭ fn get_42_backend() -> u256: -5 │ │ return 42 - │ ╰─────────────^ attributes hash: 17979516652885443340 - │ - = FunctionSignature { - self_decl: None, - params: [], - return_type: Ok( - Base( - Numeric( - U256, - ), - ), - ), - } + ┌─ ingots/basic_ingot/src/main.fe:25:5 + │ +25 │ ╭ pub fn create_bing_contract() -> u256: +26 │ │ let bing: BingContract = BingContract.create(0) +27 │ │ return bing.add(40, 50) + │ ╰───────────────────────────────^ attributes hash: 17979516652885443340 + │ + = FunctionSignature { + self_decl: None, + params: [], + return_type: Ok( + Base( + Numeric( + U256, + ), + ), + ), + } note: - ┌─ ingots/basic_ingot/src/bing.fe:5:12 - │ -5 │ return 42 - │ ^^ u256: Value + ┌─ ingots/basic_ingot/src/main.fe:26:19 + │ +26 │ let bing: BingContract = BingContract.create(0) + │ ^^^^^^^^^^^^ BingContract + +note: + ┌─ ingots/basic_ingot/src/main.fe:26:54 + │ +26 │ let bing: BingContract = BingContract.create(0) + │ ^ u256: Value + +note: + ┌─ ingots/basic_ingot/src/main.fe:26:34 + │ +26 │ let bing: BingContract = BingContract.create(0) + │ ^^^^^^^^^^^^^^^^^^^^^^ BingContract: Value +27 │ return bing.add(40, 50) + │ ^^^^ ^^ ^^ u256: Value + │ │ │ + │ │ u256: Value + │ BingContract: Value + +note: + ┌─ ingots/basic_ingot/src/main.fe:27:16 + │ +27 │ return bing.add(40, 50) + │ ^^^^^^^^^^^^^^^^ u256: Value + +note: + ┌─ ingots/basic_ingot/src/main.fe:26:34 + │ +26 │ let bing: BingContract = BingContract.create(0) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinAssociatedFunction { contract: ContractId(0), function: Create } +27 │ return bing.add(40, 50) + │ ^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } diff --git a/crates/analyzer/tests/snapshots/analysis__data_copying_stress.snap b/crates/analyzer/tests/snapshots/analysis__data_copying_stress.snap index 0f8adf8804..896144c649 100644 --- a/crates/analyzer/tests/snapshots/analysis__data_copying_stress.snap +++ b/crates/analyzer/tests/snapshots/analysis__data_copying_stress.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -506,7 +506,7 @@ note: ┌─ stress/data_copying_stress.fe:52:16 │ 52 │ return my_array.clone() - │ ^^^^^^^^^^^^^^ BuiltinValueMethod(Clone) + │ ^^^^^^^^^^^^^^ BuiltinValueMethod { method: Clone, typ: Array(Array { size: 10, inner: Numeric(U256) }) } note: ┌─ stress/data_copying_stress.fe:54:5 @@ -573,7 +573,7 @@ note: ┌─ stress/data_copying_stress.fe:55:9 │ 55 │ my_array.clone()[3] = 5 - │ ^^^^^^^^^^^^^^ BuiltinValueMethod(Clone) + │ ^^^^^^^^^^^^^^ BuiltinValueMethod { method: Clone, typ: Array(Array { size: 10, inner: Numeric(U256) }) } note: ┌─ stress/data_copying_stress.fe:58:5 @@ -726,7 +726,7 @@ note: ┌─ stress/data_copying_stress.fe:65:23 │ 65 │ my_nums_mem = self.my_nums.to_mem() - │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 5, inner: Numeric(U256) }) } note: ┌─ stress/data_copying_stress.fe:68:5 @@ -795,9 +795,9 @@ note: ┌─ stress/data_copying_stress.fe:70:13 │ 70 │ self.my_string.to_mem(), - │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 42 }) } 71 │ self.my_u256.to_mem() - │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Base(Numeric(U256)) } note: ┌─ stress/data_copying_stress.fe:69:9 diff --git a/crates/analyzer/tests/snapshots/analysis__erc20_token.snap b/crates/analyzer/tests/snapshots/analysis__erc20_token.snap index 29b84b87ab..bd07045b67 100644 --- a/crates/analyzer/tests/snapshots/analysis__erc20_token.snap +++ b/crates/analyzer/tests/snapshots/analysis__erc20_token.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -82,7 +82,7 @@ note: ┌─ demos/erc20_token.fe:26:16 │ 26 │ return self._name.to_mem() - │ ^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 100 }) } note: ┌─ demos/erc20_token.fe:28:5 @@ -127,7 +127,7 @@ note: ┌─ demos/erc20_token.fe:29:16 │ 29 │ return self._symbol.to_mem() - │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 100 }) } note: ┌─ demos/erc20_token.fe:31:5 diff --git a/crates/analyzer/tests/snapshots/analysis__external_contract.snap b/crates/analyzer/tests/snapshots/analysis__external_contract.snap index 9259a747ab..e3d739406c 100644 --- a/crates/analyzer/tests/snapshots/analysis__external_contract.snap +++ b/crates/analyzer/tests/snapshots/analysis__external_contract.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -317,7 +317,7 @@ note: 24 │ let foo: Foo = Foo(foo_address) │ ^^^ TypeConstructor(Contract(Contract { name: "Foo", id: ContractId(0) })) 25 │ foo.emit_event(my_num, my_addrs, my_string) - │ ^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } note: ┌─ features/external_contract.fe:27:5 @@ -410,6 +410,6 @@ note: 32 │ let foo: Foo = Foo(foo_address) │ ^^^ TypeConstructor(Contract(Contract { name: "Foo", id: ContractId(0) })) 33 │ return foo.build_array(a, b) - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } diff --git a/crates/analyzer/tests/snapshots/analysis__guest_book.snap b/crates/analyzer/tests/snapshots/analysis__guest_book.snap index da7471f01b..87939240f5 100644 --- a/crates/analyzer/tests/snapshots/analysis__guest_book.snap +++ b/crates/analyzer/tests/snapshots/analysis__guest_book.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(\"demos/guest_book.fe\", &src, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -159,6 +159,6 @@ note: ┌─ demos/guest_book.fe:21:16 │ 21 │ return self.messages[addr].to_mem() - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 100 }) } diff --git a/crates/analyzer/tests/snapshots/analysis__revert.snap b/crates/analyzer/tests/snapshots/analysis__revert.snap index af681a7c4c..93d79d7902 100644 --- a/crates/analyzer/tests/snapshots/analysis__revert.snap +++ b/crates/analyzer/tests/snapshots/analysis__revert.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(\"features/revert.fe\", &src, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -179,6 +179,6 @@ note: 22 │ self.my_other_error = OtherError(msg=1, val=true) │ ^^^^^^^^^^ TypeConstructor(Struct(Struct { name: "OtherError", id: StructId(1), field_count: 2 })) 23 │ revert self.my_other_error.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Struct(Struct { name: "OtherError", id: StructId(1), field_count: 2 }) } diff --git a/crates/analyzer/tests/snapshots/analysis__sized_vals_in_sto.snap b/crates/analyzer/tests/snapshots/analysis__sized_vals_in_sto.snap index de9fb17c0f..1dc8effc3f 100644 --- a/crates/analyzer/tests/snapshots/analysis__sized_vals_in_sto.snap +++ b/crates/analyzer/tests/snapshots/analysis__sized_vals_in_sto.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -193,7 +193,7 @@ note: ┌─ features/sized_vals_in_sto.fe:21:16 │ 21 │ return self.nums.to_mem() - │ ^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 42, inner: Numeric(U256) }) } note: ┌─ features/sized_vals_in_sto.fe:23:5 @@ -282,7 +282,7 @@ note: ┌─ features/sized_vals_in_sto.fe:27:16 │ 27 │ return self.str.to_mem() - │ ^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 26 }) } note: ┌─ features/sized_vals_in_sto.fe:29:5 @@ -403,8 +403,8 @@ note: ┌─ features/sized_vals_in_sto.fe:32:18 │ 32 │ nums=self.nums.to_mem(), - │ ^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Array(Array { size: 42, inner: Numeric(U256) }) } 33 │ str=self.str.to_mem() - │ ^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 26 }) } diff --git a/crates/analyzer/tests/snapshots/analysis__structs.snap b/crates/analyzer/tests/snapshots/analysis__structs.snap index 6d9ad8cfff..ada9ad3b5c 100644 --- a/crates/analyzer/tests/snapshots/analysis__structs.snap +++ b/crates/analyzer/tests/snapshots/analysis__structs.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -55,7 +55,7 @@ note: ┌─ features/structs.fe:8:16 │ 8 │ return self.abi_encode() - │ ^^^^^^^^^^^^^^^ BuiltinValueMethod(AbiEncode) + │ ^^^^^^^^^^^^^^^ BuiltinValueMethod { method: AbiEncode, typ: Struct(Struct { name: "House", id: StructId(0), field_count: 4 }) } note: ┌─ features/structs.fe:10:5 @@ -300,7 +300,7 @@ note: ┌─ features/structs.fe:27:16 │ 27 │ return self.my_house.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Struct(Struct { name: "House", id: StructId(0), field_count: 4 }) } note: ┌─ features/structs.fe:29:5 diff --git a/crates/analyzer/tests/snapshots/analysis__tuple_stress.snap b/crates/analyzer/tests/snapshots/analysis__tuple_stress.snap index cce24129f5..bfeb244940 100644 --- a/crates/analyzer/tests/snapshots/analysis__tuple_stress.snap +++ b/crates/analyzer/tests/snapshots/analysis__tuple_stress.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -611,7 +611,7 @@ note: ┌─ stress/tuple_stress.fe:34:16 │ 34 │ return self.my_sto_tuple.to_mem() - │ ^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: Tuple(Tuple { items: [Base(Numeric(U256)), Base(Numeric(I32))] }) } note: ┌─ stress/tuple_stress.fe:36:5 @@ -785,6 +785,6 @@ note: ┌─ stress/tuple_stress.fe:46:16 │ 46 │ return my_tuple.abi_encode() - │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(AbiEncode) + │ ^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: AbiEncode, typ: Tuple(Tuple { items: [Base(Numeric(U256)), Base(Bool), Base(Address)] }) } diff --git a/crates/analyzer/tests/snapshots/analysis__two_contracts.snap b/crates/analyzer/tests/snapshots/analysis__two_contracts.snap index f9d0d71402..8a901b5e3c 100644 --- a/crates/analyzer/tests/snapshots/analysis__two_contracts.snap +++ b/crates/analyzer/tests/snapshots/analysis__two_contracts.snap @@ -1,23 +1,26 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(\"features/two_contracts.fe\", &src, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: - ┌─ features/two_contracts.fe:3:5 + ┌─ features/two_contracts.fe:2:5 │ -3 │ other: Bar +2 │ other: Bar │ ^^^^^^^^^^ Bar note: - ┌─ features/two_contracts.fe:5:5 + ┌─ features/two_contracts.fe:7:5 │ -5 │ ╭ pub fn external_bar() -> u256: -6 │ │ return Bar(address(0)).bar() - │ ╰────────────────────────────────────^ attributes hash: 17979516652885443340 +7 │ ╭ pub fn foo(self) -> u256: +8 │ │ self.other.set_foo_addr(self.address) +9 │ │ return self.other.answer() + │ ╰──────────────────────────────────^ attributes hash: 2875164910451995213 │ = FunctionSignature { - self_decl: None, + self_decl: Some( + Mutable, + ), params: [], return_type: Ok( Base( @@ -29,88 +32,84 @@ note: } note: - ┌─ features/two_contracts.fe:6:28 - │ -6 │ return Bar(address(0)).bar() - │ ^ u256: Value - -note: - ┌─ features/two_contracts.fe:6:20 - │ -6 │ return Bar(address(0)).bar() - │ ^^^^^^^^^^ address: Value - -note: - ┌─ features/two_contracts.fe:6:16 + ┌─ features/two_contracts.fe:8:9 │ -6 │ return Bar(address(0)).bar() - │ ^^^^^^^^^^^^^^^ Bar: Value +8 │ self.other.set_foo_addr(self.address) + │ ^^^^ Foo: Value note: - ┌─ features/two_contracts.fe:6:16 + ┌─ features/two_contracts.fe:8:9 │ -6 │ return Bar(address(0)).bar() - │ ^^^^^^^^^^^^^^^^^^^^^ u256: Value +8 │ self.other.set_foo_addr(self.address) + │ ^^^^^^^^^^ ^^^^ Foo: Value + │ │ + │ Bar: Storage { nonce: Some(0) } => Value note: - ┌─ features/two_contracts.fe:6:20 + ┌─ features/two_contracts.fe:8:33 │ -6 │ return Bar(address(0)).bar() - │ ^^^^^^^ TypeConstructor(Base(Address)) +8 │ self.other.set_foo_addr(self.address) + │ ^^^^^^^^^^^^ address: Value note: - ┌─ features/two_contracts.fe:6:16 + ┌─ features/two_contracts.fe:8:9 │ -6 │ return Bar(address(0)).bar() - │ ^^^ TypeConstructor(Contract(Contract { name: "Bar", id: ContractId(1) })) +8 │ self.other.set_foo_addr(self.address) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (): Value +9 │ return self.other.answer() + │ ^^^^ Foo: Value note: - ┌─ features/two_contracts.fe:6:16 + ┌─ features/two_contracts.fe:9:16 │ -6 │ return Bar(address(0)).bar() - │ ^^^^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(1)), method: FunctionId(3) } - -note: - ┌─ features/two_contracts.fe:8:5 - │ -8 │ ╭ pub fn foo() -> u256: -9 │ │ return 42 - │ ╰─────────────────^ attributes hash: 17979516652885443340 - │ - = FunctionSignature { - self_decl: None, - params: [], - return_type: Ok( - Base( - Numeric( - U256, - ), - ), - ), - } +9 │ return self.other.answer() + │ ^^^^^^^^^^ Bar: Storage { nonce: Some(0) } => Value note: ┌─ features/two_contracts.fe:9:16 │ -9 │ return 42 - │ ^^ u256: Value +9 │ return self.other.answer() + │ ^^^^^^^^^^^^^^^^^^^ u256: Value note: - ┌─ features/two_contracts.fe:13:5 - │ -13 │ other: Foo - │ ^^^^^^^^^^ Foo + ┌─ features/two_contracts.fe:8:9 + │ +8 │ self.other.set_foo_addr(self.address) + │ ^^^^^^^^^^^^^^^^^^^^^^^ External { contract: ContractId(1), function: FunctionId(3) } +9 │ return self.other.answer() + │ ^^^^^^^^^^^^^^^^^ External { contract: ContractId(1), function: FunctionId(4) } note: - ┌─ features/two_contracts.fe:15:5 + ┌─ features/two_contracts.fe:11:5 │ -15 │ ╭ pub fn external_foo() -> u256: -16 │ │ return Foo(address(0)).foo() - │ ╰────────────────────────────────────^ attributes hash: 17979516652885443340 +11 │ ╭ pub fn add(x: u256, y: u256) -> u256: +12 │ │ return x + y + │ ╰────────────────────^ attributes hash: 4022593831796629401 │ = FunctionSignature { self_decl: None, - params: [], + params: [ + FunctionParam { + name: "x", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + FunctionParam { + name: "y", + typ: Ok( + Base( + Numeric( + U256, + ), + ), + ), + }, + ], return_type: Ok( Base( Numeric( @@ -121,56 +120,90 @@ note: } note: - ┌─ features/two_contracts.fe:16:28 + ┌─ features/two_contracts.fe:12:16 │ -16 │ return Foo(address(0)).foo() - │ ^ u256: Value +12 │ return x + y + │ ^ ^ u256: Value + │ │ + │ u256: Value note: - ┌─ features/two_contracts.fe:16:20 + ┌─ features/two_contracts.fe:12:16 │ -16 │ return Foo(address(0)).foo() - │ ^^^^^^^^^^ address: Value +12 │ return x + y + │ ^^^^^ u256: Value note: - ┌─ features/two_contracts.fe:16:16 + ┌─ features/two_contracts.fe:15:5 │ -16 │ return Foo(address(0)).foo() - │ ^^^^^^^^^^^^^^^ Foo: Value +15 │ other: Foo + │ ^^^^^^^^^^ Foo note: - ┌─ features/two_contracts.fe:16:16 + ┌─ features/two_contracts.fe:17:5 + │ +17 │ ╭ pub fn set_foo_addr(self, addr: address): +18 │ │ self.other = Foo(addr) + │ ╰──────────────────────────────^ attributes hash: 13016862792707996445 + │ + = FunctionSignature { + self_decl: Some( + Mutable, + ), + params: [ + FunctionParam { + name: "addr", + typ: Ok( + Base( + Address, + ), + ), + }, + ], + return_type: Ok( + Base( + Unit, + ), + ), + } + +note: + ┌─ features/two_contracts.fe:18:9 │ -16 │ return Foo(address(0)).foo() - │ ^^^^^^^^^^^^^^^^^^^^^ u256: Value +18 │ self.other = Foo(addr) + │ ^^^^ Bar: Value note: - ┌─ features/two_contracts.fe:16:20 + ┌─ features/two_contracts.fe:18:9 │ -16 │ return Foo(address(0)).foo() - │ ^^^^^^^ TypeConstructor(Base(Address)) +18 │ self.other = Foo(addr) + │ ^^^^^^^^^^ ^^^^ address: Value + │ │ + │ Foo: Storage { nonce: Some(0) } note: - ┌─ features/two_contracts.fe:16:16 + ┌─ features/two_contracts.fe:18:22 │ -16 │ return Foo(address(0)).foo() - │ ^^^ TypeConstructor(Contract(Contract { name: "Foo", id: ContractId(0) })) +18 │ self.other = Foo(addr) + │ ^^^^^^^^^ Foo: Value note: - ┌─ features/two_contracts.fe:16:16 + ┌─ features/two_contracts.fe:18:22 │ -16 │ return Foo(address(0)).foo() - │ ^^^^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } +18 │ self.other = Foo(addr) + │ ^^^ TypeConstructor(Contract(Contract { name: "Foo", id: ContractId(0) })) note: - ┌─ features/two_contracts.fe:18:5 + ┌─ features/two_contracts.fe:20:5 │ -18 │ ╭ pub fn bar() -> u256: -19 │ │ return 26 - │ ╰─────────────────^ attributes hash: 17979516652885443340 +20 │ ╭ pub fn answer(self) -> u256: +21 │ │ return self.other.add(20, 22) + │ ╰─────────────────────────────────────^ attributes hash: 2875164910451995213 │ = FunctionSignature { - self_decl: None, + self_decl: Some( + Mutable, + ), params: [], return_type: Ok( Base( @@ -182,9 +215,30 @@ note: } note: - ┌─ features/two_contracts.fe:19:16 + ┌─ features/two_contracts.fe:21:16 + │ +21 │ return self.other.add(20, 22) + │ ^^^^ Bar: Value + +note: + ┌─ features/two_contracts.fe:21:16 + │ +21 │ return self.other.add(20, 22) + │ ^^^^^^^^^^ ^^ ^^ u256: Value + │ │ │ + │ │ u256: Value + │ Foo: Storage { nonce: Some(0) } => Value + +note: + ┌─ features/two_contracts.fe:21:16 + │ +21 │ return self.other.add(20, 22) + │ ^^^^^^^^^^^^^^^^^^^^^^ u256: Value + +note: + ┌─ features/two_contracts.fe:21:16 │ -19 │ return 26 - │ ^^ u256: Value +21 │ return self.other.add(20, 22) + │ ^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(2) } diff --git a/crates/analyzer/tests/snapshots/analysis__type_aliases.snap b/crates/analyzer/tests/snapshots/analysis__type_aliases.snap index f2e344ca54..dc9a12fca6 100644 --- a/crates/analyzer/tests/snapshots/analysis__type_aliases.snap +++ b/crates/analyzer/tests/snapshots/analysis__type_aliases.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(\"features/type_aliases.fe\", &src, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -303,6 +303,6 @@ note: ┌─ features/type_aliases.fe:28:16 │ 28 │ return self.posts[id].to_mem() - │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(ToMem) + │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: ToMem, typ: String(FeString { max_size: 32 }) } diff --git a/crates/analyzer/tests/snapshots/analysis__uniswap.snap b/crates/analyzer/tests/snapshots/analysis__uniswap.snap index f882ff4c89..e54a08d2b9 100644 --- a/crates/analyzer/tests/snapshots/analysis__uniswap.snap +++ b/crates/analyzer/tests/snapshots/analysis__uniswap.snap @@ -1,6 +1,6 @@ --- source: crates/analyzer/tests/analysis.rs -expression: "build_snapshot(&files, module, &db)" +expression: "build_snapshot(&files, module_id, &db)" --- note: @@ -1975,7 +1975,7 @@ note: ┌─ demos/uniswap.fe:139:31 │ 139 │ let fee_to: address = UniswapV2Factory(self.factory).fee_to() - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(2)), method: FunctionId(26) } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ External { contract: ContractId(2), function: FunctionId(26) } 140 │ let fee_on: bool = fee_to != address(0) │ ^^^^^^^ TypeConstructor(Base(Address)) · @@ -2394,7 +2394,7 @@ note: ┌─ demos/uniswap.fe:162:30 │ 162 │ let balance0: u256 = ERC20(self.token0).balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } 163 │ let balance1: u256 = ERC20(self.token1).balanceOf(self.address) │ ^^^^^ TypeConstructor(Contract(Contract { name: "ERC20", id: ContractId(0) })) @@ -2402,7 +2402,7 @@ note: ┌─ demos/uniswap.fe:163:30 │ 163 │ let balance1: u256 = ERC20(self.token1).balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } · 167 │ let fee_on: bool = self._mint_fee(reserve0, reserve1) │ ^^^^^^^^^^^^^^ ValueMethod { is_self: true, class: Contract(ContractId(1)), method: FunctionId(17) } @@ -2869,9 +2869,9 @@ note: 192 │ let token1: ERC20 = ERC20(self.token1) │ ^^^^^ TypeConstructor(Contract(Contract { name: "ERC20", id: ContractId(0) })) 193 │ let balance0: u256 = token0.balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } 194 │ let balance1: u256 = token1.balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } · 197 │ let fee_on: bool = self._mint_fee(reserve0, reserve1) │ ^^^^^^^^^^^^^^ ValueMethod { is_self: true, class: Contract(ContractId(1)), method: FunctionId(17) } @@ -2879,13 +2879,13 @@ note: 202 │ self._burn(self.address, liquidity) │ ^^^^^^^^^^ ValueMethod { is_self: true, class: Contract(ContractId(1)), method: FunctionId(7) } 203 │ token0.transfer(to, amount0) - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } 204 │ token1.transfer(to, amount1) - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } 205 │ balance0 = token0.balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } 206 │ balance1 = token1.balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } 207 │ 208 │ self._update(balance0, balance1, reserve0, reserve1) │ ^^^^^^^^^^^^ ValueMethod { is_self: true, class: Contract(ContractId(1)), method: FunctionId(16) } @@ -3510,15 +3510,15 @@ note: │ TypeConstructor(Base(Address)) · 232 │ token0.transfer(to, amount0_out) # optimistically transfer tokens - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } 233 │ if amount1_out > 0: 234 │ token1.transfer(to, amount1_out) # optimistically transfer tokens - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } · 239 │ let balance0: u256 = token0.balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } 240 │ let balance1: u256 = token1.balanceOf(self.address) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } · 252 │ self._update(balance0, balance1, reserve0, reserve1) │ ^^^^^^^^^^^^ ValueMethod { is_self: true, class: Contract(ContractId(1)), method: FunctionId(16) } @@ -3681,21 +3681,21 @@ note: │ ^^^^^ TypeConstructor(Contract(Contract { name: "ERC20", id: ContractId(0) })) 259 │ 260 │ token0.transfer(to, token0.balanceOf(self.address) - self.reserve0) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } note: ┌─ demos/uniswap.fe:260:9 │ 260 │ token0.transfer(to, token0.balanceOf(self.address) - self.reserve0) - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } 261 │ token1.transfer(to, token1.balanceOf(self.address) - self.reserve1) - │ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } note: ┌─ demos/uniswap.fe:261:9 │ 261 │ token1.transfer(to, token1.balanceOf(self.address) - self.reserve1) - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(1) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(1) } note: ┌─ demos/uniswap.fe:264:5 @@ -3820,9 +3820,9 @@ note: 266 │ let token1: ERC20 = ERC20(self.token1) │ ^^^^^ TypeConstructor(Contract(Contract { name: "ERC20", id: ContractId(0) })) 267 │ self._update(token0.balanceOf(self.address), token1.balanceOf(self.address), self.reserve0, self.reserve1) - │ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ External { contract: ContractId(0), function: FunctionId(0) } │ │ - │ ValueMethod { is_self: false, class: Contract(ContractId(0)), method: FunctionId(0) } + │ External { contract: ContractId(0), function: FunctionId(0) } note: ┌─ demos/uniswap.fe:267:9 @@ -4570,7 +4570,7 @@ note: │ ^^^^^^^ TypeConstructor(Base(Address)) 318 │ 319 │ let salt: u256 = keccak256((token0, token1).abi_encode()) - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod(AbiEncode) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ BuiltinValueMethod { method: AbiEncode, typ: Tuple(Tuple { items: [Base(Address), Base(Address)] }) } note: ┌─ demos/uniswap.fe:319:26 @@ -4580,7 +4580,7 @@ note: 320 │ let pair: UniswapV2Pair = UniswapV2Pair.create2(0, salt) │ ^^^^^^^^^^^^^^^^^^^^^ BuiltinAssociatedFunction { contract: ContractId(1), function: Create2 } 321 │ pair.initialize(token0, token1) - │ ^^^^^^^^^^^^^^^ ValueMethod { is_self: false, class: Contract(ContractId(1)), method: FunctionId(15) } + │ ^^^^^^^^^^^^^^^ External { contract: ContractId(1), function: FunctionId(15) } 322 │ 323 │ self.pairs[token0][token1] = address(pair) │ ^^^^^^^ TypeConstructor(Base(Address)) diff --git a/crates/driver/src/lib.rs b/crates/driver/src/lib.rs index 378469ddd0..54d54444d4 100644 --- a/crates/driver/src/lib.rs +++ b/crates/driver/src/lib.rs @@ -54,7 +54,7 @@ pub fn compile_module( let module = Module { name: Path::new(&file.name) - .file_name() + .file_stem() .expect("missing file name") .to_string_lossy() .to_string(), @@ -107,7 +107,6 @@ pub fn compile_module( .replace("\\\n", "\n") ) } - panic!("Yul compilation failed with the above errors") } Ok(contracts) => contracts, diff --git a/crates/test-files/fixtures/features/two_contracts.fe b/crates/test-files/fixtures/features/two_contracts.fe index ed11f5a492..b0718ff9d1 100644 --- a/crates/test-files/fixtures/features/two_contracts.fe +++ b/crates/test-files/fixtures/features/two_contracts.fe @@ -1,19 +1,21 @@ contract Foo: - other: Bar - pub fn external_bar() -> u256: - return Bar(address(0)).bar() + pub fn __init__(self): + self.other = Bar.create(0) - pub fn foo() -> u256: - return 42 + pub fn foo(self) -> u256: + self.other.set_foo_addr(self.address) + return self.other.answer() -contract Bar: + pub fn add(x: u256, y: u256) -> u256: + return x + y +contract Bar: other: Foo - pub fn external_foo() -> u256: - return Foo(address(0)).foo() + pub fn set_foo_addr(self, addr: address): + self.other = Foo(addr) - pub fn bar() -> u256: - return 26 + pub fn answer(self) -> u256: + return self.other.add(20, 22) diff --git a/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe b/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe index a6ecad4944..81dda51a14 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/bing.fe @@ -4,7 +4,6 @@ struct Bing: fn get_42_backend() -> u256: return 42 -# currently disallowed -#contract BingContract: -# pub fn foo(): -# pass \ No newline at end of file +contract BingContract: + pub fn add(x: u256, y: u256) -> u256: + return x + y diff --git a/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dong.fe b/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dong.fe index e17327dfbf..bb60a2e6ee 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dong.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dong.fe @@ -1,4 +1,4 @@ -struct Dong: +struct Dyng: my_address: address my_u256: u256 my_i8: i8 \ No newline at end of file diff --git a/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe b/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe index 9a39cdf0a2..efd014263c 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/main.fe @@ -3,7 +3,7 @@ use bing::Bing as Bong use bing::get_42_backend use ding::{dang::Dang as Dung, dong} -#use bing::BingContract +use bing::BingContract contract Foo: pub fn get_my_baz() -> Baz: @@ -15,12 +15,13 @@ contract Foo: pub fn get_42() -> u256: return get_42_backend() -# pub fn get_my_dong() -> dong::Dong: -# return dong::Dong( -# my_address=address(26), -# my_u256=42, -# my_i8=-1 -# ) + pub fn get_my_dyng() -> dong::Dyng: + return dong::Dyng( + my_address=address(8), + my_u256=42, + my_i8=-1 + ) -# pub fn create_bing_contract(): -# BingContract.create(0) \ No newline at end of file + pub fn create_bing_contract() -> u256: + let bing: BingContract = BingContract.create(0) + return bing.add(40, 50) \ No newline at end of file diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 9fa50fb881..5cf1ff7ed7 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -93,7 +93,12 @@ impl ContractHarness { output: Option<ðabi::Token>, ) { let actual_output = self.call_function(executor, name, input); - assert_eq!(output.map(|token| token.to_owned()), actual_output) + assert_eq!( + output.map(|token| token.to_owned()), + actual_output, + "unexpected output from `fn {}`", + name + ) } pub fn call_function( diff --git a/crates/tests/src/features.rs b/crates/tests/src/features.rs index 4a26bb67cb..9fd553b7f4 100644 --- a/crates/tests/src/features.rs +++ b/crates/tests/src/features.rs @@ -1267,11 +1267,7 @@ fn math() { fn two_contracts() { with_executor(&|mut executor| { let foo_harness = deploy_contract(&mut executor, "two_contracts.fe", "Foo", &[]); - let bar_harness = deploy_contract(&mut executor, "two_contracts.fe", "Bar", &[]); - foo_harness.test_function(&mut executor, "foo", &[], Some(&uint_token(42))); - - bar_harness.test_function(&mut executor, "bar", &[], Some(&uint_token(26))); }) } diff --git a/crates/tests/src/ingots.rs b/crates/tests/src/ingots.rs index 5a5b1bf4d2..ec623b38a8 100644 --- a/crates/tests/src/ingots.rs +++ b/crates/tests/src/ingots.rs @@ -29,5 +29,21 @@ fn test_basic_ingot() { ); harness.test_function(&mut executor, "get_42", &[], Some(&uint_token(42))); + harness.test_function( + &mut executor, + "get_my_dyng", + &[], + Some(&tuple_token(&[ + address_token("8"), + uint_token(42), + int_token(-1), + ])), + ); + harness.test_function( + &mut executor, + "create_bing_contract", + &[], + Some(&uint_token(90)), + ); }) } diff --git a/crates/tests/src/runtime.rs b/crates/tests/src/runtime.rs index 51d9e4a069..e5fb7d3d6d 100644 --- a/crates/tests/src/runtime.rs +++ b/crates/tests/src/runtime.rs @@ -2,9 +2,7 @@ #![cfg(feature = "solc-backend")] -use fe_analyzer::namespace::types::{Base, FixedSize, Integer}; use fe_compiler_test_utils::*; -use fe_yulgen::runtime::functions; use yultsur::*; macro_rules! assert_eq { @@ -295,73 +293,6 @@ fn test_runtime_set_zero() { }) } -#[test] -fn test_runtime_house_struct() { - let house_api = functions::structs::struct_apis( - "House", - &[ - ( - "price".to_string(), - FixedSize::Base(Base::Numeric(Integer::U256)), - ), - ( - "size".to_string(), - FixedSize::Base(Base::Numeric(Integer::U256)), - ), - ( - "rooms".to_string(), - FixedSize::Base(Base::Numeric(Integer::U8)), - ), - ("vacant".to_string(), FixedSize::Base(Base::Bool)), - ], - ); - - with_executor(&|mut executor| { - Runtime::new().with_functions([functions::std(), house_api.clone()].concat()).with_test_statements( - statements! { - (let price := 42) - (let size := 26) - (let rooms := 5) - (let vacant := true) - - (let house := struct_House_new(price, size, rooms, vacant)) - - // For now, all values in a struct occupy a full word. Here we test - // that the word at the given word offset contains the correct 32 - // byte value. - // - // This test confirms that each value is being stored right-aligned - // and in the correct word. - [assert_eq!(price, (mload(house)))] - [assert_eq!(size, (mload((add(house, 32)))))] - [assert_eq!(rooms, (mload((add(house, 64)))))] - [assert_eq!(vacant, (mload((add(house, 96)))))] - - // To retrieve an individual struct value, we need a pointer that - // references the start of the value. - [assert_eq!(price, (mloadn((struct_House_get_price_ptr(house)), 32)))] - [assert_eq!(size, (mloadn((struct_House_get_size_ptr(house)), 32)))] - [assert_eq!(rooms, (mloadn((struct_House_get_rooms_ptr(house)), 1)))] - [assert_eq!(vacant, (mloadn((struct_House_get_vacant_ptr(house)), 1)))] - - - // We test the same thing in storage. - - // Note that the storage pointer for `house` is a multiple of 32. - (let house_storage := 2048) - (bytes_mcopys(house, house_storage, 128)) - - [assert_eq!(price, (bytes_sloadn((struct_House_get_price_ptr(house_storage)), 32)))] - [assert_eq!(size, (bytes_sloadn((struct_House_get_size_ptr(house_storage)), 32)))] - [assert_eq!(rooms, (bytes_sloadn((struct_House_get_rooms_ptr(house_storage)), 1)))] - [assert_eq!(vacant, (bytes_sloadn((struct_House_get_vacant_ptr(house_storage)), 1)))] - }, - ) - .execute(&mut executor) - .expect_success(); - }) -} - #[test] fn checked_exp_signed() { with_executor(&|mut executor| { diff --git a/crates/yulgen/Cargo.toml b/crates/yulgen/Cargo.toml index 2381aa782d..852d3a5920 100644 --- a/crates/yulgen/Cargo.toml +++ b/crates/yulgen/Cargo.toml @@ -19,6 +19,7 @@ salsa = "0.16.1" # This fork contains the shorthand macros and some other necessary updates. yultsur = { git = "https://github.com/g-r-a-n-t/yultsur"} +smol_str = "0.1.21" [dev-dependencies] insta = "1.7.1" diff --git a/crates/yulgen/src/constructor.rs b/crates/yulgen/src/constructor.rs index e64c9ca3d1..e82095bdf5 100644 --- a/crates/yulgen/src/constructor.rs +++ b/crates/yulgen/src/constructor.rs @@ -1,8 +1,7 @@ use crate::names::abi as abi_names; use crate::operations::abi as abi_operations; -use crate::types::{to_abi_types, AbiDecodeLocation}; -use fe_analyzer::namespace::types::FixedSize; -use fe_analyzer::AnalyzerDb; +use crate::runtime::functions; +use crate::types::{AbiDecodeLocation, AbiType}; use yultsur::*; /// Builds a constructor for a contract with no init function. @@ -16,25 +15,23 @@ pub fn build() -> yul::Code { } /// Builds a constructor for a contract with an init function. -/// -/// We include the entire contact runtime inside of the constructor (without the -/// ABI dispatcher), run the init function, and return the contract code. pub fn build_with_init( - db: &dyn AnalyzerDb, contract_name: &str, - init_func: yul::Statement, - init_params: Vec, - runtime: Vec, + init_function_name: &str, + init_params: &[AbiType], + init_callgraph: Vec, ) -> yul::Code { // Generate names for our constructor parameters. let (param_idents, param_exprs) = abi_names::vals("init", init_params.len()); + let decode_fns = functions::abi::decode_functions(init_params, AbiDecodeLocation::Memory); + // Decode the parameters, if any are given. let maybe_decode_params = if init_params.is_empty() { statements! {} } else { let decode_expr = abi_operations::decode_data( - &to_abi_types(db, &init_params), + init_params, expression! { params_start_mem }, expression! { params_end_mem }, AbiDecodeLocation::Memory, @@ -44,7 +41,7 @@ pub fn build_with_init( }; // init function name after it is mapped. - let init_func_name = identifier! { ("$$__init__") }; + let init_function_name = identifier! { (init_function_name) }; let contract_name = literal_expression! { (format!("\"{}\"", contract_name)) }; @@ -58,11 +55,9 @@ pub fn build_with_init( // `mem_start`. From there, parameters are decoded and passed into the // init function. code! { - // add init function to the scope - [init_func] - - // add the entire contract runtime - [runtime...] + // add init function and dependencies to scope + [init_callgraph...] + [decode_fns...] // copy the encoded parameters to memory (let params_start_code := datasize([contract_name])) @@ -76,7 +71,7 @@ pub fn build_with_init( [maybe_decode_params...] // call the init function defined above - (pop(([init_func_name]([param_exprs...])))) + (pop(([init_function_name]([param_exprs...])))) // deploy the contract [deployment...] diff --git a/crates/yulgen/src/context.rs b/crates/yulgen/src/context.rs index 2a730f0177..c54505d033 100644 --- a/crates/yulgen/src/context.rs +++ b/crates/yulgen/src/context.rs @@ -1,41 +1,21 @@ -use crate::AnalyzerDb; +use crate::{AnalyzerDb, YulgenDb}; use fe_analyzer::context::{CallType, ExpressionAttributes, FunctionBody}; -use fe_analyzer::namespace::types::{Event, FeString, FixedSize, Struct}; +use fe_analyzer::namespace::types::{Event, FixedSize}; use fe_parser::ast; use fe_parser::node::Node; -use indexmap::IndexSet; use std::rc::Rc; -#[derive(Default)] -pub struct ContractContext { - /// String literals used in the contract - pub string_literals: IndexSet, - - /// Names of contracts that have been created inside of this contract. - pub created_contracts: IndexSet, - - /// Strings that can be used as revert error in assertions - pub assert_strings: IndexSet, - - /// Structs that can be used as errors in revert statements - pub revert_errors: IndexSet, -} - -pub struct FnContext<'a, 'b> { - pub db: &'a dyn AnalyzerDb, - pub contract: &'b mut ContractContext, +pub struct FnContext<'a> { + pub adb: &'a dyn AnalyzerDb, + pub db: &'a dyn YulgenDb, fn_body: Rc, } -impl<'a, 'b> FnContext<'a, 'b> { - pub fn new( - db: &'a dyn AnalyzerDb, - contract: &'b mut ContractContext, - fn_body: Rc, - ) -> Self { +impl<'a> FnContext<'a> { + pub fn new(db: &'a dyn YulgenDb, fn_body: Rc) -> Self { Self { + adb: db.upcast(), db, - contract, fn_body, } } @@ -60,6 +40,6 @@ impl<'a, 'b> FnContext<'a, 'b> { self.fn_body .emits .get(&emit_stmt.id) - .map(|event| event.typ(self.db)) + .map(|event| event.typ(self.adb)) } } diff --git a/crates/yulgen/src/db.rs b/crates/yulgen/src/db.rs index e0c6c259c1..904b71033e 100644 --- a/crates/yulgen/src/db.rs +++ b/crates/yulgen/src/db.rs @@ -1,8 +1,12 @@ -use fe_analyzer::namespace::items::ModuleId; +use crate::types::AbiType; +use fe_analyzer::namespace::items::{ContractId, EventId, FunctionId, ModuleId, StructId}; use fe_analyzer::AnalyzerDb; use fe_common::Upcast; use fe_lowering::LoweringDb; -use indexmap::map::IndexMap; +use indexmap::{IndexMap, IndexSet}; +use smol_str::SmolStr; +use std::rc::Rc; +use yultsur::yul; mod queries; @@ -12,6 +16,46 @@ pub trait YulgenDb: { #[salsa::invoke(queries::compile_module)] fn compile_module(&self, module_id: ModuleId) -> IndexMap; + + #[salsa::invoke(queries::contracts::contract_object)] + fn contract_object(&self, contract: ContractId) -> yul::Object; + #[salsa::invoke(queries::contracts::contract_abi_dispatcher)] + fn contract_abi_dispatcher(&self, contract: ContractId) -> Vec; + + #[salsa::invoke(queries::functions::function_yul_name)] + fn function_yul_name(&self, function: FunctionId) -> SmolStr; + #[salsa::invoke(queries::functions::function_external_call_name)] + fn function_external_call_name(&self, function: FunctionId) -> SmolStr; + #[salsa::invoke(queries::functions::function_external_call_fn)] + fn function_external_call_fn(&self, function: FunctionId) -> Vec; + #[salsa::invoke(queries::functions::function_def)] + fn function_def(&self, function: FunctionId) -> yul::Statement; + #[salsa::invoke(queries::functions::function_sig_abi_types)] + fn function_sig_abi_types(&self, function: FunctionId) -> (Rc<[AbiType]>, Option); + #[salsa::invoke(queries::functions::assert_string_types)] + fn function_assert_string_types(&self, function: FunctionId) -> Rc>; + #[salsa::invoke(queries::functions::revert_types)] + fn function_revert_errors(&self, function: FunctionId) -> Rc>; + + #[salsa::invoke(queries::events::event_idx_abi_types)] + fn event_idx_abi_types(&self, event: EventId) -> Rc<[AbiType]>; + + #[salsa::invoke(queries::structs::struct_abi_type)] + fn struct_abi_type(&self, id: StructId) -> AbiType; + #[salsa::invoke(queries::structs::struct_field_abi_types)] + fn struct_field_abi_types(&self, id: StructId) -> Rc<[AbiType]>; + #[salsa::invoke(queries::structs::struct_qualified_name)] + fn struct_qualified_name(&self, id: StructId) -> SmolStr; + #[salsa::invoke(queries::structs::struct_getter_name)] + fn struct_getter_name(&self, id: StructId, field: SmolStr) -> SmolStr; + #[salsa::invoke(queries::structs::struct_getter_fn)] + fn struct_getter_fn(&self, id: StructId, field: SmolStr) -> yul::Statement; + #[salsa::invoke(queries::structs::struct_init_name)] + fn struct_init_name(&self, id: StructId) -> SmolStr; + #[salsa::invoke(queries::structs::struct_init_fn)] + fn struct_init_fn(&self, id: StructId) -> yul::Statement; + #[salsa::invoke(queries::structs::struct_api_fns)] + fn struct_api_fns(&self, id: StructId) -> Vec; } #[salsa::database( diff --git a/crates/yulgen/src/db/queries.rs b/crates/yulgen/src/db/queries.rs index 769c642882..a04e3a90b6 100644 --- a/crates/yulgen/src/db/queries.rs +++ b/crates/yulgen/src/db/queries.rs @@ -4,9 +4,13 @@ use fe_analyzer::namespace::items::ModuleId; use indexmap::map::IndexMap; use yultsur::yul; +pub mod contracts; +pub mod events; +pub mod functions; +pub mod structs; + pub fn compile_module(db: &dyn YulgenDb, module: ModuleId) -> IndexMap { - let analyzer_db = db.upcast(); - mappers::module::module(analyzer_db, module) + mappers::module::module(db, module) .drain() .map(|(name, object)| (name, to_safe_json(object))) .collect() diff --git a/crates/yulgen/src/db/queries/contracts.rs b/crates/yulgen/src/db/queries/contracts.rs new file mode 100644 index 0000000000..33417bb070 --- /dev/null +++ b/crates/yulgen/src/db/queries/contracts.rs @@ -0,0 +1,194 @@ +use crate::constructor; +use crate::db::YulgenDb; +use crate::runtime::{abi_dispatcher, functions}; +use crate::types::{AbiDecodeLocation, AsAbiType}; +use fe_analyzer::builtins::ValueMethod; +use fe_analyzer::context::CallType; +use fe_analyzer::namespace::items::{walk_local_dependencies, ContractId, DepGraph, Item, TypeDef}; +use fe_analyzer::namespace::types::FixedSize; +use fe_common::utils::keccak; +use indexmap::IndexSet; +use std::convert::TryInto; +use yultsur::*; + +pub fn contract_object(db: &dyn YulgenDb, contract: ContractId) -> yul::Object { + let adb = db.upcast(); + + let runtime_object = { + let (mut functions, data, objects) = build_dependency_graph( + db, + &contract.runtime_dependency_graph(adb), + Item::Type(TypeDef::Contract(contract)), + ); + + functions.extend(db.contract_abi_dispatcher(contract)); + functions.sort(); + functions.dedup(); + + yul::Object { + name: identifier! { runtime }, + code: yul::Code { + block: yul::Block { + statements: statements! { + [functions...] + }, + }, + }, + objects, + data, + } + }; + + let contract_name = contract.name(adb); + if let Some(init_fn) = contract.init_function(adb) { + let (functions, data, objects) = + build_dependency_graph(db, &init_fn.dependency_graph(adb), Item::Function(init_fn)); + + let (params, _) = db.function_sig_abi_types(init_fn); + let code = constructor::build_with_init( + &contract_name, + &db.function_yul_name(init_fn), + ¶ms, + functions, + ); + + // Return constructor object + yul::Object { + name: identifier! { (contract_name) }, + code, + objects: [vec![runtime_object], objects].concat(), + data, + } + } else { + yul::Object { + name: identifier! { (contract_name) }, + code: constructor::build(), + objects: vec![runtime_object], + data: vec![], + } + } +} + +/// Dispatch function and required encode/decode functions. +pub fn contract_abi_dispatcher(db: &dyn YulgenDb, contract: ContractId) -> Vec { + let adb = db.upcast(); + let public_functions = contract + .public_functions(adb) + .values() + .map(|id| { + let bare_name = id.name(adb); + let qualified_name = db.function_yul_name(*id); + let (param_types, return_type) = db.function_sig_abi_types(*id); + ( + bare_name, + qualified_name.to_string(), + param_types, + return_type, + ) + }) + .collect::>(); + + let mut fns = + public_functions + .iter() + .fold(vec![], |mut fns, (_, _, param_types, return_type)| { + fns.extend(functions::abi::decode_functions( + param_types, + AbiDecodeLocation::Calldata, + )); + if let Some(return_type) = return_type { + fns.push(functions::abi::encode(&[return_type.clone()])); + } + fns + }); + fns.push(abi_dispatcher::dispatcher(&public_functions)); + fns.sort(); + fns.dedup(); + fns +} + +fn build_dependency_graph( + db: &dyn YulgenDb, + graph: &DepGraph, + root: Item, +) -> (Vec, Vec, Vec) { + let adb = db.upcast(); // AnalyzerDb + + let mut string_literals = IndexSet::::new(); + let mut created_contracts = IndexSet::::new(); + + // We need all of the "std" yul functions, + // because we can't yet track which ones are needed. + let mut yulfns = functions::std(); + + walk_local_dependencies(graph, root, |item| { + match item { + Item::Function(function) => { + yulfns.push(db.function_def(function)); + + let body = function.body(adb); + for calltype in body.calls.values() { + match calltype { + CallType::External { function: fun, .. } => { + yulfns.extend(db.function_external_call_fn(*fun)); + } + CallType::BuiltinValueMethod { + method: ValueMethod::AbiEncode, + typ, + } => { + let typ: FixedSize = typ + .clone() + .try_into() + .expect("abi_encode non-fixedsize type"); + yulfns.push(functions::abi::encode(&[typ.as_abi_type(adb)])); + } + CallType::BuiltinAssociatedFunction { contract, .. } => { + created_contracts.insert(*contract); + } + _ => {} + } + } + + for struct_ in db.function_revert_errors(function).iter() { + yulfns.push(functions::abi::encode(&[db.struct_abi_type(*struct_)])); + yulfns.push(functions::revert::revert( + &struct_.name(adb), + &db.struct_abi_type(*struct_), + )); + } + for string_type in db.function_assert_string_types(function).iter() { + yulfns.push(functions::revert::error_revert(string_type)); + yulfns.push(functions::abi::encode(&[string_type.clone()])); + } + string_literals.extend(body.string_literals.iter().cloned()); + } + Item::Type(TypeDef::Struct(struct_)) => { + // We don't know which struct fields are actually accessed, so we need + // accessor functions for all of them. + yulfns.extend(db.struct_api_fns(struct_)); + } + Item::Event(event) => { + yulfns.push(functions::abi::encode(&db.event_idx_abi_types(event))); + } + _ => {} + } + }); + + yulfns.sort(); + yulfns.dedup(); + + let data = string_literals + .into_iter() + .map(|string| yul::Data { + name: keccak::full(string.as_bytes()), + value: string, + }) + .collect(); + + let objects = created_contracts + .iter() + .map(|contract| db.contract_object(*contract)) + .collect(); + + (yulfns, data, objects) +} diff --git a/crates/yulgen/src/db/queries/events.rs b/crates/yulgen/src/db/queries/events.rs new file mode 100644 index 0000000000..7716fbe37e --- /dev/null +++ b/crates/yulgen/src/db/queries/events.rs @@ -0,0 +1,22 @@ +use crate::db::YulgenDb; +use crate::types::{AbiType, AsAbiType}; +use fe_analyzer::namespace::items::EventId; +use std::rc::Rc; + +pub fn event_idx_abi_types(db: &dyn YulgenDb, event: EventId) -> Rc<[AbiType]> { + event + .typ(db.upcast()) + .fields + .iter() + .filter_map(|field| { + (!field.is_indexed).then(|| { + field + .typ + .clone() + .expect("event field type error") + .as_abi_type(db.upcast()) + }) + }) + .collect::>() + .into() +} diff --git a/crates/yulgen/src/db/queries/functions.rs b/crates/yulgen/src/db/queries/functions.rs new file mode 100644 index 0000000000..d69fd993e9 --- /dev/null +++ b/crates/yulgen/src/db/queries/functions.rs @@ -0,0 +1,202 @@ +use crate::context::FnContext; +use crate::db::YulgenDb; +use crate::mappers::functions::multiple_func_stmt; +use crate::names; +use crate::operations::abi as abi_operations; +use crate::runtime::functions; +use crate::types::{to_abi_selector_names, to_abi_types, AbiDecodeLocation, AbiType, AsAbiType}; +use fe_abi::utils as abi_utils; +use fe_analyzer::namespace::items::{Class, FunctionId, Item, StructId, TypeDef}; +use fe_analyzer::namespace::types::{Struct, Type}; +use fe_parser::{ast, node::Node}; +use indexmap::IndexSet; +use smol_str::SmolStr; +use std::rc::Rc; +use yultsur::*; + +pub fn function_yul_name(db: &dyn YulgenDb, function: FunctionId) -> SmolStr { + // foo::Bar::new => $$foo$Bar$new + format!("$${}", Item::Function(function).path(db.upcast()).join("$")).into() +} + +pub fn function_def(db: &dyn YulgenDb, function: FunctionId) -> yul::Statement { + let analyzer_db = db.upcast(); + let sig = function.signature(analyzer_db); + + let mut param_names = if sig.self_decl.is_some() + && matches!(function.parent(analyzer_db), Item::Type(TypeDef::Struct(_))) + { + // struct member functions take `$self` in yul + vec![names::var_name("self")] + } else { + vec![] + }; + param_names.extend(sig.params.iter().map(|param| names::var_name(¶m.name))); + + let mut fn_context = FnContext::new(db, function.body(analyzer_db)); + let function_statements = + multiple_func_stmt(&mut fn_context, &function.data(analyzer_db).ast.kind.body); + + let function_name = identifier! { (db.function_yul_name(function)) }; + // all user-defined functions are given a return value during lowering + function_definition! { + function [function_name]([param_names...]) -> return_val { + [function_statements...] + } + } +} + +pub fn function_sig_abi_types( + db: &dyn YulgenDb, + function: FunctionId, +) -> (Rc<[AbiType]>, Option) { + let adb = db.upcast(); + let sig = function.signature(adb); + let return_type = sig.return_type.clone().expect("return type error"); + ( + to_abi_types(adb, &sig.param_types()).into(), + if !return_type.is_unit() { + Some(return_type.as_abi_type(adb)) + } else { + None + }, + ) +} + +pub fn revert_types(db: &dyn YulgenDb, function: FunctionId) -> Rc> { + let body = function.body(db.upcast()); + + let mut structs = IndexSet::new(); + for_each_stmt(&function.data(db.upcast()).ast.kind.body, &mut |stmt| { + if let ast::FuncStmt::Revert { error: Some(node) } = stmt { + let attr = body + .expressions + .get(&node.id) + .expect("missing expr attributes"); + if let Type::Struct(Struct { id, .. }) = &attr.typ { + structs.insert(*id); + } + } + }); + + Rc::new(structs) +} + +pub fn assert_string_types(db: &dyn YulgenDb, function: FunctionId) -> Rc> { + let body = function.body(db.upcast()); + + let mut strings = IndexSet::new(); + for_each_stmt(&function.data(db.upcast()).ast.kind.body, &mut |stmt| { + if let ast::FuncStmt::Assert { + msg: Some(node), .. + } = stmt + { + let attr = body + .expressions + .get(&node.id) + .expect("missing expr attributes"); + if let Type::String(string) = &attr.typ { + strings.insert(string.as_abi_type(db.upcast())); + } + } + }); + + Rc::new(strings) +} + +pub fn function_external_call_name(db: &dyn YulgenDb, function: FunctionId) -> SmolStr { + // foo::Bar::new => $$foo$Bar$new + format!("call_{}", db.function_yul_name(function)).into() +} + +/// Create a yul function to make a call to an external contract function. +/// Includes required encode/decode functions. +pub fn function_external_call_fn(db: &dyn YulgenDb, function: FunctionId) -> Vec { + let adb = db.upcast(); + if !matches!(function.class(adb), Some(Class::Contract(_))) { + panic!("external call to non-contract fn") + }; + + let function_name = function.name(adb); + // get the name of the call function and its parameters + let call_fn_name = identifier! { (db.function_external_call_name(function)) }; + let (param_types, return_type) = db.function_sig_abi_types(function); + + // create a pair of identifiers and expressions for the parameters + let (param_idents, param_exprs) = names::abi::vals("param", param_types.len()); + // the function selector must be added to the first 4 bytes of the calldata + let selector = { + let selector = + abi_utils::func_selector(&function_name, &to_abi_selector_names(¶m_types)); + literal_expression! { (selector) } + }; + + // the size of the encoded data + let encoding_size = abi_operations::encoding_size(¶m_types, ¶m_exprs); + // the operations used to encode the parameters + let encoding_operation = abi_operations::encode(¶m_types, param_exprs); + + let mut fns = vec![functions::abi::encode(¶m_types)]; + + if let Some(return_type) = return_type { + fns.extend(functions::abi::decode_functions( + &[return_type.clone()], + AbiDecodeLocation::Memory, + )); + let decoding_operation = abi_operations::decode_data( + &[return_type], + expression! { outstart }, + expression! { add(outstart, outsize) }, + AbiDecodeLocation::Memory, + ); + // return data must be captured and decoded + fns.push(function_definition! { + function [call_fn_name](addr, [param_idents...]) -> return_val { + (let instart := alloc_mstoren([selector], 4)) + (let insize := add(4, [encoding_size])) + (pop([encoding_operation])) + (let success := call((gas()), addr, 0, instart, insize, 0, 0)) + (let outsize := returndatasize()) + (let outstart := alloc(outsize)) + (returndatacopy(outstart, 0, outsize)) + (if (iszero(success)) { (revert(outstart, outsize)) }) + (return_val := [decoding_operation]) + } + }) + } else { + // unit type; there is no return data to handle + fns.push(function_definition! { + function [call_fn_name](addr, [param_idents...]) -> return_val { + (let instart := alloc_mstoren([selector], 4)) + (let insize := add(4, [encoding_size])) + (pop([encoding_operation])) + (let success := call((gas()), addr, 0, instart, insize, 0, 0)) + (if (iszero(success)) { + (let outsize := returndatasize()) + (let outstart := alloc(outsize)) + (returndatacopy(outstart, 0, outsize)) + (revert(outstart, outsize)) + }) + } + }) + } + fns +} + +fn for_each_stmt(stmts: &[Node], f: &mut F) +where + F: FnMut(&ast::FuncStmt), +{ + for node in stmts { + f(&node.kind); + match &node.kind { + ast::FuncStmt::For { body, .. } => for_each_stmt(body, f), + ast::FuncStmt::While { body, .. } => for_each_stmt(body, f), + ast::FuncStmt::If { body, or_else, .. } => { + for_each_stmt(body, f); + for_each_stmt(or_else, f); + } + _ => {} + } + } +} diff --git a/crates/yulgen/src/db/queries/structs.rs b/crates/yulgen/src/db/queries/structs.rs new file mode 100644 index 0000000000..85fcd34728 --- /dev/null +++ b/crates/yulgen/src/db/queries/structs.rs @@ -0,0 +1,135 @@ +use crate::db::YulgenDb; +use crate::types::{AbiType, AsAbiType, EvmSized}; +use fe_analyzer::namespace::items::{Item, StructId, TypeDef}; +use smol_str::SmolStr; +use std::rc::Rc; +use yultsur::*; + +pub fn struct_field_abi_types(db: &dyn YulgenDb, struct_: StructId) -> Rc<[AbiType]> { + let db = db.upcast(); + struct_ + .fields(db) + .values() + .map(|field| { + field + .typ(db) + .expect("struct field type error") + .as_abi_type(db) + }) + .collect::>() + .into() +} + +pub fn struct_abi_type(db: &dyn YulgenDb, struct_: StructId) -> AbiType { + let components = db.struct_field_abi_types(struct_).to_vec(); + AbiType::Tuple { components } +} + +pub fn struct_qualified_name(db: &dyn YulgenDb, struct_: StructId) -> SmolStr { + // foo::Bar => $$foo$Bar + format!( + "$${}", + Item::Type(TypeDef::Struct(struct_)) + .path(db.upcast()) + .join("$") + ) + .into() +} + +pub fn struct_getter_name(db: &dyn YulgenDb, struct_: StructId, field: SmolStr) -> SmolStr { + format!("{}.get_{}_ptr", db.struct_qualified_name(struct_), field).into() +} + +pub fn struct_getter_fn(db: &dyn YulgenDb, struct_: StructId, field: SmolStr) -> yul::Statement { + let fields = struct_.fields(db.upcast()); + + let (index, _, field_id) = fields + .get_full(field.as_str()) + .expect("invalid struct field name"); + + let field_type = field_id.typ(db.upcast()).expect("struct field error"); + + // The value of each field occupies 32 bytes. This includes values with sizes + // less than 32 bytes. So, when we get the pointer to the value of a struct + // field, we must take into consideration the left-padding. The left-padding is + // equal to the difference between the value's size and 32 bytes, so we end up + // adding the word offset and the byte offset. + let field_offset = index * 32 + (32 - field_type.size()); + + let function_name = identifier! { (db.struct_getter_name(struct_, field)) }; + let offset = literal_expression! { (field_offset) }; + function_definition! { + function [function_name](ptr) -> return_val { + (return_val := add(ptr, [offset])) + } + } +} + +pub fn struct_init_name(db: &dyn YulgenDb, struct_: StructId) -> SmolStr { + format!("{}.new", db.struct_qualified_name(struct_)).into() +} + +pub fn struct_init_fn(db: &dyn YulgenDb, struct_: StructId) -> yul::Statement { + let function_name = identifier! { (db.struct_init_name(struct_)) }; + let fields = struct_.fields(db.upcast()); + + if fields.is_empty() { + // We return 0 here because it is safe to assume that we never write to an empty + // struct. If we end up writing to an empty struct that's an actual Fe + // bug. + return function_definition! { + function [function_name]() -> return_val { + (return_val := 0) + } + }; + } + + let params = fields + .keys() + .map(|name| { + identifier! {(name)} + }) + .collect::>(); + + let body = fields + .iter() + .enumerate() + .map(|(index, (name, _))| { + if index == 0 { + let param_identifier_exp = identifier_expression! {(name)}; + statements! { + (return_val := alloc(32)) + (mstore(return_val, [param_identifier_exp])) + } + } else { + let ptr_identifier = format!("{}_ptr", name); + let ptr_identifier = identifier! {(ptr_identifier)}; + let ptr_identifier_exp = identifier_expression! {(ptr_identifier)}; + let param_identifier_exp = identifier_expression! {(name)}; + statements! { + (let [ptr_identifier] := alloc(32)) + (mstore([ptr_identifier_exp], [param_identifier_exp])) + } + } + }) + .flatten() + .collect::>(); + + function_definition! { + function [function_name]([params...]) -> return_val { + [body...] + } + } +} + +pub fn struct_api_fns(db: &dyn YulgenDb, struct_: StructId) -> Vec { + [ + vec![db.struct_init_fn(struct_)], + struct_ + .fields(db.upcast()) + .keys() + .map(|name| db.struct_getter_fn(struct_, name.into())) + .collect(), + ] + .concat() +} diff --git a/crates/yulgen/src/mappers/contracts.rs b/crates/yulgen/src/mappers/contracts.rs deleted file mode 100644 index 30e8c64677..0000000000 --- a/crates/yulgen/src/mappers/contracts.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::constructor; -use crate::context::ContractContext; -use crate::mappers::functions; -use crate::names; -use crate::runtime; -use fe_analyzer::namespace::items::ContractId; -use fe_analyzer::AnalyzerDb; -use fe_common::utils::keccak; -use std::collections::HashMap; -use yultsur::*; - -/// Builds a Yul object from a Fe contract. -pub fn contract_def( - db: &dyn AnalyzerDb, - contract: ContractId, - contracts: &HashMap, -) -> yul::Object { - let contract_name = contract.name(db); - let mut context = ContractContext::default(); - - let init_function = contract.init_function(db).map(|id| { - ( - functions::func_def(db, &mut context, names::func_name(&id.name(db)), id), - id.signature(db) - .params - .iter() - .map(|param| param.typ.clone().expect("fn param type error")) - .collect(), - ) - }); - - let user_functions = contract - .functions(db) - .values() - .map(|func| functions::func_def(db, &mut context, names::func_name(&func.name(db)), *func)) - .collect::>(); - - // build the set of functions needed during runtime - let runtime_functions = runtime::build(db, &mut context, contract); - let abi_dispatcher = runtime::build_abi_dispatcher(db, contract); - - // build data objects for static strings (also for constants in the future) - let data = context - .string_literals - .iter() - .map(|val| yul::Data { - name: keccak::full(val.as_bytes()), - value: val.clone(), - }) - .collect::>(); - - // Map the set of created contract names to their Yul objects so they can be - // included in the Yul contract that deploys them. - let created_contracts = context - .created_contracts - .iter() - .map(|contract_name| contracts[contract_name].clone()) - .collect::>(); - - // create the runtime object - let runtime_object = yul::Object { - name: identifier! { runtime }, - code: yul::Code { - block: yul::Block { - statements: statements! { - [user_functions...] - [runtime_functions...] - [abi_dispatcher] - }, - }, - }, - objects: created_contracts.clone(), - // We can't reach to data objects in the "contract" hierachy so in order to have - // the data objects available in both places we have to put them in both places. - data: data.clone(), - }; - - // Build the code and and objects fields for the constructor object. - // - // If there is an `__init__` function defined, we must include everything that - // is in the runtime object in the constructor object too. This is so - // user-defined functions can be called from `__init__`. - let (constructor_code, constructor_objects) = - if let Some((init_func, init_params)) = init_function { - let init_runtime_functions = [runtime_functions, user_functions].concat(); - let constructor_code = constructor::build_with_init( - db, - &contract_name, - init_func, - init_params, - init_runtime_functions, - ); - - ( - constructor_code, - [vec![runtime_object], created_contracts].concat(), - ) - } else { - let constructor_code = constructor::build(); - - (constructor_code, vec![runtime_object]) - }; - - // We return the contract initialization object. - yul::Object { - name: identifier! { (contract_name) }, - code: constructor_code, - objects: constructor_objects, - data, - } -} diff --git a/crates/yulgen/src/mappers/expressions.rs b/crates/yulgen/src/mappers/expressions.rs index 4fb5f86b9d..2e19b9fbb6 100644 --- a/crates/yulgen/src/mappers/expressions.rs +++ b/crates/yulgen/src/mappers/expressions.rs @@ -38,10 +38,7 @@ pub fn expr(context: &mut FnContext, exp: &Node) -> yul::Expression { fe::Expr::Call { .. } => expr_call(context, exp), fe::Expr::List { .. } => panic!("list expressions should be lowered"), fe::Expr::Tuple { .. } => panic!("tuple expressions should be lowered"), - fe::Expr::Str(val) => { - context.contract.string_literals.insert(val.clone()); - expr_str(exp) - } + fe::Expr::Str(_) => expr_str(exp), fe::Expr::Unit => expression! { 0x0 }, }; @@ -126,7 +123,7 @@ fn expr_call(context: &mut FnContext, exp: &Node) -> yul::Expression { expression! { balance([yul_args[0].to_owned()]) } } }, - CallType::BuiltinValueMethod(method) => { + CallType::BuiltinValueMethod { method, typ } => { let target = match &func.kind { fe::Expr::Attribute { value, .. } => value, _ => unreachable!(), @@ -137,35 +134,28 @@ fn expr_call(context: &mut FnContext, exp: &Node) -> yul::Expression { // `to_mem` and `clone`. builtins::ValueMethod::ToMem => expr(context, target), builtins::ValueMethod::Clone => expr(context, target), - builtins::ValueMethod::AbiEncode => match context - .expression_attributes(target) - .expect("missing expr attributes") - .typ - .clone() - { + builtins::ValueMethod::AbiEncode => match typ { Type::Struct(struct_) => abi_operations::encode( - &[struct_.as_abi_type(context.db)], + &[struct_.as_abi_type(context.adb)], vec![expr(context, target)], ), _ => panic!("invalid attributes"), }, } } - CallType::TypeConstructor(Type::Struct(val)) => struct_operations::new(&val, yul_args), + CallType::TypeConstructor(Type::Struct(val)) => { + struct_operations::init(context.db, val.id, yul_args) + } CallType::TypeConstructor(Type::Base(Base::Numeric(integer))) => { math_operations::adjust_numeric_size(&integer, yul_args[0].to_owned()) } CallType::TypeConstructor(_) => yul_args[0].to_owned(), CallType::Pure(func) => { - let func_name = names::func_name(&func.name(context.db)); + let func_name = identifier! { (context.db.function_yul_name(func)) }; expression! { [func_name]([yul_args...]) } } CallType::BuiltinAssociatedFunction { contract, function } => { - let contract_name = contract.name(context.db); - context - .contract - .created_contracts - .insert(contract_name.clone()); + let contract_name = contract.name(context.adb); match function { ContractTypeMethod::Create2 => contract_operations::create2( &contract_name, @@ -182,10 +172,7 @@ fn expr_call(context: &mut FnContext, exp: &Node) -> yul::Expression { matches!(class, Class::Struct(_)), "call to contract-associated fn should be rejected by analyzer as not-yet-implemented" ); - let func_name = names::associated_function_name( - &class.name(context.db), - &function.name(context.db), - ); + let func_name = identifier! { (context.db.function_yul_name(function)) }; expression! { [func_name]([yul_args...]) } } CallType::ValueMethod { @@ -198,30 +185,31 @@ fn expr_call(context: &mut FnContext, exp: &Node) -> yul::Expression { _ => unreachable!(), }; - let class_name = class.name(context.db); match class { - Class::Contract(contract) => { - if is_self { - // TODO: contract fns should use names::associated_function_name - let fn_name = names::func_name(&method.name(context.db)); - expression! { [fn_name]([yul_args...]) } - } else { - let address = expr(context, target); - let fn_name = names::contract_call( - &contract.name(context.db), - &method.name(context.db), - ); - expression! { [fn_name]([address], [yul_args...]) } - } + Class::Contract(_) => { + assert!( + is_self, + "non-self contract calls should be CallType::External" + ); + let fn_name = identifier! { (context.db.function_yul_name(method)) }; + expression! { [fn_name]([yul_args...]) } } Class::Struct(_) => { let target = expr(context, target); - let fn_name = - names::associated_function_name(&class_name, &method.name(context.db)); + let fn_name = identifier! { (context.db.function_yul_name(method)) }; expression! { [fn_name]([target], [yul_args...]) } } } } + CallType::External { function, .. } => { + let target = match &func.kind { + fe::Expr::Attribute { value, .. } => value, + _ => unreachable!(), + }; + let address = expr(context, target); + let fn_name = identifier! { (context.db.function_external_call_name(function)) }; + expression! { [fn_name]([address], [yul_args...]) } + } }; } @@ -505,7 +493,7 @@ fn expr_attribute(context: &mut FnContext, exp: &Node) -> yul::Express // struct `self` is handled like any other struct value, // and keeps the name `self` in the generated yul. let target = expr(context, target); - struct_operations::get_attribute(struct_, &field.kind, target) + struct_operations::get_attribute(context.db, struct_.id, &field.kind, target) } _ => panic!("invalid type for field access: {:?}", &target_attrs.typ), } diff --git a/crates/yulgen/src/mappers/functions.rs b/crates/yulgen/src/mappers/functions.rs index 079379eaa3..f765a05126 100644 --- a/crates/yulgen/src/mappers/functions.rs +++ b/crates/yulgen/src/mappers/functions.rs @@ -1,14 +1,12 @@ use crate::constants::PANIC_FAILED_ASSERTION; -use crate::context::{ContractContext, FnContext}; +use crate::context::FnContext; use crate::mappers::{assignments, declarations, expressions}; use crate::names; use crate::operations::data as data_operations; use crate::operations::revert as revert_operations; use crate::types::{AbiType, AsAbiType, EvmSized}; use fe_analyzer::context::ExpressionAttributes; -use fe_analyzer::namespace::items::{self, FunctionId}; use fe_analyzer::namespace::types::Type; -use fe_analyzer::AnalyzerDb; use fe_parser::ast as fe; use fe_parser::node::Node; use yultsur::*; @@ -23,37 +21,6 @@ pub fn multiple_func_stmt( .collect() } -/// Builds a Yul function definition from a Fe function definition. -pub fn func_def( - db: &dyn AnalyzerDb, - context: &mut ContractContext, - function_name: yul::Identifier, - function: FunctionId, -) -> yul::Statement { - // let function_name = names::func_name(&function.name(db)); - let sig = function.signature(db); - let mut param_names = sig - .params - .iter() - .map(|param| names::var_name(¶m.name)) - .collect::>(); - - if sig.self_decl.is_some() && matches!(function.parent(db), Some(items::Class::Struct(_))) { - // struct member functions take `$self` in yul - param_names.insert(0, names::var_name("self")); - } - - let mut fn_context = FnContext::new(db, context, function.body(db)); - let function_statements = multiple_func_stmt(&mut fn_context, &function.data(db).ast.kind.body); - - // all user-defined functions are given a return value during lowering - function_definition! { - function [function_name]([param_names...]) -> return_val { - [function_statements...] - } - } -} - fn func_stmt(context: &mut FnContext, stmt: &Node) -> yul::Statement { match &stmt.kind { fe::FuncStmt::Return { .. } => func_return(context, stmt), @@ -146,11 +113,9 @@ fn revert(context: &mut FnContext, stmt: &Node) -> yul::Statement .clone(); if let Type::Struct(struct_) = &error_attributes.typ { - context.contract.revert_errors.insert(struct_.clone()); - revert_operations::revert( &struct_.name, - &struct_.as_abi_type(context.db), + &struct_.as_abi_type(context.adb), expressions::expr(context, error_expr), ) } else { @@ -182,7 +147,7 @@ fn emit(context: &mut FnContext, stmt: &Node) -> yul::Statement { .typ .clone() .expect("event field type error") - .as_abi_type(context.db), + .as_abi_type(context.adb), field.is_indexed, ) }) @@ -209,9 +174,7 @@ fn assert(context: &mut FnContext, stmt: &Node) -> yul::Statement .clone(); if let Type::String(string) = msg_attributes.typ { - let abi_type = string.as_abi_type(context.db); - context.contract.assert_strings.insert(string); - + let abi_type = string.as_abi_type(context.adb); statement! { if (iszero([test])) { [revert_operations::error_revert(&abi_type, msg)] diff --git a/crates/yulgen/src/mappers/mod.rs b/crates/yulgen/src/mappers/mod.rs index 56dbb86d43..a706b5003b 100644 --- a/crates/yulgen/src/mappers/mod.rs +++ b/crates/yulgen/src/mappers/mod.rs @@ -1,5 +1,4 @@ mod assignments; -mod contracts; mod declarations; mod expressions; pub mod functions; diff --git a/crates/yulgen/src/mappers/module.rs b/crates/yulgen/src/mappers/module.rs index ab40189936..5898048e41 100644 --- a/crates/yulgen/src/mappers/module.rs +++ b/crates/yulgen/src/mappers/module.rs @@ -1,19 +1,21 @@ -use crate::mappers::contracts; -use crate::{AnalyzerDb, ModuleId}; +use crate::{ModuleId, YulgenDb}; use std::collections::HashMap; use yultsur::yul; pub type YulContracts = HashMap; /// Builds a vector of Yul contracts from a Fe module. -pub fn module(db: &dyn AnalyzerDb, module: ModuleId) -> YulContracts { +pub fn module(db: &dyn YulgenDb, module: ModuleId) -> YulContracts { module - .all_contracts(db) + .all_contracts(db.upcast()) .iter() .fold(YulContracts::new(), |mut contracts, id| { - let yul_contract = contracts::contract_def(db, *id, &contracts); + let yul_contract = db.contract_object(*id); - if contracts.insert(id.name(db), yul_contract).is_some() { + if contracts + .insert(id.name(db.upcast()), yul_contract) + .is_some() + { panic!("duplicate contract definition"); } contracts diff --git a/crates/yulgen/src/names/mod.rs b/crates/yulgen/src/names/mod.rs index 8630968c00..38218df04a 100644 --- a/crates/yulgen/src/names/mod.rs +++ b/crates/yulgen/src/names/mod.rs @@ -60,22 +60,6 @@ pub fn adjust_numeric_size(size: &Integer) -> yul::Identifier { identifier! {(format!("adjust_numeric_{}", size.as_ref().to_lowercase()))} } -/// Generate a safe function name for a user defined function -pub fn func_name(name: &str) -> yul::Identifier { - identifier! { (format!("$${}", name)) } -} - -/// Generate a safe function name for a function defined within a struct. -/// Contract function names currently use [`func_name`] above, but should probably use -/// this instead to avoid name collisions with functions defined at the module level. -/// This is for functions that take self, and those that don't. -/// Note that this assumes that there can't be two "classes" with the same name, which -/// won't be the case when we support using items from other modules. -/// Eg. `othermod::Foo::bar()` and `Foo::bar()` yul fn names will collide. -pub fn associated_function_name(class_name: &str, func_name: &str) -> yul::Identifier { - identifier! { (format!("$${}${}", class_name, func_name)) } -} - /// Generate a safe variable name for a user defined function pub fn var_name(name: &str) -> yul::Identifier { identifier! { (format!("${}", name)) } @@ -87,26 +71,3 @@ pub fn revert(name: &str, typ: &AbiType) -> yul::Identifier { identifier! { (name) } } - -/// Generates an external call function name for a given type and location. -pub fn contract_call(contract_name: &str, func_name: &str) -> yul::Identifier { - let name = format!("{}_{}", contract_name, func_name); - identifier! { (name) } -} - -/// Generates a function name for creating a certain struct type -pub fn struct_new_call(struct_name: &str) -> yul::Identifier { - struct_function_name(struct_name, "new") -} - -/// Generates a function name for reading a named property of a certain struct -/// type -pub fn struct_getter_call(struct_name: &str, field_name: &str) -> yul::Identifier { - struct_function_name(struct_name, &format!("get_{}_ptr", field_name)) -} - -/// Generates a function name for to interact with a certain struct type -fn struct_function_name(struct_name: &str, func_name: &str) -> yul::Identifier { - let name = format!("struct_{}_{}", struct_name, func_name); - identifier! { (name) } -} diff --git a/crates/yulgen/src/operations/structs.rs b/crates/yulgen/src/operations/structs.rs index f86d9e6479..5f3982a31f 100644 --- a/crates/yulgen/src/operations/structs.rs +++ b/crates/yulgen/src/operations/structs.rs @@ -1,42 +1,18 @@ -use crate::names; -use fe_analyzer::namespace::types::Struct; +use crate::YulgenDb; +use fe_analyzer::namespace::items::StructId; use yultsur::*; -pub fn new(struct_type: &Struct, params: Vec) -> yul::Expression { - let function_name = names::struct_new_call(&struct_type.name); +pub fn init(db: &dyn YulgenDb, struct_: StructId, params: Vec) -> yul::Expression { + let function_name = identifier! { (db.struct_init_name(struct_)) }; expression! { [function_name]([params...]) } } pub fn get_attribute( - struct_type: &Struct, + db: &dyn YulgenDb, + struct_: StructId, field_name: &str, val: yul::Expression, ) -> yul::Expression { - let function_name = names::struct_getter_call(&struct_type.name, field_name); + let function_name = identifier! { (db.struct_getter_name(struct_, field_name.into())) }; expression! { [function_name]([val]) } } - -#[cfg(test)] -mod tests { - use crate::operations::structs; - use fe_analyzer::namespace::items::StructId; - use fe_analyzer::namespace::types::Struct; - use yultsur::*; - - #[test] - fn test_new() { - let val = Struct { - name: "Foo".to_string(), - id: StructId::default(), - field_count: 2, - }; - let params = vec![ - identifier_expression! { (1) }, - identifier_expression! { (2) }, - ]; - assert_eq!( - structs::new(&val, params).to_string(), - "struct_Foo_new(1, 2)" - ) - } -} diff --git a/crates/yulgen/src/runtime/abi_dispatcher.rs b/crates/yulgen/src/runtime/abi_dispatcher.rs index 7ce5c64b85..0a169499a3 100644 --- a/crates/yulgen/src/runtime/abi_dispatcher.rs +++ b/crates/yulgen/src/runtime/abi_dispatcher.rs @@ -5,8 +5,13 @@ use fe_abi::utils as abi_utils; use yultsur::*; /// Builds a switch statement that dispatches calls to the contract. -pub fn dispatcher(functions: Vec<(String, Vec, Option)>) -> yul::Statement { - let arms = functions.into_iter().map(dispatch_arm).collect::>(); +pub fn dispatcher( + functions: &[(String, String, impl AsRef<[AbiType]>, Option)], +) -> yul::Statement { + let arms = functions + .iter() + .map(|(name, qname, params, ret)| dispatch_arm(name, qname, params.as_ref(), ret)) + .collect::>(); if arms.is_empty() { statement! { return(0, 0) } @@ -19,8 +24,13 @@ pub fn dispatcher(functions: Vec<(String, Vec, Option)>) -> yu } } -fn dispatch_arm((name, params, return_type): (String, Vec, Option)) -> yul::Case { - let selector = selector(&name, ¶ms); +fn dispatch_arm( + bare_name: &str, + qualified_name: &str, + params: &[AbiType], + return_type: &Option, +) -> yul::Case { + let selector = selector(bare_name, params); let (param_idents, param_exprs) = abi_names::vals("call", params.len()); @@ -29,7 +39,7 @@ fn dispatch_arm((name, params, return_type): (String, Vec, Option, Option Vec { // revert functions. // It will be removed in https://github.com/ethereum/fe/pull/478 along with all other // batches of runtime functions. - encode(vec![AbiType::Uint { size: 32 }]), + encode(&[AbiType::Uint { size: 32 }]), ] } -/// Creates a batch of encoding function for the given type arrays. -/// -/// It sorts the functions and removes duplicates. -pub fn batch_encode(batch: Vec>) -> Vec { - let mut yul_functions: Vec<_> = batch.into_iter().map(encode).collect(); - yul_functions.sort(); - yul_functions.dedup(); - yul_functions -} - -/// Creates a batch of decoding function for the given types and decode -/// locations. -/// -/// It sorts the functions and removes duplicates. -pub fn batch_decode(data_batch: Vec<(Vec, AbiDecodeLocation)>) -> Vec { - let component_batch = data_batch - .iter() - .fold(vec![], |mut accum, (types, location)| { - for typ in types.to_owned() { - accum.push((typ, *location)); +/// Returns a yul function that decodes a block of abi-encoded data into the +/// specified [`AbiType`] componenets, eg `abi_decode_data_u256_Foo_u8_calldata`. +/// The decoding of each component is handled by a separate function, eg. +/// `abi_decode_component_uint32_mem`; these component decoding functions +/// are also included in the returned `Vec`. +pub fn decode_functions(types: &[AbiType], location: AbiDecodeLocation) -> Vec { + let mut component_fns: Vec<_> = types.iter().fold(vec![], |mut funcs, typ| { + funcs.push(decode_component(typ, location)); + match typ { + AbiType::Tuple { components } => { + for ctyp in components { + funcs.push(decode_component(ctyp, location)) + } } - accum - }); - - let data_functions: Vec<_> = data_batch - .into_iter() - .map(|(types, location)| decode_data(&types, location)) - .collect(); - let component_functions: Vec<_> = component_batch - .into_iter() - .map(|(typ, location)| { - let top_function = decode_component(&typ, location); - - let inner_functions = match &typ { - AbiType::Tuple { components } => components - .iter() - .map(|typ| decode_component(typ, location)) - .collect(), - AbiType::StaticArray { inner, .. } => vec![decode_component(inner, location)], - _ => vec![], - }; - - [vec![top_function], inner_functions].concat() - }) - .flatten() - .collect(); - - let mut yul_functions: Vec<_> = [data_functions, component_functions].concat(); - yul_functions.sort(); - yul_functions.dedup(); - yul_functions + AbiType::StaticArray { inner, .. } => funcs.push(decode_component(inner, location)), + _ => {} + }; + funcs + }); + + component_fns.sort(); + component_fns.dedup(); + component_fns.push(decode_data(types, location)); + component_fns } /// Creates a function that decodes ABI encoded data. -pub fn decode_data(types: &[AbiType], location: AbiDecodeLocation) -> yul::Statement { +fn decode_data(types: &[AbiType], location: AbiDecodeLocation) -> yul::Statement { #[derive(Clone)] struct IdentExpr { ident: yul::Identifier, @@ -525,8 +498,8 @@ pub fn unpack() -> yul::Statement { } /// Generates an encoding function for any set of type parameters. -pub fn encode(types: Vec) -> yul::Statement { - let func_name = abi_names::encode(&types); +pub fn encode(types: &[AbiType]) -> yul::Statement { + let func_name = abi_names::encode(types); // Create names for each of the values we're encoding. let (param_idents, param_exprs) = abi_names::vals("encode", types.len()); @@ -563,7 +536,7 @@ pub fn encode(types: Vec) -> yul::Statement { // Set the return to the available memory address. (return_ptr := avail()) // The data section begins at the end of the head. - (let data_offset := [abi_operations::encoding_head_size(&types)]) + (let data_offset := [abi_operations::encoding_head_size(types)]) [head_encode_stmts...] [data_encode_stmts...] } diff --git a/crates/yulgen/src/runtime/functions/contracts.rs b/crates/yulgen/src/runtime/functions/contracts.rs index 34c59fb6b9..032fe4a011 100644 --- a/crates/yulgen/src/runtime/functions/contracts.rs +++ b/crates/yulgen/src/runtime/functions/contracts.rs @@ -1,12 +1,5 @@ use crate::constants::{ERROR_FAILED_SEND_VALUE, ERROR_INSUFFICIENT_FUNDS_TO_SEND_VALUE}; -use crate::names; -use crate::names::abi as abi_names; -use crate::operations::abi as abi_operations; use crate::operations::revert as revert_operations; -use crate::types::{to_abi_selector_names, to_abi_types, AbiDecodeLocation, AsAbiType}; -use fe_abi::utils as abi_utils; -use fe_analyzer::namespace::items::ContractId; -use fe_analyzer::AnalyzerDb; use yultsur::*; /// Return all contacts runtime functions @@ -14,76 +7,6 @@ pub fn all() -> Vec { vec![create2(), create(), send_value()] } -/// Builds a set of functions used to make calls to the given contract's public -/// functions. -pub fn calls(db: &dyn AnalyzerDb, contract: ContractId) -> Vec { - let contract_name = contract.name(db); - contract - .functions(db) - .iter() - .map(|(name, function)| { - let signature = function.signature(db); - let return_type = signature.return_type.clone().expect("fn return type error"); - - // get the name of the call function and its parameters - let function_name = names::contract_call(&contract_name, name); - let param_types = to_abi_types(db, &signature.param_types()); - - // create a pair of identifiers and expressions for the parameters - let (param_idents, param_exprs) = abi_names::vals("param", signature.params.len()); - // the function selector must be added to the first 4 bytes of the calldata - let selector = { - let selector = abi_utils::func_selector(name, &to_abi_selector_names(¶m_types)); - literal_expression! { (selector) } - }; - - // the size of the encoded data - let encoding_size = abi_operations::encoding_size(¶m_types, ¶m_exprs); - // the operations used to encode the parameters - let encoding_operation = abi_operations::encode(¶m_types, param_exprs); - - if return_type.is_unit() { - // there is no return data to handle - function_definition! { - function [function_name](addr, [param_idents...]) -> return_val { - (let instart := alloc_mstoren([selector], 4)) - (let insize := add(4, [encoding_size])) - (pop([encoding_operation])) - (let success := call((gas()), addr, 0, instart, insize, 0, 0)) - (if (iszero(success)) { - (let outsize := returndatasize()) - (let outstart := alloc(outsize)) - (returndatacopy(outstart, 0, outsize)) - (revert(outstart, outsize)) - }) - } - } - } else { - let decoding_operation = abi_operations::decode_data( - &[return_type.as_abi_type(db)], - expression! { outstart }, - expression! { add(outstart, outsize) }, - AbiDecodeLocation::Memory, - ); - // return data must be captured and decoded - function_definition! { - function [function_name](addr, [param_idents...]) -> return_val { - (let instart := alloc_mstoren([selector], 4)) - (let insize := add(4, [encoding_size])) - (pop([encoding_operation])) - (let success := call((gas()), addr, 0, instart, insize, 0, 0)) - (let outsize := returndatasize()) - (let outstart := alloc(outsize)) - (returndatacopy(outstart, 0, outsize)) - (if (iszero(success)) { (revert(outstart, outsize)) }) - (return_val := [decoding_operation]) - } - } - } - }) - .collect() -} - /// Function that executes the `create2` operation. pub fn create2() -> yul::Statement { function_definition! { diff --git a/crates/yulgen/src/runtime/functions/mod.rs b/crates/yulgen/src/runtime/functions/mod.rs index 3d27785c34..3e8b800c7b 100644 --- a/crates/yulgen/src/runtime/functions/mod.rs +++ b/crates/yulgen/src/runtime/functions/mod.rs @@ -5,7 +5,6 @@ pub mod contracts; pub mod data; pub mod math; pub mod revert; -pub mod structs; /// Returns all functions that should be available during runtime. pub fn std() -> Vec { diff --git a/crates/yulgen/src/runtime/functions/structs.rs b/crates/yulgen/src/runtime/functions/structs.rs deleted file mode 100644 index 7d9f3fdae4..0000000000 --- a/crates/yulgen/src/runtime/functions/structs.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::names; -use crate::types::EvmSized; -use fe_analyzer::namespace::types::FixedSize; -use yultsur::*; - -/// Generate a YUL function that can be used to create an instance of a struct -pub fn generate_new_fn(struct_name: &str, fields: &[(String, FixedSize)]) -> yul::Statement { - let function_name = names::struct_new_call(struct_name); - - if fields.is_empty() { - // We return 0 here because it is safe to assume that we never write to an empty - // struct. If we end up writing to an empty struct that's an actual Fe - // bug. - return function_definition! { - function [function_name]() -> return_val { - (return_val := 0) - } - }; - } - - let params = fields - .iter() - .map(|(name, _)| { - identifier! {(name)} - }) - .collect::>(); - - let body = fields - .iter() - .enumerate() - .map(|(index, (name, _))| { - if index == 0 { - let param_identifier_exp = identifier_expression! {(name)}; - statements! { - (return_val := alloc(32)) - (mstore(return_val, [param_identifier_exp])) - } - } else { - let ptr_identifier = format!("{}_ptr", name); - let ptr_identifier = identifier! {(ptr_identifier)}; - let ptr_identifier_exp = identifier_expression! {(ptr_identifier)}; - let param_identifier_exp = identifier_expression! {(name)}; - statements! { - (let [ptr_identifier] := alloc(32)) - (mstore([ptr_identifier_exp], [param_identifier_exp])) - } - } - }) - .flatten() - .collect::>(); - - function_definition! { - function [function_name]([params...]) -> return_val { - [body...] - } - } -} - -/// Generate a YUL function that can be used to read a property of a struct -pub fn generate_get_fn( - struct_name: &str, - (field_name, field_type): &(String, FixedSize), - field_index: usize, -) -> yul::Statement { - let function_name = names::struct_getter_call(struct_name, field_name); - - // The value of each field occupies 32 bytes. This includes values with sizes - // less than 32 bytes. So, when we get the pointer to the value of a struct - // field, we must take into consideration the left-padding. The left-padding is - // equal to the difference between the value's size and 32 bytes, so we end up - // adding the word offset and the byte offset. - let field_offset = field_index * 32 + (32 - field_type.size()); - - let offset = literal_expression! { (field_offset) }; - function_definition! { - function [function_name](ptr) -> return_val { - (return_val := add(ptr, [offset])) - } - } -} - -/// Builds a set of functions used to interact with structs used in a contract -pub fn struct_apis(name: &str, fields: &[(String, FixedSize)]) -> Vec { - [ - vec![generate_new_fn(name, fields)], - fields - .iter() - .enumerate() - .map(|(index, field)| generate_get_fn(name, field, index)) - .collect(), - ] - .concat() -} diff --git a/crates/yulgen/src/runtime/mod.rs b/crates/yulgen/src/runtime/mod.rs index 0fcacbd287..09aa74ece8 100644 --- a/crates/yulgen/src/runtime/mod.rs +++ b/crates/yulgen/src/runtime/mod.rs @@ -1,268 +1,2 @@ pub mod abi_dispatcher; pub mod functions; -use crate::context::ContractContext; -use crate::mappers; -use crate::names; -use crate::types::{to_abi_types, AbiDecodeLocation, AbiType, AsAbiType}; -use fe_analyzer::namespace::items::{ContractId, Item}; -use fe_analyzer::namespace::types::FunctionSignature; -use fe_analyzer::AnalyzerDb; -use std::rc::Rc; -use yultsur::*; - -/// Builds the set of function statements that are needed during runtime. -pub fn build( - db: &dyn AnalyzerDb, - context: &mut ContractContext, - contract: ContractId, -) -> Vec { - let module = contract.module(db); - let module_functions = module - .items(db) - .values() - .filter_map(|item| match item { - Item::Function(fid) => Some(mappers::functions::func_def( - db, - context, - names::func_name(&fid.name(db)), - *fid, - )), - _ => None, - }) - .collect::>(); - - let struct_apis = module - .all_structs(db) - .iter() - .map(|struct_| { - let fields = struct_ - .fields(db) - .iter() - .map(|(name, id)| { - ( - name.to_string(), - id.typ(db).expect("struct field type error"), - ) - }) - .collect::>(); - - let struct_name = struct_.name(db); - let member_functions = struct_ - .functions(db) - .values() - .map(|fid| { - mappers::functions::func_def( - db, - context, - names::associated_function_name(&struct_name, &fid.name(db)), - *fid, - ) - }) - .collect::>(); - - [ - functions::structs::struct_apis(&struct_.name(db), &fields), - member_functions, - ] - .concat() - }) - .collect::>() - .concat(); - - let contract_name = contract.name(db); - let external_contracts: Vec = module - .all_contracts(db) - .iter() - .filter_map(|id| (id.name(db) != contract_name).then(|| *id)) - .collect(); - let external_functions = external_contracts - .iter() - .map(|id| { - id.functions(db) - .values() - .map(|func| func.signature(db)) - .collect::>>() - }) - .collect::>() - .concat(); - - let public_functions: Vec> = contract - .public_functions(db) - .values() - .map(|func| func.signature(db)) - .collect(); - - let encoding = { - let public_functions_batch = public_functions - .iter() - .filter_map(|sig| { - let typ = sig.return_type.clone().expect("return type error"); - (!typ.is_unit()).then(|| vec![typ.as_abi_type(db)]) - }) - .collect::>(); - - let events_batch: Vec> = contract - .events(db) - .values() - .map(|event| { - event - .typ(db) - .fields - .iter() - .filter_map(|field| { - (!field.is_indexed).then(|| { - field - .typ - .clone() - .expect("event field type error") - .as_abi_type(db) - }) - }) - .collect::>() - }) - .collect::>>(); - - let contracts_batch: Vec> = external_functions - .iter() - .map(|fn_sig| to_abi_types(db, &fn_sig.param_types())) - .collect(); - - let assert_strings_batch = context - .assert_strings - .iter() - .map(|val| vec![val.as_abi_type(db)]) - .collect::>(); - - let revert_errors_batch = context - .revert_errors - .iter() - .map(|struct_| { - struct_ - .id - .fields(db) - .values() - .map(|field| { - field - .typ(db) - .expect("struct field type error") - .as_abi_type(db) - }) - .collect::>() - }) - .collect::>(); - - let revert_panic_batch = vec![vec![AbiType::Uint { size: 32 }]]; - - let structs_batch: Vec> = module - .all_structs(db) - .iter() - .map(|struc| vec![struc.typ(db).as_abi_type(db)]) - .collect::>>(); - - let batch = [ - public_functions_batch, - events_batch, - contracts_batch, - assert_strings_batch, - revert_errors_batch, - revert_panic_batch, - structs_batch, - ] - .concat(); - functions::abi::batch_encode(batch) - }; - let decoding = { - let public_functions_batch = public_functions - .iter() - .map(|sig| { - ( - to_abi_types(db, &sig.param_types()), - AbiDecodeLocation::Calldata, - ) - }) - .collect(); - - let init_params_batch = if let Some(init_fn) = contract.init_function(db) { - let sig = init_fn.signature(db); - vec![( - to_abi_types(db, &sig.param_types()), - AbiDecodeLocation::Memory, - )] - } else { - vec![] - }; - - let contracts_batch = external_functions - .iter() - .filter_map(|function| { - let return_type = function.expect_return_type(); - (!return_type.is_unit()) - .then(|| (vec![return_type.as_abi_type(db)], AbiDecodeLocation::Memory)) - }) - .collect(); - - let batch = [public_functions_batch, init_params_batch, contracts_batch].concat(); - functions::abi::batch_decode(batch) - }; - let contract_calls = { - external_contracts - .iter() - .map(|contract| functions::contracts::calls(db, contract.to_owned())) - .collect::>() - .concat() - }; - - let revert_calls_from_assert = context - .assert_strings - .iter() - .map(|string| functions::revert::error_revert(&string.as_abi_type(db))) - .collect::>(); - - let revert_calls = context - .revert_errors - .iter() - .map(|_struct| functions::revert::revert(&_struct.name, &_struct.as_abi_type(db))) - .collect::>(); - - let mut funcs = [ - functions::std(), - encoding, - decoding, - contract_calls, - revert_calls_from_assert, - revert_calls, - struct_apis, - module_functions, - ] - .concat(); - funcs.sort(); - funcs.dedup(); - funcs -} - -pub fn build_abi_dispatcher(db: &dyn AnalyzerDb, contract: ContractId) -> yul::Statement { - let public_functions = contract - .public_functions(db) - .iter() - .map(|(name, id)| { - let sig = id.signature(db); - let return_type = sig.return_type.clone().expect("fn return type error"); - let return_type = (!return_type.is_unit()).then(|| return_type.as_abi_type(db)); - let param_types = sig - .params - .iter() - .map(|param| { - param - .typ - .clone() - .expect("fn param type error") - .as_abi_type(db) - }) - .collect::>(); - - (name.to_string(), param_types, return_type) - }) - .collect::>(); - - abi_dispatcher::dispatcher(public_functions) -} diff --git a/crates/yulgen/src/types.rs b/crates/yulgen/src/types.rs index 1458c2522c..ed7a53c307 100644 --- a/crates/yulgen/src/types.rs +++ b/crates/yulgen/src/types.rs @@ -82,7 +82,7 @@ impl EvmSized for Contract { } /// Solidity ABI type with extra information needed for generation encoding/decoding functions. -#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub enum AbiType { StaticArray { inner: Box, size: usize }, Tuple { components: Vec }, @@ -94,7 +94,7 @@ pub enum AbiType { Bytes { size: usize }, } -#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Copy)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub enum AbiDecodeLocation { Calldata, Memory, diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap index 853eed4429..59764e726c 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap @@ -1,16 +1,28 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "abi_functions::decode_data(&[AbiType::Bool, AbiType::Address],\n AbiDecodeLocation::Memory)" +expression: "yul::Block{statements:\n abi_functions::decode_functions(&[AbiType::Bool,\n AbiType::Address],\n AbiDecodeLocation::Memory),}" --- -function abi_decode_data_bool_address_mem(head_start, data_end) -> return_val_0, return_val_1 { - let encoding_size := sub(data_end, head_start) - if iszero(eq(encoding_size, 64)) { revert_with_Error_uint256(259) } - let head_offset_0 := 0 - let head_offset_1 := 32 - let decoded_val_0 := abi_decode_component_bool_mem(head_start, head_offset_0) - let decoded_val_1 := abi_decode_component_address_mem(head_start, head_offset_1) - if iszero(eq(encoding_size, 64)) { revert_with_Error_uint256(259) } - return_val_0 := decoded_val_0 - return_val_1 := decoded_val_1 +{ + function abi_decode_component_address_mem(head_start, offset) -> return_val { + let ptr := add(head_start, offset) + return_val := mload(ptr) + if iszero(is_left_padded(96, return_val)) { revert_with_Error_uint256(259) } + } + function abi_decode_component_bool_mem(head_start, offset) -> return_val { + let ptr := add(head_start, offset) + return_val := mload(ptr) + if iszero(is_left_padded(255, return_val)) { revert_with_Error_uint256(259) } + } + function abi_decode_data_bool_address_mem(head_start, data_end) -> return_val_0, return_val_1 { + let encoding_size := sub(data_end, head_start) + if iszero(eq(encoding_size, 64)) { revert_with_Error_uint256(259) } + let head_offset_0 := 0 + let head_offset_1 := 32 + let decoded_val_0 := abi_decode_component_bool_mem(head_start, head_offset_0) + let decoded_val_1 := abi_decode_component_address_mem(head_start, head_offset_1) + if iszero(eq(encoding_size, 64)) { revert_with_Error_uint256(259) } + return_val_0 := decoded_val_0 + return_val_1 := decoded_val_1 + } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function.snap index ab8c35fe34..f6abeecf6c 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function.snap @@ -1,31 +1,76 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "abi_functions::decode_data(&[AbiType::Uint{size: 32,},\n AbiType::Bytes{size: 100,},\n AbiType::String{max_size: 42,}, AbiType::Bool,\n AbiType::Address, AbiType::Bytes{size: 100,}],\n AbiDecodeLocation::Calldata)" +expression: "yul::Block{statements:\n abi_functions::decode_functions(&[AbiType::Uint{size: 32,},\n AbiType::Bytes{size: 100,},\n AbiType::String{max_size:\n 42,},\n AbiType::Bool,\n AbiType::Address,\n AbiType::Bytes{size: 100,}],\n AbiDecodeLocation::Calldata),}" --- -function abi_decode_data_uint256_bytes_100_string_42_bool_address_bytes_100_calldata(head_start, data_end) -> return_val_0, return_val_1, return_val_2, return_val_3, return_val_4, return_val_5 { - let encoding_size := sub(data_end, head_start) - if or(lt(encoding_size, 544), gt(encoding_size, 608)) { revert_with_Error_uint256(259) } - let head_offset_0 := 0 - let head_offset_1 := 32 - let head_offset_2 := 64 - let head_offset_3 := 96 - let head_offset_4 := 128 - let head_offset_5 := 160 - let decoded_val_0 := abi_decode_component_uint256_calldata(head_start, head_offset_0) - let decoded_val_1, data_start_offset_1, data_end_offset_1 := abi_decode_component_bytes_100_calldata(head_start, head_offset_1) - let decoded_val_2, data_start_offset_2, data_end_offset_2 := abi_decode_component_string_42_calldata(head_start, head_offset_2) - let decoded_val_3 := abi_decode_component_bool_calldata(head_start, head_offset_3) - let decoded_val_4 := abi_decode_component_address_calldata(head_start, head_offset_4) - let decoded_val_5, data_start_offset_5, data_end_offset_5 := abi_decode_component_bytes_100_calldata(head_start, head_offset_5) - if iszero(eq(data_start_offset_1, 192)) { revert_with_Error_uint256(259) } - if iszero(eq(data_start_offset_2, data_end_offset_1)) { revert_with_Error_uint256(259) } - if iszero(eq(data_start_offset_5, data_end_offset_2)) { revert_with_Error_uint256(259) } - if iszero(eq(encoding_size, data_end_offset_5)) { revert_with_Error_uint256(259) } - return_val_0 := decoded_val_0 - return_val_1 := decoded_val_1 - return_val_2 := decoded_val_2 - return_val_3 := decoded_val_3 - return_val_4 := decoded_val_4 - return_val_5 := decoded_val_5 +{ + function abi_decode_component_address_calldata(head_start, offset) -> return_val { + let ptr := add(head_start, offset) + return_val := calldataload(ptr) + if iszero(is_left_padded(96, return_val)) { revert_with_Error_uint256(259) } + } + function abi_decode_component_bool_calldata(head_start, offset) -> return_val { + let ptr := add(head_start, offset) + return_val := calldataload(ptr) + if iszero(is_left_padded(255, return_val)) { revert_with_Error_uint256(259) } + } + function abi_decode_component_bytes_100_calldata(head_start, head_offset) -> return_val, data_start_offset, data_end_offset { + let head_ptr := add(head_start, head_offset) + data_start_offset := calldataload(head_ptr) + let data_start := add(head_start, data_start_offset) + let bytes_size := calldataload(data_start) + if iszero(eq(bytes_size, 100)) { revert_with_Error_uint256(259) } + let data_size := add(bytes_size, 32) + let padded_data_size := ceil32(data_size) + data_end_offset := add(data_start_offset, padded_data_size) + let end_word := calldataload(sub(add(head_start, data_end_offset), 32)) + let padding_size_bits := mul(sub(padded_data_size, data_size), 8) + if iszero(is_right_padded(padding_size_bits, end_word)) { revert_with_Error_uint256(259) } + return_val := ccopym(add(data_start, 32), sub(data_size, 32)) + } + function abi_decode_component_string_42_calldata(head_start, head_offset) -> return_val, data_start_offset, data_end_offset { + let head_ptr := add(head_start, head_offset) + data_start_offset := calldataload(head_ptr) + let data_start := add(head_start, data_start_offset) + let string_size := calldataload(data_start) + if gt(string_size, 42) { revert_with_Error_uint256(259) } + let data_size := add(string_size, 32) + let padded_data_size := ceil32(data_size) + data_end_offset := add(data_start_offset, padded_data_size) + let end_word := calldataload(sub(add(head_start, data_end_offset), 32)) + let padding_size_bits := mul(sub(padded_data_size, data_size), 8) + if iszero(is_right_padded(padding_size_bits, end_word)) { revert_with_Error_uint256(259) } + return_val := ccopym(data_start, data_size) + } + function abi_decode_component_uint256_calldata(head_start, offset) -> return_val { + let ptr := add(head_start, offset) + return_val := calldataload(ptr) + if iszero(is_left_padded(0, return_val)) { revert_with_Error_uint256(259) } + } + function abi_decode_data_uint256_bytes_100_string_42_bool_address_bytes_100_calldata(head_start, data_end) -> return_val_0, return_val_1, return_val_2, return_val_3, return_val_4, return_val_5 { + let encoding_size := sub(data_end, head_start) + if or(lt(encoding_size, 544), gt(encoding_size, 608)) { revert_with_Error_uint256(259) } + let head_offset_0 := 0 + let head_offset_1 := 32 + let head_offset_2 := 64 + let head_offset_3 := 96 + let head_offset_4 := 128 + let head_offset_5 := 160 + let decoded_val_0 := abi_decode_component_uint256_calldata(head_start, head_offset_0) + let decoded_val_1, data_start_offset_1, data_end_offset_1 := abi_decode_component_bytes_100_calldata(head_start, head_offset_1) + let decoded_val_2, data_start_offset_2, data_end_offset_2 := abi_decode_component_string_42_calldata(head_start, head_offset_2) + let decoded_val_3 := abi_decode_component_bool_calldata(head_start, head_offset_3) + let decoded_val_4 := abi_decode_component_address_calldata(head_start, head_offset_4) + let decoded_val_5, data_start_offset_5, data_end_offset_5 := abi_decode_component_bytes_100_calldata(head_start, head_offset_5) + if iszero(eq(data_start_offset_1, 192)) { revert_with_Error_uint256(259) } + if iszero(eq(data_start_offset_2, data_end_offset_1)) { revert_with_Error_uint256(259) } + if iszero(eq(data_start_offset_5, data_end_offset_2)) { revert_with_Error_uint256(259) } + if iszero(eq(encoding_size, data_end_offset_5)) { revert_with_Error_uint256(259) } + return_val_0 := decoded_val_0 + return_val_1 := decoded_val_1 + return_val_2 := decoded_val_2 + return_val_3 := decoded_val_3 + return_val_4 := decoded_val_4 + return_val_5 := decoded_val_5 + } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap b/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap index d1399b8f91..01efe61a94 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap @@ -1,18 +1,18 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "abi_dispatcher::dispatcher(&function_attributes())" +expression: "abi_dispatcher::dispatcher(functions())" --- switch cloadn(0, 4) case 0x9476f922 { - let return_val := $$hello_world() + let return_val := $$somemod$hello_world() let encoding_start := abi_encode_string_42(return_val) let encoding_size := add(64, ceil32(mload(return_val))) return(encoding_start, encoding_size) } case 0x771602f7 { let call_val_0, call_val_1 := abi_decode_data_uint256_uint256_calldata(4, calldatasize()) - let return_val := $$add(call_val_0, call_val_1) + let return_val := $$somemod$add(call_val_0, call_val_1) let encoding_start := abi_encode_uint256(return_val) let encoding_size := add(32, 0) return(encoding_start, encoding_size) diff --git a/crates/yulgen/tests/yulgen.rs b/crates/yulgen/tests/yulgen.rs index 74427007fb..39cc0c38c4 100644 --- a/crates/yulgen/tests/yulgen.rs +++ b/crates/yulgen/tests/yulgen.rs @@ -1,11 +1,8 @@ -use fe_analyzer::namespace::types::{Base, FixedSize}; use fe_yulgen::constructor; use fe_yulgen::names::abi as abi_names; use fe_yulgen::operations::{abi as abi_operations, data as data_operations}; use fe_yulgen::runtime::abi_dispatcher; -use fe_yulgen::runtime::functions::{ - abi as abi_functions, revert as revert_functions, structs as structs_functions, -}; +use fe_yulgen::runtime::functions::{abi as abi_functions, revert as revert_functions}; use fe_yulgen::types::{AbiDecodeLocation, AbiType}; use insta::assert_display_snapshot; use wasm_bindgen_test::wasm_bindgen_test; @@ -31,15 +28,17 @@ macro_rules! test_yulgen { // constructor test_yulgen! { constructor_no_init, constructor::build() } -fn functions() -> Vec<(String, Vec, Option)> { +fn functions() -> Vec<(String, String, Vec, Option)> { vec![ ( "hello_world".to_string(), + "$$somemod$hello_world".to_string(), vec![], Some(AbiType::String { max_size: 42 }), ), ( "add".to_string(), + "$$somemod$add".to_string(), vec![AbiType::Uint { size: 32 }, AbiType::Uint { size: 32 }], Some(AbiType::Uint { size: 32 }), ), @@ -47,29 +46,29 @@ fn functions() -> Vec<(String, Vec, Option)> { } // ABI dispatcher -test_yulgen! { abi_dispatcher, abi_dispatcher::dispatcher(functions()) } +test_yulgen! { abi_dispatcher, abi_dispatcher::dispatcher(&functions()) } // ABI encoding functions test_yulgen! { abi_encode_u256_address_function, - abi_functions::encode(vec![AbiType::Uint { size: 32 }, AbiType::Address]) + abi_functions::encode(&[AbiType::Uint { size: 32 }, AbiType::Address]) } // ABI decoding functions test_yulgen! { abi_decode_data_address_bool_mem_function, - abi_functions::decode_data(&[AbiType::Bool, AbiType::Address], AbiDecodeLocation::Memory) + yul::Block { statements: abi_functions::decode_functions(&[AbiType::Bool, AbiType::Address], AbiDecodeLocation::Memory) } } test_yulgen! { abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function, - abi_functions::decode_data(&[ + yul::Block { statements: abi_functions::decode_functions(&[ AbiType::Uint { size: 32 }, AbiType::Bytes { size: 100 }, AbiType::String { max_size: 42 }, AbiType::Bool, AbiType::Address, AbiType::Bytes { size: 100 }, - ], AbiDecodeLocation::Calldata) + ], AbiDecodeLocation::Calldata) } } test_yulgen! { abi_decode_component_uint256_mem_function, @@ -107,31 +106,6 @@ test_yulgen! { abi_functions::decode_component_bytes(26, AbiDecodeLocation::Calldata) } -fn struct_bool_bool_fields() -> Vec<(String, FixedSize)> { - vec![ - ("bar".into(), FixedSize::Base(Base::Bool)), - ("bar2".into(), FixedSize::Base(Base::Bool)), - ] -} - -// struct functions -test_yulgen! { - struct_empty_function, - structs_functions::generate_new_fn("Foo", &[]) -} -test_yulgen! { - struct_new_gen_function, - structs_functions::generate_new_fn("Foo", &struct_bool_bool_fields()) -} -test_yulgen! { - struct_getter_gen_bar_function, - structs_functions::generate_get_fn("Foo", &struct_bool_bool_fields()[0], 0) -} -test_yulgen! { - struct_getter_gen_bar2_function, - structs_functions::generate_get_fn("Foo", &struct_bool_bool_fields()[1], 1) -} - // data operations test_yulgen! { emit_event_no_indexed_operation, diff --git a/newsfragments/562.feature.md b/newsfragments/562.feature.md index 5f5fb752e0..7bc63ffeb2 100644 --- a/newsfragments/562.feature.md +++ b/newsfragments/562.feature.md @@ -27,5 +27,3 @@ There are still a few features that will be worked on over the coming months: - `mod` statements (all fe files in the input tree are public modules) These things will be implemented in order of importance over the next few months. - -**Disclaimer:** Use of certain imported items will result in codegen panics. This is because we cannnot yet generate item dependency graphs. This will be fixed with #596. diff --git a/newsfragments/596.bugfix.md b/newsfragments/596.bugfix.md new file mode 100644 index 0000000000..aa5736655c --- /dev/null +++ b/newsfragments/596.bugfix.md @@ -0,0 +1,2 @@ +Contracts can now `create` an instance of a contract defined later in a file. +This issue was caused by a weakness in the way we generated yul. diff --git a/newsfragments/596.internal.md b/newsfragments/596.internal.md new file mode 100644 index 0000000000..6b3cd22d4e --- /dev/null +++ b/newsfragments/596.internal.md @@ -0,0 +1,9 @@ +The fe analyzer now builds a dependency graph of source code "items" (functions, contracts, structs, etc). +This is used in the yulgen phase to determine which items are needed in the yul (intermediate representation) +output. Note that the yul output is still cluttered with utility functions that may or may not be needed by +a given contract. These utility functions are defined in the yulgen phase and aren't tracked in the dependency +graph, so it's not yet possible to filter out the unused functions. We plan to move the definition of many +of these utility functions into fe; when this happens they'll become part of the dependency graph and will only +be included in the yul output when needed. + +The dependency graph will also enable future analyzer warnings about unused code.