diff --git a/huff_lexer/src/lib.rs b/huff_lexer/src/lib.rs index 761645f3..cb383805 100644 --- a/huff_lexer/src/lib.rs +++ b/huff_lexer/src/lib.rs @@ -439,7 +439,7 @@ impl<'a> Iterator for Lexer<'a> { } } - // Check for macro keyword + // Check for free storage pointer builtin let fsp = "FREE_STORAGE_POINTER"; let token_length = fsp.len() - 1; let peeked = self.peek_n_chars(token_length); @@ -503,8 +503,15 @@ impl<'a> Iterator for Lexer<'a> { self.dyn_consume(|c| c.is_alphanumeric() || *c == '[' || *c == ']'); // got a type at this point, we have to know which let raw_type: String = self.slice(); - // check for arrays first - if EVM_TYPE_ARRAY_REGEX.is_match(&raw_type) { + + // Check if calldata, memory, or storage + if raw_type == TokenKind::Calldata.to_string() { + found_kind = Some(TokenKind::Calldata); + } else if raw_type == TokenKind::Memory.to_string() { + found_kind = Some(TokenKind::Memory); + } else if raw_type == TokenKind::Storage.to_string() { + found_kind = Some(TokenKind::Storage); + } else if EVM_TYPE_ARRAY_REGEX.is_match(&raw_type) { // split to get array size and type // TODO: support multi-dimensional arrays let words: Vec = Regex::new(r"\[") diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs index 2dd4a18d..d524582e 100644 --- a/huff_parser/src/lib.rs +++ b/huff_parser/src/lib.rs @@ -789,6 +789,7 @@ impl Parser { ) -> Result, ParserError> { let mut args: Vec = Vec::new(); self.match_kind(TokenKind::OpenParen)?; + let mut on_type = true; tracing::debug!(target: "parser", "PARSING ARGs: {:?}", self.current_token.kind); while !self.check(TokenKind::CloseParen) { if is_builtin { @@ -801,6 +802,7 @@ impl Parser { arg_type: None, indexed: false, span: AstSpan(vec![self.current_token.span.clone()]), + arg_location: None, }); self.consume(); @@ -812,8 +814,9 @@ impl Parser { // present. if let TokenKind::Literal(l) = &self.current_token.kind { args.push(Argument { - name: Some(bytes32_to_string(l, false)), /* Place the literal in the - * "name" field */ + // Place literal in the "name" field + name: Some(bytes32_to_string(l, false)), + arg_location: None, arg_type: None, indexed: false, span: AstSpan(vec![self.current_token.span.clone()]), @@ -837,17 +840,73 @@ impl Parser { arg_spans.push(self.current_token.span.clone()); self.consume(); // consume "indexed" keyword } + on_type = false; + } + + // It can also be a data location + match &self.current_token.kind { + TokenKind::Calldata => { + arg.arg_location = Some(ArgumentLocation::Calldata); + arg_spans.push(self.current_token.span.clone()); + self.consume(); + } + TokenKind::Memory => { + arg.arg_location = Some(ArgumentLocation::Memory); + arg_spans.push(self.current_token.span.clone()); + self.consume(); + } + TokenKind::Storage => { + arg.arg_location = Some(ArgumentLocation::Storage); + arg_spans.push(self.current_token.span.clone()); + self.consume(); + } + _ => {} } // name comes second (is optional) - if select_name && self.check(TokenKind::Ident("x".to_string())) { + if select_name && + (self.check(TokenKind::Ident("x".to_string())) || + self.check(TokenKind::PrimitiveType(PrimitiveEVMType::Address))) + { + // We need to check if the name is a keyword - not the type + if !on_type { + // Check for reserved primitive type keyword use and throw an error if so + match self.current_token.kind.clone() { + TokenKind::Ident(arg_str) => { + if PrimitiveEVMType::try_from(arg_str.clone()).is_ok() { + return Err(ParserError { + kind: ParserErrorKind::InvalidTypeAsArgumentName( + self.current_token.kind.clone(), + ), + hint: Some(format!( + "Argument names cannot be EVM types: {}", + arg_str + )), + spans: AstSpan(vec![self.current_token.span.clone()]), + }) + } + } + TokenKind::PrimitiveType(ty) => { + return Err(ParserError { + kind: ParserErrorKind::InvalidTypeAsArgumentName( + self.current_token.kind.clone(), + ), + hint: Some(format!("Argument names cannot be EVM types: {}", ty)), + spans: AstSpan(vec![self.current_token.span.clone()]), + }) + } + _ => { /* continue, valid string */ } + } + } arg_spans.push(self.current_token.span.clone()); - arg.name = Some(self.match_kind(TokenKind::Ident("x".to_string()))?.to_string()) + arg.name = Some(self.match_kind(TokenKind::Ident("x".to_string()))?.to_string()); + on_type = !on_type; } // multiple args possible if self.check(TokenKind::Comma) { self.consume(); + on_type = true; } // If both arg type and arg name are none, we didn't consume anything @@ -907,6 +966,10 @@ impl Parser { args.push(MacroArg::Ident(ident)); self.consume(); } + TokenKind::Calldata => { + args.push(MacroArg::Ident("calldata".to_string())); + self.consume(); + } TokenKind::LeftAngle => { // Passed into the Macro Call like: // GET_SLOT_FROM_KEY() // [slot] diff --git a/huff_parser/tests/error.rs b/huff_parser/tests/error.rs index 73958647..10703fd5 100644 --- a/huff_parser/tests/error.rs +++ b/huff_parser/tests/error.rs @@ -22,7 +22,8 @@ fn test_parses_custom_error() { arg_type: Some(String::from("uint256")), name: None, indexed: false, - span: AstSpan(vec![Span { start: 24, end: 31, file: None }]) + span: AstSpan(vec![Span { start: 24, end: 31, file: None }]), + arg_location: None, }], span: AstSpan(vec![ Span { start: 0, end: 7, file: None }, diff --git a/huff_parser/tests/event.rs b/huff_parser/tests/event.rs index b24750fe..ab3e384e 100644 --- a/huff_parser/tests/event.rs +++ b/huff_parser/tests/event.rs @@ -2,6 +2,74 @@ use huff_lexer::*; use huff_parser::*; use huff_utils::{ast::Event, prelude::*}; +#[test] +fn test_prefix_event_arg_names_with_reserved_keywords() { + let source: &str = "#define event TestEvent(bytes4 indexed interfaceId, uint256 uintTest, bool stringMe, string boolean)"; + let flattened_source = FullFileSource { source, file: None, spans: vec![] }; + let lexer = Lexer::new(flattened_source); + let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::>(); + let expected_tokens: Vec = vec![ + Token { kind: TokenKind::Define, span: Span { start: 0, end: 7, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 7, end: 8, file: None } }, + Token { kind: TokenKind::Event, span: Span { start: 8, end: 13, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 13, end: 14, file: None } }, + Token { + kind: TokenKind::Ident("TestEvent".to_string()), + span: Span { start: 14, end: 23, file: None }, + }, + Token { kind: TokenKind::OpenParen, span: Span { start: 23, end: 24, file: None } }, + Token { + kind: TokenKind::PrimitiveType(PrimitiveEVMType::Bytes(4)), + span: Span { start: 24, end: 30, file: None }, + }, + Token { kind: TokenKind::Whitespace, span: Span { start: 30, end: 31, file: None } }, + Token { kind: TokenKind::Indexed, span: Span { start: 31, end: 38, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 38, end: 39, file: None } }, + Token { + kind: TokenKind::Ident("interfaceId".to_string()), + span: Span { start: 39, end: 50, file: None }, + }, + Token { kind: TokenKind::Comma, span: Span { start: 50, end: 51, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 51, end: 52, file: None } }, + Token { + kind: TokenKind::PrimitiveType(PrimitiveEVMType::Uint(256)), + span: Span { start: 52, end: 59, file: None }, + }, + Token { kind: TokenKind::Whitespace, span: Span { start: 59, end: 60, file: None } }, + Token { + kind: TokenKind::Ident("uintTest".to_string()), + span: Span { start: 60, end: 68, file: None }, + }, + Token { kind: TokenKind::Comma, span: Span { start: 68, end: 69, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 69, end: 70, file: None } }, + Token { + kind: TokenKind::PrimitiveType(PrimitiveEVMType::Bool), + span: Span { start: 70, end: 74, file: None }, + }, + Token { kind: TokenKind::Whitespace, span: Span { start: 74, end: 75, file: None } }, + Token { + kind: TokenKind::Ident("stringMe".to_string()), + span: Span { start: 75, end: 83, file: None }, + }, + Token { kind: TokenKind::Comma, span: Span { start: 83, end: 84, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 84, end: 85, file: None } }, + Token { + kind: TokenKind::PrimitiveType(PrimitiveEVMType::String), + span: Span { start: 85, end: 91, file: None }, + }, + Token { kind: TokenKind::Whitespace, span: Span { start: 91, end: 92, file: None } }, + Token { + kind: TokenKind::Ident("boolean".to_string()), + span: Span { start: 92, end: 99, file: None }, + }, + Token { kind: TokenKind::CloseParen, span: Span { start: 99, end: 100, file: None } }, + Token { kind: TokenKind::Eof, span: Span { start: 100, end: 100, file: None } }, + ]; + assert_eq!(expected_tokens, tokens); + let mut parser = Parser::new(tokens, None); + parser.parse().unwrap(); +} + #[test] fn test_parse_event() { let sources = [ @@ -14,6 +82,7 @@ fn test_parse_event() { arg_type: Some(String::from("uint256")), name: Some(String::from("a")), indexed: true, + arg_location: None, span: AstSpan(vec![ // "uint256" Span { start: 24, end: 31, file: None }, @@ -27,6 +96,7 @@ fn test_parse_event() { arg_type: Some(String::from("uint8")), name: None, indexed: true, + arg_location: None, span: AstSpan(vec![ // "uint8" Span { start: 42, end: 47, file: None }, @@ -74,6 +144,7 @@ fn test_parse_event() { arg_type: Some(String::from("uint256")), name: None, indexed: false, + arg_location: None, span: AstSpan(vec![ // "uint256" Span { start: 24, end: 31, file: None }, @@ -83,6 +154,7 @@ fn test_parse_event() { arg_type: Some(String::from("uint8")), name: Some(String::from("b")), indexed: false, + arg_location: None, span: AstSpan(vec![ // "uint8" Span { start: 32, end: 37, file: None }, @@ -126,6 +198,7 @@ fn test_parse_event() { arg_type: Some(String::from("uint256")), name: None, indexed: true, + arg_location: None, span: AstSpan(vec![ // "uint256" Span { start: 24, end: 31, file: None }, @@ -137,6 +210,7 @@ fn test_parse_event() { arg_type: Some(String::from("uint8")), name: None, indexed: false, + arg_location: None, span: AstSpan(vec![ // "uint8" Span { start: 40, end: 45, file: None }, diff --git a/huff_parser/tests/function.rs b/huff_parser/tests/function.rs index 95f69899..4813429e 100644 --- a/huff_parser/tests/function.rs +++ b/huff_parser/tests/function.rs @@ -24,12 +24,14 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 22, end: 29, file: None }]), }, Argument { name: Some(String::from("b")), arg_type: Some(String::from("bool")), indexed: false, + arg_location: None, span: AstSpan(vec![ Span { start: 30, end: 34, file: None }, Span { start: 35, end: 36, file: None }, @@ -41,6 +43,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 51, end: 58, file: None }]), }], signature: [84, 204, 215, 119], @@ -70,6 +73,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 22, end: 29, file: None }]), }], fn_type: FunctionType::Pure, @@ -77,6 +81,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 44, end: 51, file: None }]), }], signature: [41, 233, 159, 7], @@ -103,6 +108,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 22, end: 29, file: None }]), }], fn_type: FunctionType::NonPayable, @@ -110,6 +116,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 50, end: 57, file: None }]), }], signature: [41, 233, 159, 7], @@ -136,6 +143,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 22, end: 29, file: None }]), }], fn_type: FunctionType::Payable, @@ -143,6 +151,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 47, end: 54, file: None }]), }], signature: [41, 233, 159, 7], @@ -169,6 +178,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256[], bool[5]")), indexed: false, + arg_location: None, span: AstSpan(vec![]), }], fn_type: FunctionType::Payable, @@ -176,6 +186,7 @@ fn parses_valid_function_definition() { name: None, arg_type: Some(String::from("uint256")), indexed: false, + arg_location: None, span: AstSpan(vec![]), }], signature: [5, 191, 166, 243], @@ -211,3 +222,69 @@ fn cannot_parse_invalid_function_definition() { let mut parser = Parser::new(tokens, None); parser.parse().unwrap(); } + +#[test] +#[should_panic] +fn test_functions_with_keyword_arg_names_errors() { + // The function parameter's name is a reserved keyword; this should throw an error + let source: &str = "#define function myFunc(uint256 uint256) pure returns(uint256)"; + let flattened_source = FullFileSource { source, file: None, spans: vec![] }; + let lexer = Lexer::new(flattened_source); + let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::>(); + let mut parser = Parser::new(tokens, None); + parser.parse().unwrap(); +} + +#[test] +fn test_functions_with_argument_locations() { + let source: &str = "#define function myFunc(string calldata test, uint256[] storage) pure returns(bytes memory)"; + let flattened_source = FullFileSource { source, file: None, spans: vec![] }; + let lexer = Lexer::new(flattened_source); + let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::>(); + let mut parser = Parser::new(tokens, None); + parser.parse().unwrap(); +} + +#[test] +fn test_can_prefix_function_arg_names_with_reserved_keywords() { + let source: &str = "#define function supportsInterface(bytes4 interfaceId) view returns (bool)"; + let flattened_source = FullFileSource { source, file: None, spans: vec![] }; + let lexer = Lexer::new(flattened_source); + let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::>(); + let expected_tokens: Vec = vec![ + Token { kind: TokenKind::Define, span: Span { start: 0, end: 7, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 7, end: 8, file: None } }, + Token { kind: TokenKind::Function, span: Span { start: 8, end: 16, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 16, end: 17, file: None } }, + Token { + kind: TokenKind::Ident("supportsInterface".to_string()), + span: Span { start: 17, end: 34, file: None }, + }, + Token { kind: TokenKind::OpenParen, span: Span { start: 34, end: 35, file: None } }, + Token { + kind: TokenKind::PrimitiveType(PrimitiveEVMType::Bytes(4)), + span: Span { start: 35, end: 41, file: None }, + }, + Token { kind: TokenKind::Whitespace, span: Span { start: 41, end: 42, file: None } }, + Token { + kind: TokenKind::Ident("interfaceId".to_string()), + span: Span { start: 42, end: 53, file: None }, + }, + Token { kind: TokenKind::CloseParen, span: Span { start: 53, end: 54, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 54, end: 55, file: None } }, + Token { kind: TokenKind::View, span: Span { start: 55, end: 59, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 59, end: 60, file: None } }, + Token { kind: TokenKind::Returns, span: Span { start: 60, end: 67, file: None } }, + Token { kind: TokenKind::Whitespace, span: Span { start: 67, end: 68, file: None } }, + Token { kind: TokenKind::OpenParen, span: Span { start: 68, end: 69, file: None } }, + Token { + kind: TokenKind::PrimitiveType(PrimitiveEVMType::Bool), + span: Span { start: 69, end: 73, file: None }, + }, + Token { kind: TokenKind::CloseParen, span: Span { start: 73, end: 74, file: None } }, + Token { kind: TokenKind::Eof, span: Span { start: 74, end: 74, file: None } }, + ]; + assert_eq!(expected_tokens, tokens); + let mut parser = Parser::new(tokens, None); + parser.parse().unwrap(); +} diff --git a/huff_parser/tests/labels.rs b/huff_parser/tests/labels.rs index 5ec7a30f..e3940cac 100644 --- a/huff_parser/tests/labels.rs +++ b/huff_parser/tests/labels.rs @@ -225,6 +225,7 @@ pub fn builtins_under_labels() { arg_type: None, name: Some(String::from("TEST_TABLE")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 342, end: 352, file: None }]), }], span: AstSpan(vec![ @@ -244,6 +245,7 @@ pub fn builtins_under_labels() { arg_type: None, name: Some(String::from("TEST_TABLE")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 378, end: 388, file: None }]), }], span: AstSpan(vec![ @@ -263,6 +265,7 @@ pub fn builtins_under_labels() { arg_type: None, name: Some(String::from("SMALL_MACRO")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 413, end: 424, file: None }]), }], span: AstSpan(vec![ @@ -282,6 +285,7 @@ pub fn builtins_under_labels() { arg_type: None, name: Some(String::from("myFunc")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 449, end: 455, file: None }]), }], span: AstSpan(vec![ @@ -301,6 +305,7 @@ pub fn builtins_under_labels() { arg_type: None, name: Some(String::from("TestError")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 477, end: 486, file: None }]), }], span: AstSpan(vec![ @@ -320,6 +325,7 @@ pub fn builtins_under_labels() { arg_type: None, name: Some(String::from("bb")), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 513, end: 515, file: None }]), }], span: AstSpan(vec![ diff --git a/huff_parser/tests/macro.rs b/huff_parser/tests/macro.rs index f732ada4..421e14d9 100644 --- a/huff_parser/tests/macro.rs +++ b/huff_parser/tests/macro.rs @@ -261,6 +261,7 @@ fn macro_with_arg_calls() { arg_type: None, name: Some("error".to_string()), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 67, end: 72, file: None }]), }], decorator: None, @@ -607,6 +608,7 @@ fn macro_invocation_with_arg_call() { arg_type: None, name: Some("error".to_string()), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 28, end: 33, file: None }]), }], statements: vec![ @@ -822,6 +824,7 @@ fn macro_with_builtin_fn_call() { arg_type: None, name: Some("TEST".to_string()), indexed: false, + arg_location: None, span: AstSpan(vec![Span { start: 77, end: 81, file: None }]), }], span: AstSpan(vec![ diff --git a/huff_utils/src/ast.rs b/huff_utils/src/ast.rs index 099dd7f6..212e1943 100644 --- a/huff_utils/src/ast.rs +++ b/huff_utils/src/ast.rs @@ -322,11 +322,25 @@ impl Contract { } } +/// An argument's location +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum ArgumentLocation { + /// Memory location + #[default] + Memory, + /// Storage location + Storage, + /// Calldata location + Calldata, +} + /// A function, event, or macro argument #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Argument { /// Type of the argument pub arg_type: Option, + /// Optional Argument Location + pub arg_location: Option, /// The name of the argument pub name: Option, /// Is the argument indexed? TODO: should be valid for event arguments ONLY diff --git a/huff_utils/src/error.rs b/huff_utils/src/error.rs index ea9294d3..123b1164 100644 --- a/huff_utils/src/error.rs +++ b/huff_utils/src/error.rs @@ -23,6 +23,8 @@ pub struct ParserError { pub enum ParserErrorKind { /// Unexpected type UnexpectedType(TokenKind), + /// Argument name is a reserved evm primitive type keyword + InvalidTypeAsArgumentName(TokenKind), /// Invalid definition InvalidDefinition(TokenKind), /// Invalid constant value @@ -315,6 +317,14 @@ impl<'a> fmt::Display for CompilerError<'a> { pe.spans.error(pe.hint.as_ref()) ) } + ParserErrorKind::InvalidTypeAsArgumentName(ut) => { + write!( + f, + "\nError: Unexpected Argument Name is an EVM Type: \"{}\" \n{}\n", + ut, + pe.spans.error(pe.hint.as_ref()) + ) + } ParserErrorKind::InvalidDefinition(k) => { write!( f, diff --git a/huff_utils/src/token.rs b/huff_utils/src/token.rs index cebd9507..bf213c37 100644 --- a/huff_utils/src/token.rs +++ b/huff_utils/src/token.rs @@ -123,6 +123,12 @@ pub enum TokenKind { CodeTable, /// A builtin function (__codesize, __tablesize, __tablestart) BuiltinFunction(String), + /// Calldata Data Location + Calldata, + /// Memory Data Location + Memory, + /// Storage Data Location + Storage, } impl fmt::Display for TokenKind { @@ -189,6 +195,9 @@ impl fmt::Display for TokenKind { TokenKind::JumpTablePacked => "jumptable__packed", TokenKind::CodeTable => "table", TokenKind::BuiltinFunction(s) => return write!(f, "BuiltinFunction({})", s), + TokenKind::Calldata => return write!(f, "calldata"), + TokenKind::Memory => return write!(f, "memory"), + TokenKind::Storage => return write!(f, "storage"), }; write!(f, "{}", x) diff --git a/huff_utils/src/types.rs b/huff_utils/src/types.rs index a5aa1d6f..94863663 100644 --- a/huff_utils/src/types.rs +++ b/huff_utils/src/types.rs @@ -33,7 +33,10 @@ impl TryFrom for PrimitiveEVMType { // Default to 256 if no size let size = match input.get(4..input.len()) { Some(s) => match s.is_empty() { - false => s.parse::().unwrap(), + false => match s.parse::() { + Ok(s) => s, + Err(_) => return Err(format!("Invalid uint size : {}", s)), + }, true => 256, }, None => 256, @@ -44,7 +47,10 @@ impl TryFrom for PrimitiveEVMType { // Default to 256 if no size let size = match input.get(3..input.len()) { Some(s) => match s.is_empty() { - false => s.parse::().unwrap(), + false => match s.parse::() { + Ok(s) => s, + Err(_) => return Err(format!("Invalid int size : {}", s)), + }, true => 256, }, None => 256, @@ -52,16 +58,20 @@ impl TryFrom for PrimitiveEVMType { return Ok(PrimitiveEVMType::Int(size)) } if input.starts_with("bytes") && input.len() != 5 { - let size = input.get(5..input.len()).unwrap().parse::().unwrap(); + let remaining = input.get(5..input.len()).unwrap(); + let size = match remaining.parse::() { + Ok(s) => s, + Err(_) => return Err(format!("Invalid bytes size : {}", remaining)), + }; return Ok(PrimitiveEVMType::Bytes(size)) } - if input.starts_with("bool") { + if input.eq("bool") { return Ok(PrimitiveEVMType::Bool) } - if input.starts_with("address") { + if input.eq("address") { return Ok(PrimitiveEVMType::Address) } - if input.starts_with("string") { + if input.eq("string") { return Ok(PrimitiveEVMType::String) } if input == "bytes" {