Skip to content

Commit

Permalink
Implement keccak256 as builtin method
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Feb 18, 2021
1 parent 23d95b1 commit f5f2afe
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 7 deletions.
11 changes: 10 additions & 1 deletion analyzer/src/builtins.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use strum::EnumString;
use strum::{
EnumString,
IntoStaticStr,
};

#[derive(Debug, PartialEq, EnumString)]
#[strum(serialize_all = "snake_case")]
Expand All @@ -9,6 +12,12 @@ pub enum ValueMethod {
AbiEncodePacked,
}

#[derive(Clone, Debug, PartialEq, EnumString, IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum GlobalMethod {
Keccak256,
}

#[derive(Debug, PartialEq, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum ContractTypeMethod {
Expand Down
2 changes: 2 additions & 0 deletions analyzer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::namespace::types::{
Struct,
Type,
};
use builtins::GlobalMethod;
use fe_parser::ast as fe;
use fe_parser::span::{
Span,
Expand Down Expand Up @@ -222,6 +223,7 @@ impl ExpressionAttributes {
/// The type of a function call.
#[derive(Clone, Debug, PartialEq)]
pub enum CallType {
BuiltinFunction { func: GlobalMethod },
TypeConstructor { typ: Type },
SelfAttribute { func_name: String },
ValueAttribute,
Expand Down
33 changes: 33 additions & 0 deletions analyzer/src/traversal/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::{
};

use crate::builtins::ContractTypeMethod;
use builtins::GlobalMethod;
use fe_parser::ast as fe;
use fe_parser::span::Spanned;
use std::convert::TryFrom;
Expand Down Expand Up @@ -456,6 +457,9 @@ fn expr_call(
) -> Result<ExpressionAttributes, SemanticError> {
if let fe::Expr::Call { func, args } = &exp.node {
return match expr_call_type(Rc::clone(&scope), Rc::clone(&context), func)? {
CallType::BuiltinFunction { func } => {
expr_call_builtin_function(scope, context, func, args)
}
CallType::TypeConstructor { typ } => {
expr_call_type_constructor(scope, context, typ, args)
}
Expand All @@ -472,6 +476,32 @@ fn expr_call(
unreachable!()
}

fn expr_call_builtin_function(
scope: Shared<BlockScope>,
context: Shared<Context>,
typ: GlobalMethod,
args: &Spanned<Vec<Spanned<fe::CallArg>>>,
) -> Result<ExpressionAttributes, SemanticError> {
let argument_attributes = expr_call_args(Rc::clone(&scope), Rc::clone(&context), args)?;
match typ {
GlobalMethod::Keccak256 => {
if argument_attributes.len() != 1 {
return Err(SemanticError::wrong_number_of_params());
}
if !matches!(
expression_attributes_to_types(argument_attributes).first(),
Some(Type::Array(Array {
inner: Base::Byte,
..
}))
) {
return Err(SemanticError::type_error());
}
Ok(ExpressionAttributes::new(Type::Base(U256), Location::Value))
}
}
}

fn expr_call_struct_constructor(
scope: Shared<BlockScope>,
context: Shared<Context>,
Expand Down Expand Up @@ -745,6 +775,9 @@ fn expr_name_call_type(
name: &str,
) -> Result<CallType, SemanticError> {
match name {
"keccak256" => Ok(CallType::BuiltinFunction {
func: GlobalMethod::Keccak256,
}),
"address" => Ok(CallType::TypeConstructor {
typ: Type::Base(Base::Address),
}),
Expand Down
10 changes: 8 additions & 2 deletions common/src/utils/keccak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ pub fn get_full_signature(content: &[u8]) -> String {
get_partial_signature(content, 32)
}

pub fn get_partial_signature(content: &[u8], size: usize) -> String {
/// Return the keccak256 hash of the given content as an array of bytes
pub fn get_keccak256(content: &[u8]) -> [u8; 32] {
let mut keccak = Keccak::v256();
let mut selector = [0u8; 32];

keccak.update(content);
keccak.finalize(&mut selector);

format!("0x{}", hex::encode(&selector[0..size]))
selector
}

pub fn get_partial_signature(content: &[u8], size: usize) -> String {
get_keccak256(content);
format!("0x{}", hex::encode(&get_keccak256(content)[0..size]))
}
25 changes: 21 additions & 4 deletions compiler/src/yul/mappers/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ use crate::yul::operations::{
structs as struct_operations,
};
use crate::yul::utils;
use fe_analyzer::builtins::ContractTypeMethod;
use fe_analyzer::builtins::{
ContractTypeMethod,
GlobalMethod,
};
use fe_analyzer::namespace::types::{
FeSized,
FixedSize,
Type,
};
Expand Down Expand Up @@ -57,7 +61,7 @@ pub fn expr(context: &Context, exp: &Spanned<fe::Expr>) -> Result<yul::Expressio
(_, None) => Ok(expression),
}
} else {
panic!("missing expression attributes")
panic!("missing expression attributes for {:?}", exp)
}
}

Expand All @@ -67,8 +71,7 @@ fn move_expression(
from: Location,
to: Location,
) -> Result<yul::Expression, CompileError> {
let typ =
FixedSize::try_from(typ).map_err(|_| CompileError::static_str("invalid attributes"))?;
let typ = FixedSize::try_from(typ).expect("Invalid type");

match (from.clone(), to.clone()) {
(Location::Storage { .. }, Location::Value) => Ok(data_operations::sload(typ, val)),
Expand Down Expand Up @@ -105,6 +108,20 @@ fn expr_call(context: &Context, exp: &Spanned<fe::Expr>) -> Result<yul::Expressi
.collect::<Result<_, _>>()?;

return match call_type {
CallType::BuiltinFunction { func } => match func {
GlobalMethod::Keccak256 => {
let first_arg = args.node.first().expect("Missing argument");
let arg_expr = context
.get_expression(first_arg)
.expect("invalid attributes");
let size = FixedSize::try_from(arg_expr.typ.clone()).expect("Invalid type");
let func_name: &str = func.into();

let func_name = identifier! { (func_name) };
let size = identifier_expression! { (size.size()) };
Ok(expression! { [func_name]([yul_args[0].to_owned()], [size]) })
}
},
CallType::TypeConstructor {
typ: Type::Struct(val),
} => Ok(struct_operations::new(val, yul_args)),
Expand Down
1 change: 1 addition & 0 deletions compiler/tests/compile_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::fs;
fixture_file,
expected_error,
case("call_event_with_wrong_types.fe", "TypeError"),
case("keccak_called_with_wrong_type.fe", "TypeError"),
case("continue_without_loop.fe", "ContinueWithoutLoop"),
case("continue_without_loop_2.fe", "ContinueWithoutLoop"),
case("break_without_loop.fe", "BreakWithoutLoop"),
Expand Down
59 changes: 59 additions & 0 deletions compiler/tests/evm_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use std::collections::BTreeMap;
use std::iter;

mod utils;
use fe_common::utils::keccak::get_keccak256;
use utils::ToBeBytes;
use utils::*;

#[test]
Expand Down Expand Up @@ -713,6 +715,63 @@ fn structs() {
});
}

#[test]
fn keccak() {
with_executor(&|mut executor| {
let harness = deploy_contract(&mut executor, "keccak.fe", "Keccak", &[]);
// The expected value in clear text is on purpose for added clarity. All other
// tests use get_keccak256 to calculate the expected results on the fly.
harness.test_function(
&mut executor,
"return_hash_from_u256",
&[ethabi::Token::FixedBytes(
U256::from(1).to_be_bytes().to_vec(),
)],
Some(&uint_token_from_dec_str(
"80084422859880547211683076133703299733277748156566366325829078699459944778998",
)),
);

harness.test_function(
&mut executor,
"return_hash_from_u256",
&[ethabi::Token::FixedBytes(
U256::from(1).to_be_bytes().to_vec(),
)],
Some(&ethabi::Token::Uint(
get_keccak256(&U256::from(1).to_be_bytes()).into(),
)),
);

harness.test_function(
&mut executor,
"return_hash_from_u8",
&[ethabi::Token::FixedBytes([1].into())],
Some(&ethabi::Token::Uint(
get_keccak256(&1u8.to_be_bytes()).into(),
)),
);

harness.test_function(
&mut executor,
"return_hash_from_u8",
&[ethabi::Token::FixedBytes([0].into())],
Some(&ethabi::Token::Uint(
get_keccak256(&0u8.to_be_bytes()).into(),
)),
);

harness.test_function(
&mut executor,
"return_hash_from_foo",
&[bytes_token("foo")],
Some(&ethabi::Token::Uint(
get_keccak256(&"foo".as_bytes()).into(),
)),
);
});
}

#[test]
fn erc20_token() {
with_executor(&|mut executor| {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
contract Foo:

pub def bar() -> u256:
wrong: u256[1]
return keccak256(wrong)
10 changes: 10 additions & 0 deletions compiler/tests/fixtures/keccak.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
contract Keccak:

pub def return_hash_from_u8(val: bytes[1]) -> u256:
return keccak256(val)

pub def return_hash_from_foo(val: bytes[3]) -> u256:
return keccak256(val)

pub def return_hash_from_u256(val: bytes[32]) -> u256:
return keccak256(val)
17 changes: 17 additions & 0 deletions compiler/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ use std::str::FromStr;
use stringreader::StringReader;
use yultsur::*;

pub trait ToBeBytes {
fn to_be_bytes(&self) -> [u8; 32];
}

impl ToBeBytes for U256 {
fn to_be_bytes(&self) -> [u8; 32] {
let mut input_bytes: [u8; 32] = [0; 32];
self.to_big_endian(&mut input_bytes);
input_bytes
}
}

#[allow(dead_code)]
pub type Executor<'a> = evm::executor::StackExecutor<'a, 'a, evm::backend::MemoryBackend<'a>>;

Expand Down Expand Up @@ -268,6 +280,11 @@ pub fn uint_token(n: usize) -> ethabi::Token {
ethabi::Token::Uint(U256::from(n))
}

#[allow(dead_code)]
pub fn uint_token_from_dec_str(val: &str) -> ethabi::Token {
ethabi::Token::Uint(U256::from_dec_str(val).expect("Not a valid dec string"))
}

#[allow(dead_code)]
pub fn int_token(val: isize) -> ethabi::Token {
ethabi::Token::Int(to_2s_complement(val))
Expand Down
10 changes: 10 additions & 0 deletions newsfragments/255.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Implement global `keccak256` method. The method expects one parameter of `bytes[n]`
and returns the hash as an `u256`. In a future version `keccak256` will most likely
be moved behind an import so that it has to be imported (e.g. `from std.crypto import keccak256`).

Example:

```
pub def hash_single_byte(val: bytes[1]) -> u256:
return keccak256(val)
```

0 comments on commit f5f2afe

Please sign in to comment.