From c738036e3d5b1529981479f0d27dcc86deb6d5af Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Tue, 12 Jan 2021 21:15:47 +0100 Subject: [PATCH] Add support for string literals Closes #186 --- Cargo.lock | 13 ++++++++-- common/Cargo.toml | 11 ++++++++ common/src/lib.rs | 1 + common/src/utils/keccak.rs | 18 ++++++++++++++ common/src/utils/mod.rs | 1 + compiler/Cargo.toml | 4 +-- compiler/src/abi/utils.rs | 20 ++++----------- compiler/src/yul/mappers/contracts.rs | 19 ++++++++++++++ compiler/src/yul/mappers/expressions.rs | 29 +++++++++++++--------- compiler/src/yul/runtime/functions/data.rs | 12 +++++++++ compiler/src/yul/runtime/functions/mod.rs | 1 + compiler/tests/evm_contracts.rs | 8 ++++++ compiler/tests/fixtures/strings.fe | 8 ++++-- newsfragments/186.feature.md | 11 ++++++++ parser/src/parsers.rs | 8 +++++- parser/tests/fixtures/parsers/atom.ron | 4 +-- semantics/Cargo.toml | 1 + semantics/src/lib.rs | 3 +++ semantics/src/namespace/events.rs | 20 +++------------ semantics/src/namespace/scopes.rs | 7 ++++++ semantics/src/traversal/expressions.rs | 27 +++++++++++++++++++- 21 files changed, 172 insertions(+), 54 deletions(-) create mode 100644 common/Cargo.toml create mode 100644 common/src/lib.rs create mode 100644 common/src/utils/keccak.rs create mode 100644 common/src/utils/mod.rs create mode 100644 newsfragments/186.feature.md diff --git a/Cargo.lock b/Cargo.lock index 1a0e7cf2d0..70d5da3bba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,6 +610,14 @@ dependencies = [ "fe-parser", ] +[[package]] +name = "fe-common" +version = "0.0.1" +dependencies = [ + "hex", + "tiny-keccak 2.0.2", +] + [[package]] name = "fe-compiler" version = "0.0.1" @@ -617,6 +625,7 @@ dependencies = [ "ethabi", "evm", "evm-runtime", + "fe-common", "fe-parser", "fe-semantics", "hex", @@ -627,7 +636,6 @@ dependencies = [ "serde_json", "solc", "stringreader", - "tiny-keccak 2.0.2", "yultsur", ] @@ -649,6 +657,7 @@ name = "fe-semantics" version = "0.0.1" dependencies = [ "ansi_term 0.12.1", + "fe-common", "fe-parser", "hex", "num-bigint", @@ -1927,4 +1936,4 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "yultsur" version = "0.1.0" -source = "git+https://github.com/g-r-a-n-t/yultsur#ab0c49099d06722e09bd1003a95ae56f1ffe3757" +source = "git+https://github.com/cburgdorf/yultsur?branch=christoph/feat/data-support#c2fcba489ca2ce04b51e4f254ccd9a3a06b81000" diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000000..fa820ac33d --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fe-common" +version = "0.0.1" +authors = ["Ethereum Foundation "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tiny-keccak = { version = "2.0", features = ["keccak"] } +hex = "0.4" diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000000..b5614dd823 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1 @@ +pub mod utils; diff --git a/common/src/utils/keccak.rs b/common/src/utils/keccak.rs new file mode 100644 index 0000000000..9381733646 --- /dev/null +++ b/common/src/utils/keccak.rs @@ -0,0 +1,18 @@ +use tiny_keccak::{ + Hasher, + Keccak, +}; + +pub fn get_full_signature(content: &[u8]) -> String { + get_partial_signature(content, 32) +} + +pub fn get_partial_signature(content: &[u8], size: usize) -> String { + let mut keccak = Keccak::v256(); + let mut selector = [0u8; 32]; + + keccak.update(content); + keccak.finalize(&mut selector); + + format!("0x{}", hex::encode(&selector[0..size])) +} diff --git a/common/src/utils/mod.rs b/common/src/utils/mod.rs new file mode 100644 index 0000000000..ebcb6d4dd9 --- /dev/null +++ b/common/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod keccak; diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index a81cf7f2ed..9dfd4570fb 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -12,15 +12,15 @@ description = "Compiler lib for the Fe language." solc-backend = ["solc"] [dependencies] +fe-common = {path = "../common", version = "0.0.1"} fe-parser = {path = "../parser", version = "0.0.1"} fe-semantics = {path = "../semantics", version = "0.0.1"} serde_json = "1.0" serde = "1.0" hex = "0.4" # This fork contains the shorthand macros and some other necessary updates. -yultsur = { git = "https://github.com/g-r-a-n-t/yultsur" } +yultsur = { git = "https://github.com/cburgdorf/yultsur", branch="christoph/feat/data-support"} ethabi = "12.0" -tiny-keccak = { version = "2.0", features = ["keccak"] } stringreader = "0.1" # Optional # This fork supports concurrent compilation, which is required for Rust tests. diff --git a/compiler/src/abi/utils.rs b/compiler/src/abi/utils.rs index 3061343a45..71366c2829 100644 --- a/compiler/src/abi/utils.rs +++ b/compiler/src/abi/utils.rs @@ -1,27 +1,17 @@ -use tiny_keccak::{ - Hasher, - Keccak, -}; +use fe_common::utils::keccak::get_partial_signature; /// Formats the name and fields and calculates the 32 byte keccak256 value of /// the signature. pub fn event_topic(name: String, fields: Vec) -> String { - sig_keccak256(name, fields, 32) + sign_event_or_func(name, fields, 32) } /// Formats the name and params and calculates the 4 byte keccak256 value of the /// signature. pub fn func_selector(name: String, params: Vec) -> String { - sig_keccak256(name, params, 4) + sign_event_or_func(name, params, 4) } -fn sig_keccak256(name: String, params: Vec, size: usize) -> String { +fn sign_event_or_func(name: String, params: Vec, size: usize) -> String { let signature = format!("{}({})", name, params.join(",")); - - let mut keccak = Keccak::v256(); - let mut selector = [0u8; 32]; - - keccak.update(signature.as_bytes()); - keccak.finalize(&mut selector); - - format!("0x{}", hex::encode(&selector[0..size])) + get_partial_signature(signature.as_bytes(), size) } diff --git a/compiler/src/yul/mappers/contracts.rs b/compiler/src/yul/mappers/contracts.rs index 129b890d0c..af9700ae09 100644 --- a/compiler/src/yul/mappers/contracts.rs +++ b/compiler/src/yul/mappers/contracts.rs @@ -2,6 +2,7 @@ use crate::errors::CompileError; use crate::yul::constructor; use crate::yul::mappers::functions; use crate::yul::runtime; +use fe_common::utils::keccak::get_full_signature; use fe_parser::ast as fe; use fe_parser::span::Spanned; use fe_semantics::Context; @@ -40,6 +41,20 @@ pub fn contract_def( let runtime = runtime::build_with_abi_dispatcher(context, stmt); + let data = if let Some(attributes) = context.get_contract(stmt) { + attributes + .strings + .clone() + .into_iter() + .map(|val| yul::Data { + name: get_full_signature(val.as_bytes()), + value: val, + }) + .collect::>() + } else { + vec![] + }; + return Ok(yul::Object { name: identifier! { Contract }, code: constructor, @@ -54,7 +69,11 @@ pub fn contract_def( }, }, objects: vec![], + // 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(), }], + data, }); } diff --git a/compiler/src/yul/mappers/expressions.rs b/compiler/src/yul/mappers/expressions.rs index dc2e310c75..d95077aa1d 100644 --- a/compiler/src/yul/mappers/expressions.rs +++ b/compiler/src/yul/mappers/expressions.rs @@ -2,6 +2,7 @@ use crate::errors::CompileError; use crate::yul::names; use crate::yul::operations::data as data_operations; use crate::yul::utils; +use fe_common::utils::keccak::get_full_signature; use fe_parser::ast as fe; use fe_parser::span::Spanned; use fe_semantics::builtins; @@ -15,10 +16,6 @@ use fe_semantics::{ Location, }; use std::convert::TryFrom; -use tiny_keccak::{ - Hasher, - Keccak, -}; use yultsur::*; /// Builds a Yul expression from a Fe expression. @@ -39,7 +36,7 @@ pub fn expr(context: &Context, exp: &Spanned) -> Result unimplemented!(), fe::Expr::ListComp { .. } => unimplemented!(), fe::Expr::Tuple { .. } => unimplemented!(), - fe::Expr::Str(_) => unimplemented!(), + fe::Expr::Str(_) => expr_str(exp), fe::Expr::Ellipsis => unimplemented!(), }?; @@ -272,6 +269,20 @@ fn expr_bool(exp: &Spanned) -> Result { unreachable!() } +fn expr_str(exp: &Spanned) -> Result { + if let fe::Expr::Str(lines) = &exp.node { + let content = lines.join(""); + let string_identifier = format!(r#""{}""#, get_full_signature(content.as_bytes())); + + let offset = expression! { dataoffset([literal_expression! { (string_identifier) }]) }; + let size = expression! { datasize([literal_expression! { (string_identifier) }]) }; + + return Ok(expression! {load_data_string([offset], [size])}); + } + + unreachable!() +} + fn expr_subscript( context: &Context, exp: &Spanned, @@ -338,13 +349,7 @@ fn expr_attribute_self( /// Converts a storage nonce into a pointer based on the keccak256 hash pub fn nonce_to_ptr(nonce: usize) -> yul::Expression { - let mut keccak = Keccak::v256(); - let mut ptr = [0u8; 32]; - - keccak.update(nonce.to_string().as_bytes()); - keccak.finalize(&mut ptr); - - let ptr = format!("0x{}", hex::encode(&ptr[0..32])); + let ptr = get_full_signature(nonce.to_string().as_bytes()); literal_expression! { (ptr) } } diff --git a/compiler/src/yul/runtime/functions/data.rs b/compiler/src/yul/runtime/functions/data.rs index 8896c7f938..256d6e12f4 100644 --- a/compiler/src/yul/runtime/functions/data.rs +++ b/compiler/src/yul/runtime/functions/data.rs @@ -50,6 +50,18 @@ pub fn ccopym() -> yul::Statement { } } +/// Load a static string from data into a newly allocated segment of memory. +pub fn load_data_string() -> yul::Statement { + function_definition! { + function load_data_string(code_ptr, size) -> mptr { + (mptr := alloc(32)) + (mstore(mptr, size)) + (let content_ptr := alloc(size)) + (datacopy(content_ptr, code_ptr, size)) + } + } +} + /// Copy memory to a given segment of storage. pub fn mcopys() -> yul::Statement { function_definition! { diff --git a/compiler/src/yul/runtime/functions/mod.rs b/compiler/src/yul/runtime/functions/mod.rs index 8784cbb747..cc15d8793a 100644 --- a/compiler/src/yul/runtime/functions/mod.rs +++ b/compiler/src/yul/runtime/functions/mod.rs @@ -12,6 +12,7 @@ pub fn std() -> Vec { data::alloc_mstoren(), data::free(), data::ccopym(), + data::load_data_string(), data::mcopys(), data::scopym(), data::mcopym(), diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index 067e796aa1..00372c09ea 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -749,6 +749,13 @@ fn strings() { Some(string_token("string 5")), ); + harness.test_function( + &mut executor, + "return_static_string", + vec![], + Some(string_token("The quick brown fox jumps over the lazy dog")), + ); + harness.events_emitted( executor, vec![( @@ -759,6 +766,7 @@ fn strings() { string_token("string 1"), string_token("string 3"), address_token("1000000000000000000000000000000000000001"), + string_token("static string"), ], )], ); diff --git a/compiler/tests/fixtures/strings.fe b/compiler/tests/fixtures/strings.fe index d101854cf1..aefee30adf 100644 --- a/compiler/tests/fixtures/strings.fe +++ b/compiler/tests/fixtures/strings.fe @@ -5,9 +5,13 @@ contract Foo: s1: string42 s3: string100 a: address + s4: string13 pub def __init__(s1: string42, a: address, s2: string26, u: u256, s3: string100): - emit MyEvent(s2, u, s1, s3, a) + emit MyEvent(s2, u, s1, s3, a, "static string") pub def bar(s1: string100, s2: string100) -> string100: - return s2 \ No newline at end of file + return s2 + + pub def return_static_string() -> string43: + return "The quick brown fox jumps over the lazy dog" diff --git a/newsfragments/186.feature.md b/newsfragments/186.feature.md new file mode 100644 index 0000000000..2c22b7d89e --- /dev/null +++ b/newsfragments/186.feature.md @@ -0,0 +1,11 @@ +Add support for string literals. + +Example: + +``` +def get_ticker_symbol() -> string3: + return "ETH" +``` + +String literals are stored in and loaded from the compiled bytecode. + diff --git a/parser/src/parsers.rs b/parser/src/parsers.rs index baaa52910d..bf921937b8 100644 --- a/parser/src/parsers.rs +++ b/parser/src/parsers.rs @@ -1449,7 +1449,13 @@ pub fn atom(input: Cursor) -> ParseResult> { span: tok.span, }), map(many1(string_token), |toks| { - let tok_strings: Vec<_> = toks.iter().map(|t| t.string).collect(); + let tok_strings: Vec<_> = toks + .iter() + .map(|t| { + // We don't want to carry quotes around strings past the parsing stage + &t.string[1..t.string.len() - 1] + }) + .collect(); let fst = toks.first().unwrap(); let snd = toks.last().unwrap(); diff --git a/parser/tests/fixtures/parsers/atom.ron b/parser/tests/fixtures/parsers/atom.ron index dbc014e833..f616c485a3 100644 --- a/parser/tests/fixtures/parsers/atom.ron +++ b/parser/tests/fixtures/parsers/atom.ron @@ -48,8 +48,8 @@ x ), Spanned( node: Str([ - "\"asdf\"", - "\"foo\"", + "asdf", + "foo", ]), span: Span( start: 14, diff --git a/semantics/Cargo.toml b/semantics/Cargo.toml index 5886672bdb..5a47aed9e4 100644 --- a/semantics/Cargo.toml +++ b/semantics/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +fe-common = {path = "../common", version = "0.0.1"} fe-parser = {path = "../parser", version = "0.0.1"} rstest = "0.6.4" tiny-keccak = { version = "2.0", features = ["keccak"] } diff --git a/semantics/src/lib.rs b/semantics/src/lib.rs index 4d7757c994..9d473dfac6 100644 --- a/semantics/src/lib.rs +++ b/semantics/src/lib.rs @@ -67,6 +67,8 @@ pub struct ContractAttributes { pub init_function: Option, /// Events that have been defined by the user. pub events: Vec, + /// Static strings that the contract defines + pub strings: Vec, } impl From> for ContractAttributes { @@ -102,6 +104,7 @@ impl From> for ContractAttributes { .values() .map(|event| event.to_owned()) .collect::>(), + strings: scope.string_defs.clone(), } } } diff --git a/semantics/src/namespace/events.rs b/semantics/src/namespace/events.rs index 2a6aacf820..01728bff6f 100644 --- a/semantics/src/namespace/events.rs +++ b/semantics/src/namespace/events.rs @@ -2,10 +2,7 @@ use crate::namespace::types::{ AbiEncoding, FixedSize, }; -use tiny_keccak::{ - Hasher, - Keccak, -}; +use fe_common::utils::keccak::get_full_signature; #[derive(Clone, Debug, PartialEq)] pub struct Event { @@ -59,19 +56,8 @@ impl Event { } fn build_event_topic(name: String, fields: Vec) -> String { - sig_keccak256(name, fields, 32) -} - -fn sig_keccak256(name: String, params: Vec, size: usize) -> String { - let signature = format!("{}({})", name, params.join(",")); - - let mut keccak = Keccak::v256(); - let mut selector = [0u8; 32]; - - keccak.update(signature.as_bytes()); - keccak.finalize(&mut selector); - - format!("0x{}", hex::encode(&selector[0..size])) + let signature = format!("{}({})", name, fields.join(",")); + get_full_signature(signature.as_bytes()) } #[cfg(test)] diff --git a/semantics/src/namespace/scopes.rs b/semantics/src/namespace/scopes.rs index ffeaad7366..3a3015de05 100644 --- a/semantics/src/namespace/scopes.rs +++ b/semantics/src/namespace/scopes.rs @@ -35,6 +35,7 @@ pub struct ContractScope { pub event_defs: HashMap, pub field_defs: HashMap, pub function_defs: HashMap, + pub string_defs: Vec, num_fields: usize, } @@ -96,6 +97,7 @@ impl ContractScope { function_defs: HashMap::new(), event_defs: HashMap::new(), field_defs: HashMap::new(), + string_defs: vec![], interface: vec![], num_fields: 0, })) @@ -154,6 +156,11 @@ impl ContractScope { pub fn add_event(&mut self, name: String, event: Event) { self.event_defs.insert(name, event); } + + /// Add a static string definition to the scope. + pub fn add_string(&mut self, value: String) { + self.string_defs.push(value); + } } impl BlockScope { diff --git a/semantics/src/traversal/expressions.rs b/semantics/src/traversal/expressions.rs index 92830ca446..43a440c801 100644 --- a/semantics/src/traversal/expressions.rs +++ b/semantics/src/traversal/expressions.rs @@ -10,6 +10,7 @@ use crate::namespace::operations; use crate::namespace::types::{ Array, Base, + FeString, FixedSize, Integer, Type, @@ -51,7 +52,7 @@ pub fn expr( fe::Expr::List { .. } => expr_list(scope, Rc::clone(&context), exp), fe::Expr::ListComp { .. } => unimplemented!(), fe::Expr::Tuple { .. } => unimplemented!(), - fe::Expr::Str(_) => unimplemented!(), + fe::Expr::Str(_) => expr_str(scope, exp), fe::Expr::Ellipsis => unimplemented!(), } .map_err(|error| error.with_context(exp.span))?; @@ -197,6 +198,30 @@ fn expr_name( unreachable!() } +fn expr_str( + scope: Shared, + exp: &Spanned, +) -> Result { + if let fe::Expr::Str(lines) = &exp.node { + let string_length = lines.iter().map(|val| val.len()).sum(); + let string_val = lines.join(""); + scope + .borrow_mut() + .contract_scope() + .borrow_mut() + .add_string(string_val); + + return Ok(ExpressionAttributes::new( + Type::String(FeString { + max_size: string_length, + }), + Location::Memory, + )); + } + + unreachable!() +} + fn expr_bool(exp: &Spanned) -> Result { if let fe::Expr::Bool(_) = &exp.node { return Ok(ExpressionAttributes::new(