Cubipods uses a sequential execution model where bytecode is read and executed one instruction at a time. The VM maintains state across four main components: the Lexer, Stack, Memory, and Storage.
// From src/lexer.rs:40-58pub fn next_byte(&mut self) -> Result<String, Box<dyn Error>> { let first_nibble = self.ch; self.read_char(); let second_nibble = self.ch; // ... validation self.read_char(); Ok(format!("{first_nibble}{second_nibble}"))}
Result: "60" → Decoded as PUSH1
3
Execute PUSH1 (read data)
PUSH1 needs 1 data byte, so read the next byte:
// From src/vm.rs:65-85InstructionType::PUSH(size) => { let mut counter = 0; let mut data = "".to_string(); while counter < size { data += &self.lexer.next_byte()?; counter += 1; } let index = self.stack.push(data.clone())?; // ...}
Read 0x03
Push "03" to stack
State:
Stack: ["03"]
Lexer position: 4 (read “6003”)
4
Read Second Byte (0x60)
Read next byte pair:Result: "60" → Another PUSH1
5
Execute Second PUSH1
Read data byte 0x02
Push "02" to stack
State:
Stack: ["03", "02"] (top is “02”)
Lexer position: 8 (read “60036002”)
6
Read Third Byte (0x01)
Read next byte pair:Result: "01" → ADD instruction
7
Execute ADD
Pop two values, add them, push result:
// From src/vm.rs:146-152InstructionType::ADD => { let (item_1, item_2) = *build_initials()?.downcast::<(Bytes32, Bytes32)>().unwrap(); let result = item_1 + item_2; self.stack.push(result.parse_and_trim()?)?;}
Pop "02" and "03" from stack
Add: 2 + 3 = 5
Push "05" to stack
State:
Stack: ["05"]
Lexer position: 10 (end of bytecode)
8
Termination
Lexer character is '\0', exit main loop:
// From src/vm.rs:40'main: while self.lexer.ch != '\0' { // ...}
pub enum StackError { StackUnderflow, // Pop from empty stack StackOverflow, // Exceed 1024 elements StackSizeExceeded, // DUP/SWAP index out of bounds WrongIndex, // SWAP with index 0 StackIsEmpty,}
Example: Stack underflow
cubipods -b 01 # ADD with empty stack# Error: ShallowStack