Skip to content

Commit

Permalink
Add support for string literals
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Jan 20, 2021
1 parent 98f20e2 commit c738036
Show file tree
Hide file tree
Showing 21 changed files with 172 additions and 54 deletions.
13 changes: 11 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "fe-common"
version = "0.0.1"
authors = ["Ethereum Foundation <snakecharmers@ethereum.org>"]
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"
1 change: 1 addition & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod utils;
18 changes: 18 additions & 0 deletions common/src/utils/keccak.rs
Original file line number Diff line number Diff line change
@@ -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]))
}
1 change: 1 addition & 0 deletions common/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod keccak;
4 changes: 2 additions & 2 deletions compiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 5 additions & 15 deletions compiler/src/abi/utils.rs
Original file line number Diff line number Diff line change
@@ -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>) -> 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>) -> String {
sig_keccak256(name, params, 4)
sign_event_or_func(name, params, 4)
}

fn sig_keccak256(name: String, params: Vec<String>, size: usize) -> String {
fn sign_event_or_func(name: String, params: Vec<String>, 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)
}
19 changes: 19 additions & 0 deletions compiler/src/yul/mappers/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<Vec<_>>()
} else {
vec![]
};

return Ok(yul::Object {
name: identifier! { Contract },
code: constructor,
Expand All @@ -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,
});
}

Expand Down
29 changes: 17 additions & 12 deletions compiler/src/yul/mappers/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -39,7 +36,7 @@ pub fn expr(context: &Context, exp: &Spanned<fe::Expr>) -> Result<yul::Expressio
fe::Expr::List { .. } => unimplemented!(),
fe::Expr::ListComp { .. } => unimplemented!(),
fe::Expr::Tuple { .. } => unimplemented!(),
fe::Expr::Str(_) => unimplemented!(),
fe::Expr::Str(_) => expr_str(exp),
fe::Expr::Ellipsis => unimplemented!(),
}?;

Expand Down Expand Up @@ -272,6 +269,20 @@ fn expr_bool(exp: &Spanned<fe::Expr>) -> Result<yul::Expression, CompileError> {
unreachable!()
}

fn expr_str(exp: &Spanned<fe::Expr>) -> Result<yul::Expression, CompileError> {
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<fe::Expr>,
Expand Down Expand Up @@ -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) }
}

Expand Down
12 changes: 12 additions & 0 deletions compiler/src/yul/runtime/functions/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down
1 change: 1 addition & 0 deletions compiler/src/yul/runtime/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub fn std() -> Vec<yul::Statement> {
data::alloc_mstoren(),
data::free(),
data::ccopym(),
data::load_data_string(),
data::mcopys(),
data::scopym(),
data::mcopym(),
Expand Down
8 changes: 8 additions & 0 deletions compiler/tests/evm_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![(
Expand All @@ -759,6 +766,7 @@ fn strings() {
string_token("string 1"),
string_token("string 3"),
address_token("1000000000000000000000000000000000000001"),
string_token("static string"),
],
)],
);
Expand Down
8 changes: 6 additions & 2 deletions compiler/tests/fixtures/strings.fe
Original file line number Diff line number Diff line change
Expand Up @@ -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
return s2

pub def return_static_string() -> string43:
return "The quick brown fox jumps over the lazy dog"
11 changes: 11 additions & 0 deletions newsfragments/186.feature.md
Original file line number Diff line number Diff line change
@@ -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.

8 changes: 7 additions & 1 deletion parser/src/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1449,7 +1449,13 @@ pub fn atom(input: Cursor) -> ParseResult<Spanned<Expr>> {
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();
Expand Down
4 changes: 2 additions & 2 deletions parser/tests/fixtures/parsers/atom.ron
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ x
),
Spanned(
node: Str([
"\"asdf\"",
"\"foo\"",
"asdf",
"foo",
]),
span: Span(
start: 14,
Expand Down
1 change: 1 addition & 0 deletions semantics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
3 changes: 3 additions & 0 deletions semantics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub struct ContractAttributes {
pub init_function: Option<FunctionAttributes>,
/// Events that have been defined by the user.
pub events: Vec<Event>,
/// Static strings that the contract defines
pub strings: Vec<String>,
}

impl From<Ref<'_, ContractScope>> for ContractAttributes {
Expand Down Expand Up @@ -102,6 +104,7 @@ impl From<Ref<'_, ContractScope>> for ContractAttributes {
.values()
.map(|event| event.to_owned())
.collect::<Vec<Event>>(),
strings: scope.string_defs.clone(),
}
}
}
Expand Down
20 changes: 3 additions & 17 deletions semantics/src/namespace/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -59,19 +56,8 @@ impl Event {
}

fn build_event_topic(name: String, fields: Vec<String>) -> String {
sig_keccak256(name, fields, 32)
}

fn sig_keccak256(name: String, params: Vec<String>, 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)]
Expand Down
Loading

0 comments on commit c738036

Please sign in to comment.