Repository: huff-language/huff-rs (rust)

Issue:
feat request: new func __RIGHTPAD()
The problem:
When we want to store a bytes as a constant we have 2 choices.  

1) Create a constant without any padding (i.e. 0xBEEF) and it gets treated as a value so its left padded.  Then when we use it we can some bitshifting or some MSTORE's with offset to get the important bytes to move the high order bits.  This is a waste of runtime gas.
2) Create a constant as fully right padded (i.e. 0xBEEF000000000000000000000000000000000000000000000000000000000000)  This way saves gas but it's kind of annoying to have this huge long number in the code.

Solution:

A new function __RIGHTPAD(0xBEEF), when compiled will be the same as the right padded value in #2 above.  This way the code is more readable, and we don't have to use any runtime gas to bitshift.

We could also consider something more flexible than just right padding.  Like __PAD(0xBEEF, left 0's, right 0's) but this sounds unnecessarily complex.  Left padding is done by default, so we don't need a func for that.  And mid padding is easily achieved by adding in the zeros yourself on either side.

Pull request description:
feat(huff_codegen): `__RIGHTPAD` builtin
## Overview

Adds a new builtin, `__RIGHTPAD(n)` to assist with pushing right-padded bytes to the stack.

#### Syntax
```
#define macro MAIN() = takes (0) returns (0) {
    // a57b000000000000000000000000000000000000000000000000000000000000
    __RIGHTPAD(0xa57b) 

    // 48656c6c6f2c20576f726c642100000000000000000000000000000000000000
    __RIGHTPAD(0x48656c6c6f2c20576f726c6421)

    // 06d6f6f736500000000000000000000000000000000000000000000000000000
    __RIGHTPAD(0x06d6f6f7365)
}
```

closes #184 

