From f5f2afeb29047006936ff3490387f315134cb382 Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Tue, 16 Feb 2021 15:35:59 +0100 Subject: [PATCH] Implement keccak256 as builtin method --- analyzer/src/builtins.rs | 11 +++- analyzer/src/lib.rs | 2 + analyzer/src/traversal/expressions.rs | 33 +++++++++++ common/src/utils/keccak.rs | 10 +++- compiler/src/yul/mappers/expressions.rs | 25 ++++++-- compiler/tests/compile_errors.rs | 1 + compiler/tests/evm_contracts.rs | 59 +++++++++++++++++++ .../keccak_called_with_wrong_type.fe | 5 ++ compiler/tests/fixtures/keccak.fe | 10 ++++ compiler/tests/utils.rs | 17 ++++++ newsfragments/255.feature.md | 10 ++++ 11 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 compiler/tests/fixtures/compile_errors/keccak_called_with_wrong_type.fe create mode 100644 compiler/tests/fixtures/keccak.fe create mode 100644 newsfragments/255.feature.md diff --git a/analyzer/src/builtins.rs b/analyzer/src/builtins.rs index 4a28505f7c..034b48d972 100644 --- a/analyzer/src/builtins.rs +++ b/analyzer/src/builtins.rs @@ -1,4 +1,7 @@ -use strum::EnumString; +use strum::{ + EnumString, + IntoStaticStr, +}; #[derive(Debug, PartialEq, EnumString)] #[strum(serialize_all = "snake_case")] @@ -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 { diff --git a/analyzer/src/lib.rs b/analyzer/src/lib.rs index d3f8639409..be2df1ec13 100644 --- a/analyzer/src/lib.rs +++ b/analyzer/src/lib.rs @@ -22,6 +22,7 @@ use crate::namespace::types::{ Struct, Type, }; +use builtins::GlobalMethod; use fe_parser::ast as fe; use fe_parser::span::{ Span, @@ -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, diff --git a/analyzer/src/traversal/expressions.rs b/analyzer/src/traversal/expressions.rs index a4cf3f41a0..2f4c6f5cd9 100644 --- a/analyzer/src/traversal/expressions.rs +++ b/analyzer/src/traversal/expressions.rs @@ -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; @@ -456,6 +457,9 @@ fn expr_call( ) -> Result { 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) } @@ -472,6 +476,32 @@ fn expr_call( unreachable!() } +fn expr_call_builtin_function( + scope: Shared, + context: Shared, + typ: GlobalMethod, + args: &Spanned>>, +) -> Result { + 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, context: Shared, @@ -745,6 +775,9 @@ fn expr_name_call_type( name: &str, ) -> Result { match name { + "keccak256" => Ok(CallType::BuiltinFunction { + func: GlobalMethod::Keccak256, + }), "address" => Ok(CallType::TypeConstructor { typ: Type::Base(Base::Address), }), diff --git a/common/src/utils/keccak.rs b/common/src/utils/keccak.rs index 9381733646..b327768dde 100644 --- a/common/src/utils/keccak.rs +++ b/common/src/utils/keccak.rs @@ -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])) } diff --git a/compiler/src/yul/mappers/expressions.rs b/compiler/src/yul/mappers/expressions.rs index d9e8f90b93..7d69f15e7b 100644 --- a/compiler/src/yul/mappers/expressions.rs +++ b/compiler/src/yul/mappers/expressions.rs @@ -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, }; @@ -57,7 +61,7 @@ pub fn expr(context: &Context, exp: &Spanned) -> Result Ok(expression), } } else { - panic!("missing expression attributes") + panic!("missing expression attributes for {:?}", exp) } } @@ -67,8 +71,7 @@ fn move_expression( from: Location, to: Location, ) -> Result { - 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)), @@ -105,6 +108,20 @@ fn expr_call(context: &Context, exp: &Spanned) -> 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)), diff --git a/compiler/tests/compile_errors.rs b/compiler/tests/compile_errors.rs index a243748e6b..212fdc976a 100644 --- a/compiler/tests/compile_errors.rs +++ b/compiler/tests/compile_errors.rs @@ -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"), diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index 3a04be9a29..3ebb3dfead 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -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] @@ -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(ðabi::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(ðabi::Token::Uint( + get_keccak256(&1u8.to_be_bytes()).into(), + )), + ); + + harness.test_function( + &mut executor, + "return_hash_from_u8", + &[ethabi::Token::FixedBytes([0].into())], + Some(ðabi::Token::Uint( + get_keccak256(&0u8.to_be_bytes()).into(), + )), + ); + + harness.test_function( + &mut executor, + "return_hash_from_foo", + &[bytes_token("foo")], + Some(ðabi::Token::Uint( + get_keccak256(&"foo".as_bytes()).into(), + )), + ); + }); +} + #[test] fn erc20_token() { with_executor(&|mut executor| { diff --git a/compiler/tests/fixtures/compile_errors/keccak_called_with_wrong_type.fe b/compiler/tests/fixtures/compile_errors/keccak_called_with_wrong_type.fe new file mode 100644 index 0000000000..d87c375977 --- /dev/null +++ b/compiler/tests/fixtures/compile_errors/keccak_called_with_wrong_type.fe @@ -0,0 +1,5 @@ +contract Foo: + + pub def bar() -> u256: + wrong: u256[1] + return keccak256(wrong) diff --git a/compiler/tests/fixtures/keccak.fe b/compiler/tests/fixtures/keccak.fe new file mode 100644 index 0000000000..622b1996ba --- /dev/null +++ b/compiler/tests/fixtures/keccak.fe @@ -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) diff --git a/compiler/tests/utils.rs b/compiler/tests/utils.rs index a9c4ca8bbd..a9da650ced 100644 --- a/compiler/tests/utils.rs +++ b/compiler/tests/utils.rs @@ -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>>; @@ -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)) diff --git a/newsfragments/255.feature.md b/newsfragments/255.feature.md new file mode 100644 index 0000000000..767d00833b --- /dev/null +++ b/newsfragments/255.feature.md @@ -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) +```