Skip to content
This repository has been archived by the owner on Oct 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #235 from huff-language/md/free-slot-bug
Browse files Browse the repository at this point in the history
fix: free storage pointer used in param bug
  • Loading branch information
clabby authored Jan 15, 2023
2 parents 7d356f6 + 1111a7d commit c4bdf53
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 73 deletions.
4 changes: 2 additions & 2 deletions huff_codegen/src/irgen/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ pub fn statement_gen(
)),
span: bf.span.clone(),
token: None,
})
});
}

let arg_index = bf.args[0].name.as_ref().unwrap();
Expand All @@ -476,7 +476,7 @@ pub fn statement_gen(
),
span: bf.span.clone(),
token: None,
})
});
}

// Insert a 17 byte placeholder- will be filled when constructor args are added
Expand Down
37 changes: 37 additions & 0 deletions huff_core/tests/free_storage_pointer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use huff_codegen::Codegen;
use huff_lexer::Lexer;
use huff_parser::Parser;
use huff_utils::{prelude::FullFileSource, token::Token};

/// Check that free storage pointers referenced outside of macro bodies
/// are assigned correctly at compilation
#[test]
fn test_set_free_storage_pointers() {
let source: &str = r#"
#define constant FREE = FREE_STORAGE_POINTER()
#define macro TEST_MACRO(slot) = {
<slot> sload 0x00 mstore 0x20 0x00 return
}
#define macro MAIN() = {
TEST_MACRO(FREE)
}
"#;

// Parse tokens
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::<Vec<Token>>();
let mut parser = Parser::new(tokens, None);

// Parse AST
let mut contract = parser.parse().unwrap();

// Derive storage pointers
contract.derive_storage_pointers();

// Assert the Free storage pointer has been set to 0
let mbytes = Codegen::generate_main_bytecode(&contract, None).unwrap();
assert!(mbytes.starts_with("6000"));
}
5 changes: 2 additions & 3 deletions huff_core/tests/macro_invoc_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ fn test_all_opcodes_in_macro_args() {
}}
#define macro MAIN() = takes(0) returns(0) {{
RETURN1({})
RETURN1({o})
}}
"#,
o
"#
);

// Lex + Parse
Expand Down
13 changes: 5 additions & 8 deletions huff_core/tests/parser_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,8 @@ fn test_invalid_token_in_macro_body() {
for (value, kind) in invalids {
let source = &format!(
r#"#define macro CONSTANT() = takes (0) returns (0) {{
{}
}}"#,
value
{value}
}}"#
);

let full_source = FullFileSource { source, file: None, spans: vec![] };
Expand Down Expand Up @@ -215,9 +214,8 @@ fn test_invalid_token_in_label_definition() {
let source = &format!(
r#"#define macro CONSTANT() = takes (0) returns (0) {{
lab:
{}
}}"#,
value
{value}
}}"#
);

let full_source = FullFileSource { source, file: None, spans: vec![] };
Expand Down Expand Up @@ -277,8 +275,7 @@ fn test_invalid_single_arg() {
e,
ParserError {
kind: ParserErrorKind::InvalidSingleArg(TokenKind::Ident(format!(
"{}",
random_char
"{random_char}"
))),
hint: Some("Expected number representing stack item count.".to_string()),
spans: AstSpan(vec![Span { start: 34, end: 35, file: None }]),
Expand Down
25 changes: 11 additions & 14 deletions huff_lexer/tests/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ fn commented_lex_imports() {
let import_str = "../huff-examples/erc20/contracts/utils/Ownable.huff";
let source = format!(
r#"
// #include "{}"
/* #include "{}" */
// #include "{import_str}"
/* #include "{import_str}" */
/* test test test */
#define macro ()
#include "{}"
"#,
import_str, import_str, import_str
#include "{import_str}"
"#
);

let lexed_imports = Lexer::lex_imports(&source);
Expand All @@ -35,13 +34,12 @@ fn multiple_lex_imports() {
let import_str = "../huff-examples/erc20/contracts/utils/Ownable.huff";
let source = format!(
r#"
#include "{}"
#include "{}"
#include "{import_str}"
#include "{import_str}"
/* test test test */
#define macro ()
#include "{}"
"#,
import_str, import_str, import_str
#include "{import_str}"
"#
);

let lexed_imports = Lexer::lex_imports(&source);
Expand All @@ -56,10 +54,9 @@ fn multiple_lex_imports_single_quotes() {
let import_str = "../huff-examples/erc20/contracts/utils/Ownable.huff";
let source = format!(
r#"
#include '{}'
#include '{}'
"#,
import_str, import_str
#include '{import_str}'
#include '{import_str}'
"#
);

let lexed_imports = Lexer::lex_imports(&source);
Expand Down
10 changes: 4 additions & 6 deletions huff_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl Parser {
kind: ParserErrorKind::InvalidDefinition(self.current_token.kind.clone()),
hint: Some("Definition must be one of: `function`, `event`, `constant`, `error`, `macro`, `fn`, or `test`.".to_string()),
spans: AstSpan(vec![self.current_token.span.clone()]),
})
});
}
};
} else {
Expand Down Expand Up @@ -574,11 +574,10 @@ impl Parser {
return Err(ParserError {
kind: ParserErrorKind::InvalidPush(o),
hint: Some(format!(
"Literal {:?} contains too many bytes for opcode \"{:?}\"",
hex_literal, o
"Literal {hex_literal:?} contains too many bytes for opcode \"{o:?}\""
)),
spans: AstSpan(curr_spans),
})
});
}

