Repository: huff-language/huff-rs
Pull request #272 (2023-04-12 23:31:07)

feat: `PUSH0` opcode
## Overview

Adds the `PUSH0` opcode introduced in [EIP-3855](https://eips.ethereum.org/EIPS/eip-3855)


Patch:
diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs
index fceed3a..5eba06a 100644
--- a/huff_parser/src/lib.rs
+++ b/huff_parser/src/lib.rs
@@ -560,8 +560,9 @@ impl Parser {
                         ty: StatementType::Opcode(o),
                         span: AstSpan(curr_spans),
                     });
-                    // If the opcode is a push, we need to parse the next literal
-                    if o.is_push() {
+                    // If the opcode is a push that takes a literal value, we need to parse the next
+                    // literal
+                    if o.is_value_push() {
                         match self.current_token.kind.clone() {
                             TokenKind::Literal(val) => {
                                 let curr_spans = vec![self.current_token.span.clone()];
diff --git a/huff_utils/src/ast.rs b/huff_utils/src/ast.rs
index 17a2f74..af114dd 100644
--- a/huff_utils/src/ast.rs
+++ b/huff_utils/src/ast.rs
@@ -601,9 +601,9 @@ impl MacroDefinition {
                         ty: IRByteType::Bytes(Bytes(opcode_str)),
                         span: &statement.span,
                     });
-                    // If the opcode is a push, we need to consume the next statement, which must be
-                    // a literal as checked in the parser
-                    if o.is_push() {
+                    // If the opcode is a push that takes a literal value, we need to consume the
+                    // next statement, which must be a literal as checked in the parser
+                    if o.is_value_push() {
                         match statement_iter.next() {
                             Some(Statement { ty: StatementType::Literal(l), span: _ }) => {
                                 let hex_literal: String = bytes32_to_string(l, false);
diff --git a/huff_utils/src/evm.rs b/huff_utils/src/evm.rs
index d843187..8ad61a4 100644
--- a/huff_utils/src/evm.rs
+++ b/huff_utils/src/evm.rs
@@ -6,7 +6,7 @@ use strum_macros::EnumString;
 /// They are arranged in a particular order such that all the opcodes that have common
 /// prefixes are ordered by decreasing length to avoid mismatch when lexing.
 /// Example : [origin, or] or [push32, ..., push3]
-pub const OPCODES: [&str; 146] = [
+pub const OPCODES: [&str; 147] = [
     "lt",
     "gt",
     "slt",
@@ -121,6 +121,7 @@ pub const OPCODES: [&str; 146] = [
     "push3",
     "push2",
     "push1",
+    "push0",
     "swap16",
     "swap15",
     "swap14",
@@ -198,6 +199,7 @@ pub static OPCODES_MAP: phf::Map<&'static str, Opcode> = phf_map! {
     "jumpi" => Opcode::Jumpi,
     "pc" => Opcode::Pc,
     "msize" => Opcode::Msize,
+    "push0" => Opcode::Push0,
     "push1" => Opcode::Push1,
     "push2" => Opcode::Push2,
     "push3" => Opcode::Push3,
@@ -438,6 +440,8 @@ pub enum Opcode {
     Gas,
     /// Marks a valid destination for jumps
     Jumpdest,
+    /// Places a zero on top of the stack
+    Push0,
     /// Places 1 byte item on top of the stack
     Push1,
     /// Places 2 byte item on top of the stack
@@ -674,6 +678,7 @@ impl Opcode {
             Opcode::Msize => "59",
             Opcode::Gas => "5a",
             Opcode::Jumpdest => "5b",
+            Opcode::Push0 => "5f",
             Opcode::Push1 => "60",
             Opcode::Push2 => "61",
             Opcode::Push3 => "62",
@@ -759,8 +764,8 @@ impl Opcode {
         opcode_str.to_string()
     }
 
-    /// Returns if the current opcode is a push opcode
-    pub fn is_push(&self) -> bool {
+    /// Returns true if the current opcode is a push opcode that takes a literal value
+    pub fn is_value_push(&self) -> bool {
         matches!(
             self,
             Opcode::Push1 |
@@ -800,7 +805,7 @@ impl Opcode {
 
     /// Prefixes the literal if necessary
     pub fn prefix_push_literal(&self, literal: &str) -> String {
-        if self.is_push() {
+        if self.is_value_push() {
             if let Ok(len) = u8::from_str_radix(&self.to_string(), 16) {
                 if len >= 96 {
                     let size = (len - 96 + 1) * 2;
@@ -820,7 +825,7 @@ impl Opcode {
 
     /// Checks if the value overflows the given push opcode
     pub fn push_overflows(&self, literal: &str) -> bool {
-        if self.is_push() {
+        if self.is_value_push() {
             if let Ok(len) = u8::from_str_radix(&self.to_string(), 16) {
                 if len >= 96 {
                     let size = (len - 96 + 1) * 2;


Test patch:
diff --git a/huff_core/tests/push_overrides.rs b/huff_core/tests/push_overrides.rs
index bff3ccd..e69a3a0 100644
--- a/huff_core/tests/push_overrides.rs
+++ b/huff_core/tests/push_overrides.rs
@@ -74,6 +74,40 @@ fn test_constructs_exact_push_override() {
     assert!(!has_custom_bootstrap);
 }
 
+#[test]
+fn test_no_push0_override() {
+    // Create the raw source
+    const OVERRIDEN_PUSH: &str = r#"
+        #define macro CONSTRUCTOR() = {
+            push0
+        }
+        #define macro MAIN() = {}
+    "#;
+
+    // Lex and Parse the source code
+    let flattened_source = FullFileSource { source: OVERRIDEN_PUSH, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source.source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, None);
+
+    // Grab the first macro
+    let mut contract = parser.parse().unwrap();
+    // Derive storage pointers
+    contract.derive_storage_pointers();
+
+    // Instantiate Codegen
+    let cg = Codegen::new();
+
+    // The codegen instance should have no artifact
+    assert!(cg.artifact.is_none());
+
+    // Have the Codegen create the constructor bytecode
+    let (cbytes, has_custom_bootstrap) =
+        Codegen::generate_constructor_bytecode(&contract, None).unwrap();
+    assert_eq!(cbytes, String::from("5f"));
+    assert!(!has_custom_bootstrap);
+}
+
 #[test]
 #[should_panic]
 fn test_fails_on_push_underflow() {
diff --git a/huff_parser/tests/opcodes.rs b/huff_parser/tests/opcodes.rs
index ebe8756..667f212 100644
--- a/huff_parser/tests/opcodes.rs
+++ b/huff_parser/tests/opcodes.rs
@@ -148,3 +148,99 @@ fn test_push_literals() {
     let mut parser = Parser::new(tokens, None);
     parser.parse().unwrap();
 }
+
+#[test]
+fn test_push0() {
+    let source: &str = r#"
+        #define macro MAIN() = {
+            push1 0x10
+            push32 0x108
+            push1 0x10 0x10
+            push0
+        }
+    "#;
+
+    // Parse tokens
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source.source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+
+    let expected_tokens = vec![
+        Token { kind: TokenKind::Whitespace, span: Span { start: 0, end: 8, file: None } },
+        Token { kind: TokenKind::Define, span: Span { start: 9, end: 15, file: None } },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 16, end: 16, file: None } },
+        Token { kind: TokenKind::Macro, span: Span { start: 17, end: 21, file: None } },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 22, end: 22, file: None } },
+        Token {
+            kind: TokenKind::Ident("MAIN".to_string()),
+            span: Span { start: 23, end: 26, file: None },
+        },
+        Token { kind: TokenKind::OpenParen, span: Span { start: 27, end: 27, file: None } },
+        Token { kind: TokenKind::CloseParen, span: Span { start: 28, end: 28, file: None } },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 29, end: 29, file: None } },
+        Token { kind: TokenKind::Assign, span: Span { start: 30, end: 30, file: None } },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 31, end: 31, file: None } },
+        Token { kind: TokenKind::OpenBrace, span: Span { start: 32, end: 32, file: None } },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 33, end: 45, file: None } },
+        Token {
+            kind: TokenKind::Opcode(Opcode::Push1),
+            span: Span { start: 46, end: 50, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 51, end: 51, file: None } },
+        Token {
+            kind: TokenKind::Literal([
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 16,
+            ]),
+            span: Span { start: 54, end: 55, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 56, end: 68, file: None } },
+        Token {
+            kind: TokenKind::Opcode(Opcode::Push32),
+            span: Span { start: 69, end: 74, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 75, end: 75, file: None } },
+        Token {
+            kind: TokenKind::Literal([
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 1, 8,
+            ]),
+            span: Span { start: 78, end: 80, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 81, end: 93, file: None } },
+        Token {
+            kind: TokenKind::Opcode(Opcode::Push1),
+            span: Span { start: 94, end: 98, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 99, end: 99, file: None } },
+        Token {
+            kind: TokenKind::Literal([
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 16,
+            ]),
+            span: Span { start: 102, end: 103, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 104, end: 104, file: None } },
+        Token {
+            kind: TokenKind::Literal([
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 16,
+            ]),
+            span: Span { start: 107, end: 108, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 109, end: 121, file: None } },
+        Token {
+            kind: TokenKind::Opcode(Opcode::Push0),
+            span: Span { start: 122, end: 126, file: None },
+        },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 127, end: 135, file: None } },
+        Token { kind: TokenKind::CloseBrace, span: Span { start: 136, end: 136, file: None } },
+        Token { kind: TokenKind::Whitespace, span: Span { start: 137, end: 141, file: None } },
+        Token { kind: TokenKind::Eof, span: Span { start: 141, end: 141, file: None } },
+    ];
+    assert_eq!(expected_tokens, tokens);
+
+    // This should parse correctly
+    let mut parser = Parser::new(tokens, None);
+    parser.parse().unwrap();
+}