Expressions

push opcodes

The push family of instructions require an immediate argument. Unlike most arguments (which come from the stack), immediate arguments are encoded in the bytes immediately following the opcode.

For example, push2 258 would assemble to 0x610102. 0x61 is the instruction, and 0x0102 is the immediate argument. push2 instructs the EVM to use the next two bytes 0x01 and 0x02 as input for the op. That value is left-padded with zeros to 32 bytes (so 0x0000...000102) and placed on the stack.

While an assembled push must have a concrete value, it is often useful when developing a program to have the ability to manipulate the operand at compile time. For this reason, push opcodes take a single expression as their operand.


#![allow(unused)]
fn main() {
extern crate etk_asm;
let src = r#"
push1 1+(2*3)/4
"#;
let mut output = Vec::new();
let mut ingest = etk_asm::ingest::Ingest::new(&mut output);
ingest.ingest(file!(), src).unwrap();
assert_eq!(output, &[0x60, 0x02]);
}

Definition

An expression is a standard infix mathematical expression that is evaluated during assembly. Its computed value must fit within the preceding push's size allowance (eg. less than 256 for push8).

Terms

Many different types of values are allowed as a term in an expression:

Integer Literals

Integer literals are described by the following Pest grammar:

number   = _{ binary | octal | hex | decimal | negative }
decimal  = @{ ASCII_DIGIT+ }
negative = @{ "-" ~ ASCII_DIGIT+ }
binary   = @{ "0b" ~ ASCII_BIN_DIGIT+ }
hex      = @{ "0x" ~ ASCII_HEX_DIGIT ~ ASCII_HEX_DIGIT+ }
octal    = @{ "0o" ~ ASCII_OCT_DIGIT+ }

There is no limit for the length of integer literals. While expressions support both signed and unsigned integers, the result of the expression must non-negative and fit within the width of the corresponding push instruction.

Labels

A label may be used as a term in an expression.


#![allow(unused)]
fn main() {
extern crate etk_asm;
let src = r#"
start:
    push1 start + 1
"#;
let mut output = Vec::new();
let mut ingest = etk_asm::ingest::Ingest::new(&mut output);
ingest.ingest(file!(), src).unwrap();
assert_eq!(output, &[0x60, 0x01]);
}

Macros

Expression macros may be used as a term in an expression.


#![allow(unused)]
fn main() {
extern crate etk_asm;
let src = r#"
push4 selector("transfer(uint256,uint256)")
"#;
let mut output = Vec::new();
let mut ingest = etk_asm::ingest::Ingest::new(&mut output);
ingest.ingest(file!(), src).unwrap();
assert_eq!(output, &[0x63, 12, 247, 158, 10]);
}

Operators

Binary

Expressions support the following binary operators:


#![allow(unused)]
fn main() {
extern crate etk_asm;
let src = r#"
push1 1+2       # addition
push1 1*2       # multiplication
push1 2-1       # subtraction
push1 2/2       # division

"#;
let mut output = Vec::new();
let mut ingest = etk_asm::ingest::Ingest::new(&mut output);
ingest.ingest(file!(), src).unwrap();
assert_eq!(output, &[0x60, 0x03, 0x60, 0x02, 0x60, 0x01, 0x60, 0x01]);
}