// Otherwise we can push the literal
Expand Down Expand Up @@ -924,8 +923,7 @@ impl Parser {
self.current_token.kind.clone(),
),
hint: Some(format!(
"Argument names cannot be EVM types: {}",
arg_str
"Argument names cannot be EVM types: {arg_str}"
)),
spans: AstSpan(vec![self.current_token.span.clone()]),
})
Expand Down
7 changes: 3 additions & 4 deletions huff_parser/tests/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ fn not_mistaken_as_opcode() {
r#"
#define macro IS_AUTHORIZED(some_arg) = takes(0) returns(0) {{}}
#define macro MAIN() = takes(0) returns(0) {{
IS_AUTHORIZED({})
{}:
IS_AUTHORIZED({label})
{label}:
return
}}
"#,
label, label
"#
);
let flattened_source = FullFileSource { source, file: None, spans: vec![] };
let lexer = Lexer::new(flattened_source);
Expand Down
112 changes: 76 additions & 36 deletions huff_utils/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
bytes_util::*,
error::CodegenError,
evm::Opcode,
prelude::{Span, TokenKind},
prelude::{MacroArg::Ident, Span, TokenKind},
};
use std::{
collections::BTreeMap,
Expand Down Expand Up @@ -201,51 +201,47 @@ impl Contract {
checking_constructor: bool,
) {
let mut statements = macro_def.statements.clone();

let mut i = 0;
loop {
if i >= statements.len() {
break
}
match &statements[i].clone().ty {
StatementType::Constant(const_name) => {
tracing::debug!(target: "ast", "Found constant \"{}\" in macro def \"{}\" statements!", const_name, macro_def.name);
if storage_pointers
.iter()
.filter(|pointer| pointer.0.eq(const_name))
.collect::<Vec<&(String, [u8; 32])>>()
.get(0)
.is_none()
{
tracing::debug!(target: "ast", "No storage pointer already set for \"{}\"!", const_name);
// Get the associated constant
match self
.constants
.lock()
.unwrap()
.iter()
.filter(|c| c.name.eq(const_name))
.collect::<Vec<&ConstantDefinition>>()
.get(0)
{
Some(c) => {
let new_value = match c.value {
ConstVal::Literal(l) => l,
ConstVal::FreeStoragePointer(_) => {
let old_p = *last_p;
*last_p += 1;
str_to_bytes32(&format!("{old_p}"))
}
};
storage_pointers.push((const_name.to_string(), new_value));
}
None => {
tracing::warn!(target: "ast", "CONSTANT \"{}\" NOT FOUND IN AST CONSTANTS", const_name)
}
}
}
self.assign_free_storage_pointers(
const_name,
&macro_def.name,
storage_pointers,
last_p,
);
}
StatementType::MacroInvocation(mi) => {
tracing::debug!(target: "ast", "Found macro invocation: \"{}\" in macro def: \"{}\"!", mi.macro_name, macro_def.name);

// Check for constant references in macro arguments
let mut constant_args: Vec<String> = Vec::new();
for arg in &mi.args {
// check if it is a constant
if let Ident(name) = arg {
self.constants.lock().unwrap().iter().for_each(|constant| {
if name == &constant.name {
tracing::debug!(target: "ast", "CONSTANT FOUND AS MACRO PARAMETER {}", name);
constant_args.push(name.to_string());
}
})
}
}
// Assign constants that reference the Free Storage Pointer
for constant_arg in constant_args {
self.assign_free_storage_pointers(
&constant_arg,
&macro_def.name,
storage_pointers,
last_p,
);
}

match self
.macros
.iter()
Expand Down Expand Up @@ -321,6 +317,50 @@ impl Contract {
// }
}

fn assign_free_storage_pointers(
&self,
const_name: &String,
macro_name: &String,
storage_pointers: &mut Vec<(String, [u8; 32])>,
last_p: &mut i32,
) {
tracing::debug!(target: "ast", "Found constant \"{}\" in macro def \"{}\" statements!", const_name, macro_name);
if storage_pointers
.iter()
.filter(|pointer| pointer.0.eq(const_name))
.collect::<Vec<&(String, [u8; 32])>>()
.get(0)
.is_none()
{
tracing::debug!(target: "ast", "No storage pointer already set for \"{}\"!", const_name);
// Get the associated constant
match self
.constants
.lock()
.unwrap()
.iter()
.filter(|c| c.name.eq(const_name))
.collect::<Vec<&ConstantDefinition>>()
.get(0)
{
Some(c) => {
let new_value = match c.value {
ConstVal::Literal(l) => l,
ConstVal::FreeStoragePointer(_) => {
let old_p = *last_p;
*last_p += 1;
str_to_bytes32(&format!("{old_p}"))
}
};
storage_pointers.push((const_name.to_string(), new_value));
}
None => {
tracing::warn!(target: "ast", "CONSTANT \"{}\" NOT FOUND IN AST CONSTANTS", const_name)
}
}
}
}

/// Add override constants to the AST
///
/// ## Overview
Expand Down

0 comments on commit c4bdf53

Please sign in to comment.