Patch:
diff --git a/huff_codegen/src/irgen/statements.rs b/huff_codegen/src/irgen/statements.rs
index 037105e..722c1a9 100644
--- a/huff_codegen/src/irgen/statements.rs
+++ b/huff_codegen/src/irgen/statements.rs
@@ -280,16 +280,15 @@ pub fn statement_gen(
                         .iter()
                         .find(|f| bf.args[0].name.as_ref().unwrap().eq(&f.name))
                     {
-                        let sig = hex::encode(func.signature);
-                        let push_bytes = format!("{:02x}{}", 95 + sig.len() / 2, sig);
+                        let push_bytes =
+                            format!("{}{}", Opcode::Push4, hex::encode(func.signature));
                         *offset += push_bytes.len() / 2;
                         bytes.push((starting_offset, Bytes(push_bytes)));
                     } else if let Some(s) = &bf.args[0].name {
                         let mut signature = [0u8; 4]; // Only keep first 4 bytes
                         hash_bytes(&mut signature, s);
 
-                        let sig = hex::encode(signature);
-                        let push_bytes = format!("{:02x}{}", 95 + sig.len() / 2, sig);
+                        let push_bytes = format!("{}{}", Opcode::Push4, hex::encode(signature));
                         *offset += push_bytes.len() / 2;
                         bytes.push((starting_offset, Bytes(push_bytes)));
                     } else {
@@ -332,15 +331,14 @@ pub fn statement_gen(
                         .find(|e| bf.args[0].name.as_ref().unwrap().eq(&e.name))
                     {
                         let hash = bytes32_to_string(&event.hash, false);
-                        let push_bytes = format!("{:02x}{}", 95 + hash.len() / 2, hash);
+                        let push_bytes = format!("{}{}", Opcode::Push32, hash);
                         *offset += push_bytes.len() / 2;
                         bytes.push((starting_offset, Bytes(push_bytes)));
                     } else if let Some(s) = &bf.args[0].name {
                         let mut hash = [0u8; 32];
                         hash_bytes(&mut hash, s);
 
-                        let hash = hex::encode(hash);
-                        let push_bytes = format!("{:02x}{}", 95 + hash.len() / 2, hash);
+                        let push_bytes = format!("{}{}", Opcode::Push32, hex::encode(hash));
                         *offset += push_bytes.len() / 2;
                         bytes.push((starting_offset, Bytes(push_bytes)));
                     } else {
@@ -383,7 +381,7 @@ pub fn statement_gen(
                         // Add 28 bytes to left-pad the 4 byte selector
                         let selector =
                             format!("{}{}", hex::encode(error.selector), "00".repeat(28));
-                        let push_bytes = format!("{:02x}{}", 95 + selector.len() / 2, selector);
+                        let push_bytes = format!("{}{}", Opcode::Push32, selector);
                         *offset += push_bytes.len() / 2;
                         bytes.push((starting_offset, Bytes(push_bytes)));
                     } else {
@@ -401,6 +399,29 @@ pub fn statement_gen(
                         })
                     }
                 }
+                BuiltinFunctionKind::RightPad => {
+                    if bf.args.len() != 1 {
+                        tracing::error!(
+                            target = "codegen",
+                            "Incorrect number of arguments passed to __RIGHTPAD, should be 1: {}",
+                            bf.args.len()
+                        );
+                        return Err(CodegenError {
+                            kind: CodegenErrorKind::InvalidArguments(format!(
+                                "Incorrect number of arguments passed to __RIGHTPAD, should be 1: {}",
+                                bf.args.len()
+                            )),
+                            span: bf.span.clone(),
+                            token: None,
+                        })
+                    }
+
+                    let hex = format_even_bytes(bf.args[0].name.as_ref().unwrap().clone());
+                    let push_bytes =
+                        format!("{}{}{}", Opcode::Push32, hex, "0".repeat(64 - hex.len()));
+                    *offset += push_bytes.len() / 2;
+                    bytes.push((starting_offset, Bytes(push_bytes)));
+                }
             }
         }
         sty => {
diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs
index 70ba732..d9edec9 100644
--- a/huff_parser/src/lib.rs
+++ b/huff_parser/src/lib.rs
@@ -7,7 +7,7 @@
 use huff_utils::{
     ast::*,
     error::*,
-    prelude::{hash_bytes, str_to_bytes32, FileSource, Span},
+    prelude::{bytes32_to_string, hash_bytes, str_to_bytes32, FileSource, Span},
     token::{Token, TokenKind},
     types::*,
 };
@@ -705,26 +705,37 @@ impl Parser {
         self.match_kind(TokenKind::OpenParen)?;
         tracing::debug!(target: "parser", "PARSING ARGs: {:?}", self.current_token.kind);
         while !self.check(TokenKind::CloseParen) {
-            // The builtin functions `__FUNC_SIG` and `__EVENT_HASH` can accept a single string as
-            // input. If the `is_builtin` flag was passed, check to see if a single
-            // string is present.
-            if let TokenKind::Str(s) = &self.current_token.kind {
-                if !is_builtin {
-                    return Err(ParserError {
-                        kind: ParserErrorKind::InvalidArgs(self.current_token.kind.clone()),
-                        hint: None,
-                        spans: AstSpan(vec![self.current_token.span.clone()]),
-                    })
+            if is_builtin {
+                // The builtin functions `__FUNC_SIG` and `__EVENT_HASH` can accept a single string
+                // as input. If the `is_builtin` flag was passed, check to see if a
+                // single string is present.
+                if let TokenKind::Str(s) = &self.current_token.kind {
+                    args.push(Argument {
+                        name: Some(s.to_owned()), // Place the string in the "name" field
+                        arg_type: None,
+                        indexed: false,
+                        span: AstSpan(vec![self.current_token.span.clone()]),
+                    });
+
+                    self.consume();
+                    continue
                 }
 
-                args.push(Argument {
-                    name: Some(s.to_owned()), // Place the string in the "name" field
-                    arg_type: None,
-                    indexed: false,
-                    span: AstSpan(vec![self.current_token.span.clone()]),
-                });
-                self.consume();
-                continue
+                // The builtin function `__RIGHTPAD` can accept a single literal as input. If the
+                // `is_builtin` flag was passed, check to see if a single literal is
+                // 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 */
+                        arg_type: None,
+                        indexed: false,
+                        span: AstSpan(vec![self.current_token.span.clone()]),
+                    });
+
+                    self.consume();
+                    continue
+                }
             }
 
             let mut arg = Argument::default();
diff --git a/huff_utils/src/ast.rs b/huff_utils/src/ast.rs
index 34e9d6d..6a9f8ec 100644
--- a/huff_utils/src/ast.rs
+++ b/huff_utils/src/ast.rs
@@ -676,6 +676,8 @@ pub enum BuiltinFunctionKind {
     EventHash,
     /// Error selector function
     Error,
+    /// Rightpad function
+    RightPad,
 }
 
 impl From<String> for BuiltinFunctionKind {
@@ -687,6 +689,7 @@ impl From<String> for BuiltinFunctionKind {
             "__FUNC_SIG" => BuiltinFunctionKind::FunctionSignature,
             "__EVENT_HASH" => BuiltinFunctionKind::EventHash,
             "__ERROR" => BuiltinFunctionKind::Error,
+            "__RIGHTPAD" => BuiltinFunctionKind::RightPad,
             _ => panic!("Invalid Builtin Function Kind"), /* This should never be reached,
                                                            * builtins are validated with a
                                                            * `try_from` call in the lexer. */
@@ -705,6 +708,7 @@ impl TryFrom<&String> for BuiltinFunctionKind {
             "__FUNC_SIG" => Ok(BuiltinFunctionKind::FunctionSignature),
             "__EVENT_HASH" => Ok(BuiltinFunctionKind::EventHash),
             "__ERROR" => Ok(BuiltinFunctionKind::Error),
+            "__RIGHTPAD" => Ok(BuiltinFunctionKind::RightPad),
             _ => Err(()),
         }
     }


Test patch:
diff --git a/huff_core/tests/builtins.rs b/huff_core/tests/builtins.rs
index 55592c8..d233936 100644
--- a/huff_core/tests/builtins.rs
+++ b/huff_core/tests/builtins.rs
@@ -548,3 +548,51 @@ fn test_error_selector_builtin() {
         )
     );
 }
+
+#[test]
+fn test_rightpad_builtin() {
+    let source: &str = r#"
+        #define macro MAIN() = takes (0) returns (0) {
+            __RIGHTPAD(0xa57b)
+            __RIGHTPAD(0x48656c6c6f2c20576f726c6421)
+            __RIGHTPAD(0x6d6f6f7365)
+        }
+    "#;
+
+    // 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 the AST
+    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 Codegen create the runtime bytecode
+    let r_bytes = Codegen::generate_main_bytecode(&contract).unwrap();
+    assert_eq!(&r_bytes[2..66], "a57b000000000000000000000000000000000000000000000000000000000000");
+    assert_eq!(
+        &r_bytes[68..132],
+        "48656c6c6f2c20576f726c642100000000000000000000000000000000000000"
+    );
+    assert_eq!(
+        &r_bytes[134..198],
+        "6d6f6f7365000000000000000000000000000000000000000000000000000000"
+    );
+    assert_eq!(r_bytes.len(), (32 * 3 + 3) * 2);
+    assert_eq!(
+        r_bytes,
+        String::from(
+            "7fa57b0000000000000000000000000000000000000000000000000000000000007f48656c6c6f2c20576f726c6421000000000000000000000000000000000000007f6d6f6f7365000000000000000000000000000000000000000000000000000000"
+        )
+    );
+}
diff --git a/huff_lexer/tests/builtins.rs b/huff_lexer/tests/builtins.rs
index d69295c..929085a 100644
--- a/huff_lexer/tests/builtins.rs
+++ b/huff_lexer/tests/builtins.rs
@@ -4,8 +4,15 @@ use std::ops::Deref;
 
 #[test]
 fn parses_builtin_function_in_macro_body() {
-    let builtin_funcs =
-        ["__codesize", "__tablesize", "__tablestart", "__FUNC_SIG", "__EVENT_HASH", "__ERROR"];
+    let builtin_funcs = [
+        "__codesize",
+        "__tablesize",
+        "__tablestart",
+        "__FUNC_SIG",
+        "__EVENT_HASH",
+        "__ERROR",
+        "__RIGHTPAD",
+    ];
 
     for builtin in builtin_funcs {
         let source = &format!(
@@ -70,8 +77,15 @@ fn parses_builtin_function_in_macro_body() {
 #[test]
 #[should_panic]
 fn fails_to_parse_builtin_outside_macro_body() {
-    let builtin_funcs =
-        ["__codesize", "__tablesize", "__tablestart", "__FUNC_SIG", "__EVENT_HASH", "__ERROR"];
+    let builtin_funcs = [
+        "__codesize",
+        "__tablesize",
+        "__tablestart",
+        "__FUNC_SIG",
+        "__EVENT_HASH",
+        "__ERROR",
+        "__RIGHTPAD",
+    ];
 
     for builtin in builtin_funcs {
         let source = &format!("{}(MAIN)", builtin);
diff --git a/huff_parser/tests/labels.rs b/huff_parser/tests/labels.rs
index d87ca9d..f7222f1 100644
--- a/huff_parser/tests/labels.rs
+++ b/huff_parser/tests/labels.rs
@@ -198,6 +198,7 @@ pub fn builtins_under_labels() {
             __codesize(SMALL_MACRO)
             __FUNC_SIG(myFunc)
             __ERROR(TestError)
+            __RIGHTPAD(0xBB)
     }
     "#;
     let flattened_source = FullFileSource { source, file: None, spans: vec![] };
@@ -309,6 +310,25 @@ pub fn builtins_under_labels() {
                             Span { start: 477, end: 486, file: None },
                         ]),
                     },
+                    Statement {
+                        ty: StatementType::BuiltinFunctionCall(BuiltinFunctionCall {
+                            kind: BuiltinFunctionKind::RightPad,
+                            args: vec![Argument {
+                                arg_type: None,
+                                name: Some(String::from("bb")),
+                                indexed: false,
+                                span: AstSpan(vec![Span { start: 513, end: 515, file: None }]),
+                            }],
+                            span: AstSpan(vec![
+                                Span { start: 500, end: 510, file: None },
+                                Span { start: 513, end: 515, file: None },
+                            ]),
+                        }),
+                        span: AstSpan(vec![
+                            Span { start: 500, end: 510, file: None },
+                            Span { start: 513, end: 515, file: None },
+                        ]),
+                    },
                 ],
                 span: AstSpan(vec![
                     Span { start: 307, end: 315, file: None },
@@ -322,6 +342,8 @@ pub fn builtins_under_labels() {
                     Span { start: 449, end: 455, file: None },
                     Span { start: 469, end: 476, file: None },
                     Span { start: 477, end: 486, file: None },
+                    Span { start: 500, end: 510, file: None },
+                    Span { start: 513, end: 515, file: None },
                 ]),
             }),
             span: AstSpan(vec![
@@ -336,6 +358,8 @@ pub fn builtins_under_labels() {
                 Span { start: 449, end: 455, file: None },
                 Span { start: 469, end: 476, file: None },
                 Span { start: 477, end: 486, file: None },
+                Span { start: 500, end: 510, file: None },
+                Span { start: 513, end: 515, file: None },
             ]),
         }],
         takes: 3,
@@ -378,7 +402,11 @@ pub fn builtins_under_labels() {
             Span { start: 476, end: 477, file: None },
             Span { start: 477, end: 486, file: None },
             Span { start: 486, end: 487, file: None },
-            Span { start: 492, end: 493, file: None },
+            Span { start: 500, end: 510, file: None },
+            Span { start: 510, end: 511, file: None },
+            Span { start: 513, end: 515, file: None },
+            Span { start: 515, end: 516, file: None },
+            Span { start: 521, end: 522, file: None },
         ]),
         outlined: false,
     };