22 changed files with 339 additions and 6442 deletions
-1
View File
@@ -1 +0,0 @@
use flake
+3 -5
View File
@@ -1,7 +1,5 @@
onchain/cache/ onchain/cache/
onchain/out/ onchain/out/
out/
offchain/target/ cache/
lib/forge-std
.direnv
.env
+47
View File
@@ -0,0 +1,47 @@
me: Bounty
about: Bounty
title: ''
labels: bounty
assignees: ''
---
# Bounty
## Scope
<!-- A list of specific things which should be done to deliver the bounty. These could be seen as requirements to verify/review bounty against -->
-
-
-
## Deliverables
<!-- Artifacts produced as the result of this bounty. Something that could be verified/reviewed. Some examples: updated code, deployment made, blog post published, public event conducted etc -->
-
-
## Bounty Owner/Gardener
<!-- The Role who is responsible for the bounty. -->
<@your github username> as **<Role>**
## Gain for the Role
<!-- How the completion of this bounty helps to pursue the Role's purpose -->
## Roles
bounty gardener: <@your git username> / X DAI <!-- likely shouldn't be a % but rather flat -->
bounty worker: name / share
bounty reviewer: name / share
## Gardener checklist
- [ ] I have attached one of the `size-` labels to this bounty
<!--
# Bounty sizes
XS / 200 DAI / effort ~ 3h
S / 350 DAI / effort ~5h
M / 550 DAI / effort ~8h
L / 900 DAI / effort ~13h
XL / 1400 DAI / effort ~21h
-->
-124
View File
@@ -1,124 +0,0 @@
# Interpreter.md
## Overview
This repository provides a **Push3-like interpreter** implemented in Solidity. It uses a _token-based bytecode_ approach to parse programs from a `bytes` array (the `code`) into typed **descriptors**, which are then executed by the interpreter.
**Push3** is a stack-based language originally designed for genetic programming and autoconstructive evolution. Each data type (e.g. `INT_LITERAL`, `FLOAT`, `BOOLEAN`, etc.) has its own stack. The **`EXEC`** stack manages the flow of execution.
**In this implementation**:
- We store code in a single `bytes` array (`code`).
- We parse sublists and integer literals from that array.
- We push instructions and literals onto an execution stack.
- We pop them in LIFO order and execute them.
### Key Features
1. **Token-Based Parsing**: We define small tokens (`0x00` => NOOP, `0x01` => INTEGER_PLUS, `0x02` => INT_LITERAL, `0x03` => SUBLIST). If we see `INT_LITERAL`, we read 4 bytes for a 32-bit integer; if we see `SUBLIST`, we read 2 bytes for the sublist length, then parse it recursively.
2. **Descriptor System**: We represent instructions, literals, and sublists using 256-bit “descriptors.” Each descriptor encodes:
- A `tag` (top 8 bits): e.g. `INT_LITERAL`, `INSTRUCTION`, `SUBLIST`.
- `offset` and `length` for sublists.
- A leftover field (e.g. for storing integer data).
3. **Reversing Items**: When we pop a sublist descriptor, we parse it into descriptors and then push them onto the `EXEC` stack in reverse order. This ensures the list’s leftmost token executes first, consistent with the Push3 spec.
4. **Separation of Parsing and Execution**:
- **Parsing**: Transforms raw byte tokens into typed descriptors (instructions, literals, or nested sublists).
- **Execution**: The `runInterpreter` function repeatedly pops from `EXEC`, decides if it’s an instruction or literal or sublist, and acts accordingly.
## Code Structure
### 1. **Enums**
- **`CodeTag`**:
- `NO_TAG` (unused),
- `INSTRUCTION`,
- `INT_LITERAL`,
- `SUBLIST`.
- **`OpCode`**:
- `NOOP`,
- `INTEGER_PLUS`.
### 2. **Descriptor Layout**
A **256-bit** word has:
```
[ top 8 bits | offset(32 bits) | length(32 bits) | leftover(184 bits) ]
```
- `tag`: The top 8 bits (e.g. `INT_LITERAL`).
- `offset, length`: If `tag = SUBLIST`, these define which bytes in `code` represent the sub-sublists.
- `leftover`: If `tag = INT_LITERAL`, we store the 32-bit integer in the bottom 32 bits of leftover. If `tag = INSTRUCTION`, we store the opcode (`NOOP`, `INTEGER_PLUS`) in the lowest 8 bits of leftover.
### 3. **Helper Reads**
- **`readUint32(code, start)`**: Copies 4 bytes from calldata into a small buffer, returns the final `uint32`. By shifting bits if needed, we ensure we get the integer from the correct part of the loaded word.
- **`readUint16(code, start)`**: Similar logic for 2 bytes.
### 4. **`parseSublist` Function**
When we see a sublist descriptor (`tag=SUBLIST`), we parse the slice `[off..off+len]` in the `bytes code`. We iterate token by token:
- `0x00` => `NOOP`
- `0x01` => `INTEGER_PLUS`
- `0x02` => read 4 bytes => `INT_LITERAL`
- `0x03` => read 2 bytes => sub-sublist length => build a sub-descriptor
We accumulate these descriptors in an array. Then, later, we push them _in reverse_ onto the `EXEC` stack.
### 5. **`runInterpreter` Function**
1. Loads initial CODE, EXEC, and INT stacks from the provided arrays.
2. While `EXEC` is non-empty:
- Pop the top descriptor.
- If `INSTRUCTION`, decode the opcode (e.g. `INTEGER_PLUS`) and do it (pop top 2 ints, push their sum).
- If `INT_LITERAL`, push that integer onto the int stack.
- If `SUBLIST`, parse that sublist into descriptors, then push those in reverse.
## How to Contribute
1. **Add New Instructions**
- Expand `OpCode` enum (e.g. `INTEGER_MINUS`, `INTEGER_MUL`, etc.).
- Write a `makeInstruction` or `makeIntLiteral` function to build the descriptor for it.
- In the main loop, implement the logic (pop the correct number of integers, do the operation, push the result).
2. **Support More Data Types**
- For instance, a `FLOAT` or `BOOLEAN` stack.
- Add a new tag (`FLOAT_LITERAL`?), new instructions (`FLOAT.PLUS`?), etc.
3. **Improve Parsing**
- If you want 256-bit literals, define a new token that reads 32 bytes, or do other expansions (like `CODE` type).
- Or define advanced sublist structures (like nested `( ( ) )` expansions).
4. **Gas Optimization**
- Check how large arrays are allocated (like in `parseSublist`).
- Possibly implement step-by-step execution if large code exceeds block gas limits.
5. **Add Logging & Debug Tools**
- Use `console.log(...)` to trace tokens, track final states, or measure performance.
6. **Test Coverage**
- Provide more unit tests in Foundry for advanced instructions, nested sublists, edge cases with zero-length sublists, etc.
## Next Steps
### A. Extending the VM
A “full” Push3 VM might include:
- **Multiple Data Stacks**: `BOOLEAN`, `FLOAT`, `NAME`, etc.
- **NAME Binding**: Let `NAME`s reference values or code.
- **Control Structures**: `EXEC.IF`, `EXEC.DO*COUNT`, or combinators like `EXEC.Y`.
### B. Example Project
You could use this interpreter in:
- A genetic programming environment, where random code is generated and mutated.
- A puzzle solver that runs tiny “scripts” in EVM-like constraints.
## Conclusion
This `Push3Interpreter` is a **minimal** scaffold for a token-based, list-expanding interpreter with typed descriptors. By following the patterns for instructions, sublists, and typed stacks, you can easily expand it into a more complete VM. We welcome contributions in the form of new instructions, data types, and performance optimizations.
1. **Clone** this repo.
2. **Check** the `test/` folder for Foundry-based tests.
3. **Add** or modify instructions & sublists in `parseSublist` and `runInterpreter`.
4. **Run** `forge test --via-ir -vv` to see logs and confirm your changes.
Enjoy exploring the **Push3** style VM in Solidity!
+95 -9
View File
@@ -1,6 +1,14 @@
# **SoLUSH**: A Push 3 Virtual Machine in Solidity # **SoLUSH**: A Push 3 Virtual Machine in Solidity
Welcome to **SoLUSH 3** (pronounced like **"slush"** without the "o", composed from **S**o**L**idity and p**USH** 3), a Solidity implementation of the [Push 3 stack machine](http://faculty.hampshire.edu/lspector/push3-description.html), designed to bring evolved genetic algorithms on-chain. By enabling the execution of Push 3 programs directly within Ethereum smart contracts, SoLush 3 paves the way for decentralized, adaptive systems to address complex optimization problems. Welcome to **SoLUSH 3** (pronounced like **"slush"** without the "o", composed from **S**o**L**idity and p**USH** 3), a **Push3-like interpreter** implemented in Solidity. It uses a _token-based bytecode_ approach to parse programs from a `bytes` array (the `code`) into typed **descriptors**, which are then executed by the interpreter.
**Push3** is a stack-based language originally designed for genetic programming and autoconstructive evolution. Each data type (e.g. `INT_LITERAL`, `FLOAT`, `BOOLEAN`, etc.) has its own stack. The **`EXEC`** stack manages the flow of execution.
**In this implementation**:
- We store code in a single `bytes` array (`code`).
- We parse sublists and integer literals from that array.
- We push instructions and literals onto an execution stack.
- We pop them in LIFO order and execute them.
## 🌟 Vision ## 🌟 Vision
@@ -12,19 +20,97 @@ SoLUSH is a key step toward realizing the vision of running decentralized evolut
To understand the broader vision, check out the [Vision Document](vision.md). To understand the broader vision, check out the [Vision Document](vision.md).
### Key Features
1. **Token-Based Parsing**: We define small tokens (`0x00` => NOOP, `0x01` => INTEGER_PLUS, `0x02` => INT_LITERAL, `0x03` => SUBLIST). If we see `INT_LITERAL`, we read 4 bytes for a 32-bit integer; if we see `SUBLIST`, we read 2 bytes for the sublist length, then parse it recursively.
2. **Descriptor System**: We represent instructions, literals, and sublists using 256-bit “descriptors.” Each descriptor encodes:
- A `tag` (top 8 bits): e.g. `INT_LITERAL`, `INSTRUCTION`, `SUBLIST`.
- `offset` and `length` for sublists.
- A leftover field (e.g. for storing integer data).
3. **Reversing Items**: When we pop a sublist descriptor, we parse it into descriptors and then push them onto the `EXEC` stack in reverse order. This ensures the list’s leftmost token executes first, consistent with the Push3 spec.
4. **Separation of Parsing and Execution**:
- **Parsing**: Transforms raw byte tokens into typed descriptors (instructions, literals, or nested sublists).
- **Execution**: The `runInterpreter` function repeatedly pops from `EXEC`, decides if it’s an instruction or literal or sublist, and acts accordingly.
## Code Structure
### 1. **Enums**
- **`CodeTag`**:
- `NO_TAG` (unused),
- `INSTRUCTION`,
- `INT_LITERAL`,
- `SUBLIST`.
- **`OpCode`**:
- `NOOP`,
- `INTEGER_PLUS`.
### 2. **Descriptor Layout**
A **256-bit** word has:
```
[ top 8 bits | offset(32 bits) | length(32 bits) | leftover(184 bits) ]
```
- `tag`: The top 8 bits (e.g. `INT_LITERAL`).
- `offset, length`: If `tag = SUBLIST`, these define which bytes in `code` represent the sub-sublists.
- `leftover`: If `tag = INT_LITERAL`, we store the 32-bit integer in the bottom 32 bits of leftover. If `tag = INSTRUCTION`, we store the opcode (`NOOP`, `INTEGER_PLUS`) in the lowest 8 bits of leftover.
### 3. **Helper Reads**
- **`readUint32(code, start)`**: Copies 4 bytes from calldata into a small buffer, returns the final `uint32`. By shifting bits if needed, we ensure we get the integer from the correct part of the loaded word.
- **`readUint16(code, start)`**: Similar logic for 2 bytes.
### 4. **`parseSublist` Function**
When we see a sublist descriptor (`tag=SUBLIST`), we parse the slice `[off..off+len]` in the `bytes code`. We iterate token by token:
- `0x00` => `NOOP`
- `0x01` => `INTEGER_PLUS`
- `0x02` => read 4 bytes => `INT_LITERAL`
- `0x03` => read 2 bytes => sub-sublist length => build a sub-descriptor
We accumulate these descriptors in an array. Then, later, we push them _in reverse_ onto the `EXEC` stack.
### 5. **`runInterpreter` Function**
1. Loads initial CODE, EXEC, and INT stacks from the provided arrays.
2. While `EXEC` is non-empty:
- Pop the top descriptor.
- If `INSTRUCTION`, decode the opcode (e.g. `INTEGER_PLUS`) and do it (pop top 2 ints, push their sum).
- If `INT_LITERAL`, push that integer onto the int stack.
- If `SUBLIST`, parse that sublist into descriptors, then push those in reverse.
## Next Steps
### A. Extending the VM
A “full” Push3 VM might include:
- **Multiple Data Stacks**: `BOOLEAN`, `FLOAT`, `NAME`, etc.
- **NAME Binding**: Let `NAME`s reference values or code.
- **Control Structures**: `EXEC.IF`, `EXEC.DO*COUNT`, or combinators like `EXEC.Y`.
### B. Example Project
You could use this interpreter in:
- A genetic programming environment, where random code is generated and mutated.
- A puzzle solver that runs tiny “scripts” in EVM-like constraints.
## 🚀 Getting Started ## 🚀 Getting Started
SoLUSH 3 is in active development. To contribute or test the functionality, follow these steps: This `Push3Interpreter` is a **minimal** scaffold for a token-based, list-expanding interpreter with typed descriptors. By following the patterns for instructions, sublists, and typed stacks, you can easily expand it into a more complete VM. We welcome contributions in the form of new instructions, data types, and performance optimizations.
1. Clone the repository: 1. **Clone** this repo.
```bash 2. **Check** the `test/` folder for Foundry-based tests.
git clone https://github.com/your-username/SoLUSH.git 3. **Add** or modify instructions & sublists in `parseSublist` and `runInterpreter`.
cd SoLUSH 4. **Run** `forge test --via-ir -vv` to see logs and confirm your changes.
```
2. Install dependencies (if applicable).
3. Review the examples and test cases to understand how Push 3 programs are executed on the VM.
### How to contribute
Check the [Issues](../../../issues) section to find tasks to tackle.
## 🛠️ Roadmap ## 🛠️ Roadmap
- **Core Features:** - **Core Features:**
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 113 KiB

-3
View File
@@ -23,9 +23,6 @@
# ... any other dependencies we need # ... any other dependencies we need
solc solc
cargo
rustc
rust-analyzer
]; ];
# Decorative prompt override so we know when we're in a dev shell # Decorative prompt override so we know when we're in a dev shell
-4627
View File
File diff suppressed because it is too large Load Diff
-18
View File
@@ -1,18 +0,0 @@
[package]
name = "offchain"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rand = "0.8"
hex = "0.4"
# ethers = { version = "2.0", features = ["abi"] }
anyhow = "1.0"
revm = { git = "https://github.com/bluealloy/revm.git", rev = "03ea24bd9550e93b08f343e0b410725557c1adb4", features = ["std"] }
database = { git = "https://github.com/bluealloy/revm.git", rev = "03ea24bd9550e93b08f343e0b410725557c1adb4", package = "revm-database" }
ethers = "2.0"
-530
View File
@@ -1,530 +0,0 @@
// pub trait Push3Ast {
// /// Convert this AST into a bytecode array (Push3 tokens).
// fn to_bytecode(&self) -> Vec<u8>;
// // Parse an AST from bytecode (the "decompiler"), if possible.
// // Possibly returns a Result in case of partial/invalid parse.
// // fn from_bytecode(data: &[u8]) -> Self
// // where
// // Self: Sized;
// // Randomly mutate this AST in-place (GP mutation).
// // fn mutate(&mut self, rng: &mut impl rand::Rng);
// // Possibly a "crossover" method, or we do it externally.
// // fn crossover(&self, other: &Self, rng: &mut impl rand::Rng) -> Self;
// // Evaluate the AST with some input(s).
// // Optionally do we want this or do we always run in EVM?
// // We can stub it or do an interpret approach for local test.
// // fn interpret_locally(&self, inputs: &[i32]) -> Vec<i32>;
// }
// #[derive(Debug, Clone)]
// pub enum TypedAst {
// IntLiteral(i32),
// Instruction(InstructionKind),
// }
// #[derive(Debug, Clone)]
// pub enum InstructionKind {
// Plus(Box<TypedAst>, Box<TypedAst>),
// Minus(Box<TypedAst>, Box<TypedAst>),
// Mult(Box<TypedAst>, Box<TypedAst>),
// Dup, // 0 children
// Pop, // 0 children
// }
// impl Push3Ast for TypedAst {
// fn to_bytecode(&self) -> Vec<u8> {
// // do typed encoding:
// // - for Plus(e1, e2): e1 -> bytecode, e2 -> bytecode, then PLUS opcode
// unimplemented!()
// }
// // fn from_bytecode(data: &[u8]) -> Self {
// // // parse in a strictly typed manner; might fail or panic
// // // if the code doesn't line up properly
// // unimplemented!()
// // }
// // fn mutate(&mut self, rng: &mut impl rand::Rng) {
// // // e.g. randomly pick a node, replace it with something
// // // that respects the typed structure
// // unimplemented!()
// // }
// // fn interpret_locally(&self, inputs: &[i32]) -> Vec<i32> {
// // // do a normal expression evaluation. If a node is "Plus(a,b)",
// // // interpret (a) + interpret(b).
// // unimplemented!()
// // }
// }
// #[derive(Debug, Clone)]
// pub enum UntypedAst {
// /// A single token in a list-based approach, e.g. "Integer literal"
// IntLiteral(i32),
// /// "Instruction" might be a simple variant with no children,
// /// because the stack-based evaluation decides how many arguments it consumes.
// Instruction(OpCode),
// /// A sublist of tokens, e.g. "( 2 3 DUP + )"
// Sublist(Vec<UntypedAst>),
// }
// /// Possibly an enum for OpCode:
// #[derive(Debug, Clone)]
// pub enum OpCode {
// Noop,
// Plus,
// Minus,
// Mult,
// Dup,
// Pop,
// // maybe Unknown => treat as no-op
// }
// // impl Push3Ast for UntypedAst {
// // fn to_bytecode(&self) -> Vec<u8> {
// // // flatten out sublists vs instructions
// // unimplemented!()
// // }
// // // fn from_bytecode(data: &[u8]) -> Self {
// // // // parse "0x02 => INT_LITERAL => read next 4 bytes"
// // // // instructions might have no subchildren
// // // unimplemented!()
// // // }
// // // fn mutate(&mut self, rng: &mut impl rand::Rng) {
// // // // can randomly insert unknown tokens, reorder sublists, etc.
// // // // possibly produce "invalid ops => noops"
// // // unimplemented!()
// // // }
// // // fn interpret_locally(&self, inputs: &[i32]) -> Vec<i32> {
// // // // push-based interpretation:
// // // // sublist => interpret each item in sequence
// // // // if an instruction sees insufficient args => no-op
// // // unimplemented!()
// // // }
// // }
// use crate::compiler::ast::Push3Ast;
// /// An untyped AST node, matching your Push3 structure.
// #[derive(Debug, Clone)]
// pub enum UntypedAst {
// /// A single integer literal.
// IntLiteral(i32),
// /// A single push instruction (no subchildren).
// Instruction(OpCode),
// /// A sublist of items, each of which is another UntypedAst.
// Sublist(Vec<UntypedAst>),
// }
// /// Your Push3 opcodes in an untyped form.
// #[derive(Debug, Clone)]
// pub enum OpCode {
// Noop,
// Plus,
// Minus,
// Mult,
// Dup,
// Pop,
// }
// /// Implementation of `Push3Ast` for `UntypedAst`.
// ///
// /// This will produce **bytecode** compatible with your Solidity `Push3Interpreter`:
// ///
// /// - `0x00` => NOOP
// /// - `0x01` => INTEGER_PLUS
// /// - `0x02` => INT_LITERAL => next 4 bytes (little-endian i32)
// /// - `0x03` => SUBLIST => next 2 bytes (big-endian `u16` length), then the sublist contents
// /// - `0x04` => INTEGER_MINUS
// /// - `0x05` => INTEGER_MULT
// /// - `0x06` => INTEGER_DUP
// /// - `0x07` => INTEGER_POP
// ///
// /// In the Solidity parser, `SUBLIST (0x03)` consumes two bytes to determine
// /// how many bytes of tokens to read in that sublist.
// impl Push3Ast for UntypedAst {
// /// Convert this untyped AST into a flattened push3 bytecode sequence.
// fn to_bytecode(&self) -> Vec<u8> {
// match self {
// UntypedAst::IntLiteral(val) => {
// // 0x02 followed by 4 bytes of i32 (little-endian)
// let mut bytes = Vec::with_capacity(1 + 4);
// bytes.push(0x02);
// bytes.extend_from_slice(&val.to_le_bytes());
// bytes
// }
// UntypedAst::Instruction(op) => {
// // Single opcode byte
// let opcode_byte = match op {
// OpCode::Noop => 0x00,
// OpCode::Plus => 0x01,
// OpCode::Minus => 0x04,
// OpCode::Mult => 0x05,
// OpCode::Dup => 0x06,
// OpCode::Pop => 0x07,
// };
// vec![opcode_byte]
// }
// UntypedAst::Sublist(children) => {
// // 0x03 => SUBLIST
// // Then 2 bytes for length (big-endian u16),
// // Then the concatenated bytecode for all children.
// let mut payload = Vec::new();
// for child in children {
// let child_bytes = child.to_bytecode();
// payload.extend_from_slice(&child_bytes);
// }
// let sub_len = payload.len() as u16; // assume it fits in 65535
// let mut bytes = Vec::with_capacity(1 + 2 + payload.len());
// bytes.push(0x03);
// // Store the sublist length as big-endian, because readUint16 in solidity
// // does `val = uint16(word >> 240)` (effectively big-endian).
// bytes.extend_from_slice(&sub_len.to_be_bytes());
// // Then the actual sublist payload
// bytes.extend_from_slice(&payload);
// bytes
// }
// }
// }
// }
// #[derive(Debug, Clone, PartialEq)]
// pub enum SExpr {
// Atom(String), // e.g. "3", "DUP", "+"
// List(Vec<SExpr>), // e.g. (3 5 +)
// }
// fn tokenize(s: &str) -> Vec<String> {
// let mut tokens = Vec::new();
// let mut current = String::new();
// for c in s.chars() {
// match c {
// '(' | ')' => {
// // If we were building up an atom, push it first
// if !current.trim().is_empty() {
// tokens.push(current.trim().to_string());
// }
// current.clear();
// // Then push the single-char token
// tokens.push(c.to_string());
// }
// ' ' | '\t' | '\n' | '\r' => {
// // Whitespace => if we have an atom, finish it
// if !current.trim().is_empty() {
// tokens.push(current.trim().to_string());
// current.clear();
// }
// }
// _ => {
// // Part of an atom
// current.push(c);
// }
// }
// }
// // If something is left in 'current' at the end
// if !current.trim().is_empty() {
// tokens.push(current.trim().to_string());
// }
// tokens
// }
// fn parse_sexpr(tokens: &[String], pos: &mut usize) -> Result<SExpr, String> {
// // If we’re out of tokens, error
// if *pos >= tokens.len() {
// return Err("Unexpected end of tokens".into());
// }
// let token = &tokens[*pos];
// match token.as_str() {
// "(" => {
// // Parse a list
// *pos += 1; // consume '('
// let mut list_items = Vec::new();
// while *pos < tokens.len() && tokens[*pos] != ")" {
// let expr = parse_sexpr(tokens, pos)?;
// list_items.push(expr);
// }
// if *pos >= tokens.len() {
// return Err("Missing closing parenthesis".into());
// }
// // consume ")"
// *pos += 1;
// Ok(SExpr::List(list_items))
// }
// ")" => {
// // Should never parse_sexpr starting with ')'
// Err("Unexpected ')'".into())
// }
// _ => {
// // Atom
// let atom_str = token.clone();
// *pos += 1;
// Ok(SExpr::Atom(atom_str))
// }
// }
// }
// /// Parse an entire string into a single top-level `SExpr`.
// fn parse_string_to_sexpr(s: &str) -> Result<SExpr, String> {
// let tokens = tokenize(s);
// let mut pos = 0;
// let expr = parse_sexpr(&tokens, &mut pos)?;
// // If there's leftover tokens, optionally check
// if pos < tokens.len() {
// return Err(format!("Extra tokens after parse: {:?}", &tokens[pos..]));
// }
// Ok(expr)
// }
// fn sexpr_to_untyped(expr: &SExpr) -> Result<UntypedAst, String> {
// match expr {
// SExpr::Atom(text) => {
// // 1) try to parse as i32
// if let Ok(val) = text.parse::<i32>() {
// Ok(UntypedAst::IntLiteral(val))
// } else {
// // 2) otherwise parse as OpCode symbol
// match text.to_uppercase().as_str() {
// "+" => Ok(UntypedAst::Instruction(OpCode::Plus)),
// "-" => Ok(UntypedAst::Instruction(OpCode::Minus)),
// "*" => Ok(UntypedAst::Instruction(OpCode::Mult)),
// "DUP" => Ok(UntypedAst::Instruction(OpCode::Dup)),
// "POP" => Ok(UntypedAst::Instruction(OpCode::Pop)),
// // more opcodes ...
// _ => Ok(UntypedAst::Instruction(OpCode::Noop))
// }
// }
// }
// SExpr::List(list) => {
// // parse each child into UntypedAst
// let mut result = Vec::new();
// for child in list {
// result.push(sexpr_to_untyped(child)?);
// }
// Ok(UntypedAst::Sublist(result))
// }
// }
// }
// src/compiler/ast.rs
pub trait Push3Ast {
/// Convert this AST into a bytecode array (Push3 tokens).
fn to_bytecode(&self) -> Vec<u8>;
}
/* --------------------------------------------
1) TypedAst stubs (optional)
-------------------------------------------- */
#[derive(Debug, Clone)]
pub enum TypedAst {
IntLiteral(i32),
Instruction(InstructionKind),
}
#[derive(Debug, Clone)]
pub enum InstructionKind {
Plus(Box<TypedAst>, Box<TypedAst>),
Minus(Box<TypedAst>, Box<TypedAst>),
Mult(Box<TypedAst>, Box<TypedAst>),
Dup,
Pop,
}
// Example stub; not fully implemented
impl Push3Ast for TypedAst {
fn to_bytecode(&self) -> Vec<u8> {
unimplemented!("TypedAst::to_bytecode not yet implemented");
}
}
/* --------------------------------------------
2) UntypedAst + OpCode
-------------------------------------------- */
#[derive(Debug, Clone)]
pub enum UntypedAst {
IntLiteral(i32),
Instruction(OpCode),
Sublist(Vec<UntypedAst>),
}
#[derive(Debug, Clone)]
pub enum OpCode {
Noop,
Plus,
Minus,
Mult,
Dup,
Pop,
}
/// Implementation of `Push3Ast` for `UntypedAst`.
/// Matches the Solidity-based Push3Interpreter token IDs.
impl Push3Ast for UntypedAst {
fn to_bytecode(&self) -> Vec<u8> {
match self {
UntypedAst::IntLiteral(val) => {
// 0x02 => INT_LITERAL => next 4 bytes (little-endian i32)
let mut bytes = Vec::with_capacity(1 + 4);
bytes.push(0x02);
bytes.extend_from_slice(&val.to_be_bytes());
bytes
}
UntypedAst::Instruction(op) => {
let opcode_byte = match op {
OpCode::Noop => 0x00,
OpCode::Plus => 0x01,
OpCode::Minus => 0x04,
OpCode::Mult => 0x05,
OpCode::Dup => 0x06,
OpCode::Pop => 0x07,
};
vec![opcode_byte]
}
UntypedAst::Sublist(children) => {
// 0x03 => SUBLIST => next 2 bytes = big-endian length, then sub-payload
let mut payload = Vec::new();
for child in children {
payload.extend(child.to_bytecode());
}
let sub_len = payload.len() as u16;
let mut bytes = Vec::with_capacity(1 + 2 + payload.len());
bytes.push(0x03);
bytes.extend_from_slice(&sub_len.to_be_bytes()); // big-endian
bytes.extend_from_slice(&payload);
bytes
}
}
}
}
/* --------------------------------------------
3) S-expression parsing helpers
-------------------------------------------- */
#[derive(Debug, Clone, PartialEq)]
pub enum SExpr {
Atom(String),
List(Vec<SExpr>),
}
pub fn tokenize(s: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current = String::new();
for c in s.chars() {
match c {
'(' | ')' => {
if !current.trim().is_empty() {
tokens.push(current.trim().to_string());
}
current.clear();
tokens.push(c.to_string());
}
' ' | '\t' | '\n' | '\r' => {
if !current.trim().is_empty() {
tokens.push(current.trim().to_string());
current.clear();
}
}
_ => {
current.push(c);
}
}
}
if !current.trim().is_empty() {
tokens.push(current.trim().to_string());
}
tokens
}
fn parse_sexpr_internal(tokens: &[String], pos: &mut usize) -> Result<SExpr, String> {
if *pos >= tokens.len() {
return Err("Unexpected end of tokens".into());
}
let token = &tokens[*pos];
match token.as_str() {
"(" => {
*pos += 1; // consume '('
let mut items = Vec::new();
while *pos < tokens.len() && tokens[*pos] != ")" {
let expr = parse_sexpr_internal(tokens, pos)?;
items.push(expr);
}
if *pos >= tokens.len() {
return Err("Missing closing parenthesis".into());
}
*pos += 1; // consume ')'
Ok(SExpr::List(items))
}
")" => {
Err("Unexpected ')'".into())
}
_ => {
let atom = token.clone();
*pos += 1;
Ok(SExpr::Atom(atom))
}
}
}
/// Parse an entire string into a single top-level `SExpr`.
pub fn parse_string_to_sexpr(s: &str) -> Result<SExpr, String> {
let tokens = tokenize(s);
let mut pos = 0;
let expr = parse_sexpr_internal(&tokens, &mut pos)?;
if pos < tokens.len() {
return Err(format!("Extra tokens after parse: {:?}", &tokens[pos..]));
}
Ok(expr)
}
/// Convert an SExpr => UntypedAst
pub fn sexpr_to_untyped(expr: &SExpr) -> Result<UntypedAst, String> {
match expr {
SExpr::Atom(text) => {
if let Ok(val) = text.parse::<i32>() {
Ok(UntypedAst::IntLiteral(val))
} else {
match text.to_uppercase().as_str() {
"+" => Ok(UntypedAst::Instruction(OpCode::Plus)),
"-" => Ok(UntypedAst::Instruction(OpCode::Minus)),
"*" => Ok(UntypedAst::Instruction(OpCode::Mult)),
"DUP" => Ok(UntypedAst::Instruction(OpCode::Dup)),
"POP" => Ok(UntypedAst::Instruction(OpCode::Pop)),
_ => Ok(UntypedAst::Instruction(OpCode::Noop)),
}
}
}
SExpr::List(items) => {
let mut sub_asts = Vec::new();
for child in items {
sub_asts.push(sexpr_to_untyped(child)?);
}
Ok(UntypedAst::Sublist(sub_asts))
}
}
}
-1
View File
@@ -1 +0,0 @@
pub mod ast;
-1
View File
@@ -1 +0,0 @@
pub mod revm_runner;
-10
View File
@@ -1,10 +0,0 @@
// use crate::compiler::ast::{Expr};
// pub fn crossover(a: &Expr, b: &Expr) -> (Expr, Expr) {
// // stub
// (a.clone(), b.clone())
// }
// pub fn mutate(expr: &mut Expr) {
// // stub
// }
-13
View File
@@ -1,13 +0,0 @@
pub mod population;
pub mod genetic_ops;
// pub mod fitness;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dummy() {
assert_eq!(2+2, 4);
}
}
-9
View File
@@ -1,9 +0,0 @@
#[derive(Debug, Clone)]
pub struct Individual {
pub fitness: f64,
// your Expr or bytecode or both
}
pub struct Population {
pub individuals: Vec<Individual>,
}
File diff suppressed because one or more lines are too long
+9 -17
View File
@@ -28,7 +28,7 @@ contract Push3IntAdapter {
view view
returns (uint256) returns (uint256)
{ {
// CodeTag.SUBLIST is typically enum = 4, but // CodeTag.SUBLIST is typically enum = 3, but
// let's fetch it via // let's fetch it via
// (uint8)Push3Interpreter.CodeTag.SUBLIST // (uint8)Push3Interpreter.CodeTag.SUBLIST
// or we can directly pass 3 if we know the numeric value. // or we can directly pass 3 if we know the numeric value.
@@ -69,12 +69,9 @@ contract Push3IntAdapter {
int256[] memory initIntStack = new int256[](1); int256[] memory initIntStack = new int256[](1);
initIntStack[0] = in1; initIntStack[0] = in1;
// No bool stack
bool[] memory initBoolStack = new bool[](0);
// 4) Run // 4) Run
(, , int256[] memory finalIntStack,) = (, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// 5) Return top of final int stack // 5) Return top of final int stack
require(finalIntStack.length > 0, "No integer output produced"); require(finalIntStack.length > 0, "No integer output produced");
@@ -99,10 +96,8 @@ contract Push3IntAdapter {
initIntStack[0] = in1; initIntStack[0] = in1;
initIntStack[1] = in2; initIntStack[1] = in2;
bool[] memory initBoolStack = new bool[](0); (, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
(, , int256[] memory finalIntStack,) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
require(finalIntStack.length > 0, "No integer output"); require(finalIntStack.length > 0, "No integer output");
@@ -126,10 +121,8 @@ contract Push3IntAdapter {
int256[] memory initIntStack = new int256[](1); int256[] memory initIntStack = new int256[](1);
initIntStack[0] = in1; initIntStack[0] = in1;
bool[] memory initBoolStack = new bool[](0); (, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
(, , int256[] memory finalIntStack,) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
require(finalIntStack.length >= 2, "Not enough outputs"); require(finalIntStack.length >= 2, "Not enough outputs");
out1 = finalIntStack[finalIntStack.length - 1]; out1 = finalIntStack[finalIntStack.length - 1];
@@ -150,10 +143,9 @@ contract Push3IntAdapter {
uint256[] memory initCodeStack = new uint256[](0); uint256[] memory initCodeStack = new uint256[](0);
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
(, , int256[] memory finalIntStack,) = (, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack); interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
return finalIntStack; return finalIntStack;
} }
+58 -260
View File
@@ -9,15 +9,6 @@ import "forge-std/console.sol";
* a token-based bytecode approach. * a token-based bytecode approach.
*/ */
contract Push3Interpreter { contract Push3Interpreter {
// ReadUint`numBits` out of the code range.
error ReadUintOutOfRange(uint256 numBits);
// -----------------------------------------------------
// 0. CONSTANTS
// -----------------------------------------------------
uint8 internal constant OPCODE_INTEGER_OFFSET = uint8(OpCode.INTEGER_PLUS);
uint8 internal constant OPCODE_BOOL_OFFSET = uint8(OpCode.BOOL_DUP);
uint8 internal constant OPCODE_LAST = uint8(OpCode.BOOL_RAND);
// ----------------------------------------------------- // -----------------------------------------------------
// 1. ENUMS // 1. ENUMS
// ----------------------------------------------------- // -----------------------------------------------------
@@ -25,38 +16,16 @@ contract Push3Interpreter {
NO_TAG, // 0 NO_TAG, // 0
INSTRUCTION, // 1 INSTRUCTION, // 1
INT_LITERAL, // 2 INT_LITERAL, // 2
BOOL_LITERAL, // 3 SUBLIST // 3
SUBLIST // 4
} }
enum OpCode { enum OpCode {
NOOP, // 0 NOOP, // 0
NOOP_1, // 1 - empty for INSTRUCTION INTEGER_PLUS, // 1
NOOP_2, // 2 - empty for INT_LITERAL INTEGER_MINUS, // 2
NOOP_3, // 3 - empty for BOOL_LITERAL INTEGER_MULT, // 3
NOOP_4, // 4 - empty for SUBLIST INTEGER_DUP, // 4
INTEGER_PLUS, // 5 - OPCODE_INTEGER_OFFSET INTEGER_POP // 5
INTEGER_MINUS, // OPCODE_INTEGER_OFFSET + 1
INTEGER_MULT, // OPCODE_INTEGER_OFFSET + 2
INTEGER_DUP, // OPCODE_INTEGER_OFFSET + 3
INTEGER_POP, // OPCODE_INTEGER_OFFSET + 4
BOOL_DUP, // 10 - OPCODE_BOOL_OFFSET
BOOL_POP, // OPCODE_BOOL_OFFSET + 1
BOOL_SWAP, // OPCODE_BOOL_OFFSET + 2
BOOL_FLUSH, // OPCODE_BOOL_OFFSET + 3
BOOL_STACKDEPTH, // OPCODE_BOOL_OFFSET + 4
BOOL_NOT, // OPCODE_BOOL_OFFSET + 5
BOOL_AND, // OPCODE_BOOL_OFFSET + 6
BOOL_OR, // OPCODE_BOOL_OFFSET + 7
BOOL_EQ, // OPCODE_BOOL_OFFSET + 8
BOOL_FROMFLOAT, // OPCODE_BOOL_OFFSET + 9
BOOL_FROMINTEGER,// OPCODE_BOOL_OFFSET + 10
BOOL_ROT, // OPCODE_BOOL_OFFSET + 11
BOOL_SHOVE, // OPCODE_BOOL_OFFSET + 12
BOOL_YANK, // OPCODE_BOOL_OFFSET + 13
BOOL_YANKDUP, // OPCODE_BOOL_OFFSET + 14
BOOL_DEFINE, // OPCODE_BOOL_OFFSET + 15
BOOL_RAND // OPCODE_BOOL_OFFSET + 16
} }
// ----------------------------------------------------- // -----------------------------------------------------
@@ -116,81 +85,56 @@ contract Push3Interpreter {
return makeDescriptor(CodeTag.INT_LITERAL, 0, 0, masked); return makeDescriptor(CodeTag.INT_LITERAL, 0, 0, masked);
} }
function makeBoolLiteral(bool val) internal pure returns (uint256) {
uint256 masked;
if (val) masked = uint256(1);
return makeDescriptor(CodeTag.BOOL_LITERAL, 0, 0, masked);
}
function extractInt32(uint256 desc) internal pure returns (int32) { function extractInt32(uint256 desc) internal pure returns (int32) {
// read the low 32 bits // read the low 32 bits
return int32(uint32(getLow184(desc))); return int32(uint32(getLow184(desc)));
} }
function extractBool(uint256 desc) internal pure returns (bool) {
return desc & 1 == 1;
}
// ----------------------------------------------------- // -----------------------------------------------------
// 3. HELPER READS // 3. HELPER READS
// ----------------------------------------------------- // -----------------------------------------------------
/**
* @dev Read `x` bytes from `code` at `start` as uint.
*/
function readUint(bytes calldata code, uint32 start, uint256 numBytes) internal pure returns (uint256 word) {
uint256 numBits = numBytes * 8;
if (start + numBytes > code.length) revert ReadUintOutOfRange(numBits);
assembly {
let buf := mload(0x40) // free memory
// copy exactly x bytes from code into buf
calldatacopy(buf, add(code.offset, start), numBytes)
word := mload(buf)
}
word >>= 256 - numBits;
}
/** /**
* @dev Read 4 bytes from `code` at `start` as uint32. * @dev Read 4 bytes from `code` at `start` as uint32.
*/ */
function readUint32(bytes calldata code, uint32 start) internal pure returns (uint32 val) { function readUint32(bytes calldata code, uint32 start) internal pure returns (uint32 val) {
val = uint32(readUint(code, start, 4)); require(start + 4 <= code.length, "readUint32 out of range");
uint256 word;
assembly {
let buf := mload(0x40) // free memory
// copy exactly 4 bytes from code into buf
calldatacopy(buf, add(code.offset, start), 4)
word := mload(buf)
}
val = uint32(word >> 224);
} }
/** /**
* @dev Read 2 bytes from `code` at `start` as uint16. * @dev Read 2 bytes from `code` at `start` as uint16.
*/ */
function readUint16(bytes calldata code, uint32 start) internal pure returns (uint16 val) { function readUint16(bytes calldata code, uint32 start) internal pure returns (uint16 val) {
val = uint16(readUint(code, start, 2)); require(start + 2 <= code.length, "readUint16 out of range");
uint256 word;
assembly {
let buf := mload(0x40)
calldatacopy(buf, add(code.offset, start), 2)
word := mload(buf)
val := and(word, 0xffff)
} }
// Not sure this is correct
/** val = uint16(word >> 240);
* @dev Read 1 byte from `code` at `start` as uint8.
*/
function readUint8(bytes calldata code, uint32 start) internal pure returns (uint8 val) {
val = uint8(readUint(code, start, 1));
}
/**
* @dev Read 1 byte from `code` at `start` as bool.
*/
function readBool(bytes calldata code, uint32 start) internal pure returns (bool val) {
val = readUint(code, start, 1) & 1 == 1;
} }
// ----------------------------------------------------- // -----------------------------------------------------
// 4. SUBLIST PARSING // 4. SUBLIST PARSING
// ----------------------------------------------------- // -----------------------------------------------------
/** /**
* Token format is combination of ordered CodeTag and OpCode: * Token format:
* 0x00 => NOOP/NO_TAG * 0x00 => NOOP
* 0x01 => INSTRUCTION => read next 1 byte => OpCode * 0x01 => INTEGER_PLUS
* 0x02 => INT_LITERAL => read next 4 bytes => int32 * 0x02 => INT_LITERAL => read next 4 bytes => int32
* 0x03 => BOOL_LITERAL => read next 1 byte => bool * 0x03 => SUBLIST => read next 2 bytes => subLen => parse that
* 0x04 => SUBLIST => read next 2 bytes => subLen => parse that * 0x04 => INTEGER_MINUS
* 0x05 => INTEGER_PLUS
* 0x06 => INTEGER_MINUS
* ... opcode list with type offsets
*/ */
function parseSublist(bytes calldata code, uint32 off, uint32 len) function parseSublist(bytes calldata code, uint32 off, uint32 len)
internal internal
@@ -216,15 +160,9 @@ contract Push3Interpreter {
count++; count++;
} }
else if (tokenType == 0x01) { else if (tokenType == 0x01) {
// INSTRUCTION // INTEGER_PLUS
if (cur + 1 <= endPos && (cur + 1) <= code.length) { temp[count] = makeInstruction(OpCode.INTEGER_PLUS);
uint8 opcode = readUint8(code,cur);
if (opcode < OPCODE_INTEGER_OFFSET || opcode > OPCODE_LAST) opcode = uint8(0); // NOOP
temp[count] = makeInstruction(OpCode(opcode));
count++; count++;
} else {
break;
}
} }
else if (tokenType == 0x02) { else if (tokenType == 0x02) {
// INT_LITERAL => 4 bytes // INT_LITERAL => 4 bytes
@@ -239,17 +177,6 @@ contract Push3Interpreter {
} }
} }
else if (tokenType == 0x03) { else if (tokenType == 0x03) {
// BOOL_LITERAL
if (cur + 1 <= endPos && (cur + 1) <= code.length) {
bool val = readBool(code, cur);
cur += 1;
temp[count] = makeBoolLiteral(val);
count++;
} else {
break;
}
}
else if (tokenType == 0x04) {
// SUBLIST => read 2 bytes => length // SUBLIST => read 2 bytes => length
if (cur + 2 <= endPos && (cur + 2) <= code.length) { if (cur + 2 <= endPos && (cur + 2) <= code.length) {
uint16 subLen = readUint16(code, cur); uint16 subLen = readUint16(code, cur);
@@ -267,9 +194,22 @@ contract Push3Interpreter {
break; break;
} }
} }
else if (tokenType <= OPCODE_LAST) { else if (tokenType == 0x04) {
// OPCODE // INTEGER_PLUS
temp[count] = makeInstruction(OpCode(tokenType)); temp[count] = makeInstruction(OpCode.INTEGER_MINUS);
count++;
}
else if (tokenType == 0x05) {
// INTEGER_PLUS
temp[count] = makeInstruction(OpCode.INTEGER_MULT);
count++;
}
else if (tokenType == 0x06) {
temp[count] = makeInstruction(OpCode.INTEGER_DUP);
count++;
}
else if (tokenType == 0x07) {
temp[count] = makeInstruction(OpCode.INTEGER_POP);
count++; count++;
} }
else { else {
@@ -289,19 +229,17 @@ contract Push3Interpreter {
// 5. MAIN INTERPRETER // 5. MAIN INTERPRETER
// ----------------------------------------------------- // -----------------------------------------------------
function runInterpreter( function runInterpreter(
bytes calldata code, // non-empty - genetic agent (DNA) bytes calldata code,
uint256[] calldata initCodeStack, // empty uint256[] calldata initCodeStack,
uint256[] calldata initExecStack, // descriptors uint256[] calldata initExecStack,
int256[] calldata initIntStack, // 32bit word int256[] calldata initIntStack
bool[] calldata initBoolStack // bool array
) )
external external
view pure
returns ( returns (
uint256[] memory finalCodeStack, uint256[] memory finalCodeStack,
uint256[] memory finalExecStack, uint256[] memory finalExecStack,
int256[] memory finalIntStack, int256[] memory finalIntStack
bool[] memory finalBoolStack
) )
{ {
// A) CODE STACK // A) CODE STACK
@@ -325,13 +263,6 @@ contract Push3Interpreter {
intStack[k] = initIntStack[k]; intStack[k] = initIntStack[k];
} }
// D) BOOL STACK
bool[] memory boolStack = new bool[](initBoolStack.length + 256);
uint256 boolTop = initBoolStack.length;
for (uint256 k = 0; k < initBoolStack.length; k++) {
boolStack[k] = initBoolStack[k];
}
// D) MAIN LOOP // D) MAIN LOOP
while (execTop > 0) { while (execTop > 0) {
uint256 topDesc = execStack[execTop - 1]; uint256 topDesc = execStack[execTop - 1];
@@ -341,12 +272,10 @@ contract Push3Interpreter {
if (tag == CodeTag.INSTRUCTION) { if (tag == CodeTag.INSTRUCTION) {
OpCode op = getOpCode(topDesc); OpCode op = getOpCode(topDesc);
if (uint8(op) < OPCODE_INTEGER_OFFSET) { if (op == OpCode.NOOP) {
// NOOP, do nothing // do nothing
} }
else if (uint8(op) < OPCODE_BOOL_OFFSET) { else if (op == OpCode.INTEGER_PLUS) {
// INTEGER OPCODES
if (op == OpCode.INTEGER_PLUS) {
// pop top 2 => sum // pop top 2 => sum
if (intTop >= 2) { if (intTop >= 2) {
int256 a = intStack[intTop - 1]; int256 a = intStack[intTop - 1];
@@ -389,137 +318,11 @@ contract Push3Interpreter {
} }
} }
} }
else if (uint8(op) <= OPCODE_LAST) {
// BOOL OPCODES
if (op == OpCode.BOOL_DUP) {
// dup top
if (boolTop > 0) {
boolStack[boolTop] = boolStack[boolTop - 1];
boolTop++;
}
}
else if (op == OpCode.BOOL_POP) {
// pop top
if (boolTop > 0) {
boolTop--;
}
}
else if (op == OpCode.BOOL_SWAP) {
// swap top 2
if (boolTop > 1) {
(boolStack[boolTop - 1], boolStack[boolTop - 2]) = (boolStack[boolTop - 2], boolStack[boolTop - 1]);
}
}
else if (op == OpCode.BOOL_FLUSH) {
// empty stack
boolTop = 0;
}
else if (op == OpCode.BOOL_STACKDEPTH) {
// push bool depth onto int stack
intStack[intTop] = int256(boolTop);
intTop++;
}
else if (op == OpCode.BOOL_NOT) {
// push NOT of top
if (boolTop > 0) {
boolStack[boolTop - 1] = !boolStack[boolTop - 1];
}
}
else if (op == OpCode.BOOL_AND) {
// push AND of top 2
if (boolTop > 1) {
boolStack[boolTop - 2] = boolStack[boolTop - 1] && boolStack[boolTop - 2];
boolTop--;
}
}
else if (op == OpCode.BOOL_OR) {
// push OR of top 2
if (boolTop > 1) {
boolStack[boolTop - 2] = boolStack[boolTop - 1] || boolStack[boolTop - 2];
boolTop--;
}
}
else if (op == OpCode.BOOL_EQ) {
// push True if top 2 equal
if (boolTop > 1) {
boolStack[boolTop - 2] = boolStack[boolTop - 1] == boolStack[boolTop - 2];
boolTop--;
}
}
else if (op == OpCode.BOOL_FROMFLOAT) {
// FLOAT is not implemented yet
}
else if (op == OpCode.BOOL_FROMINTEGER) {
// push True if top int not eq 0
if (intTop > 0) {
boolStack[boolTop] = intStack[intTop - 1] != int256(0);
boolTop++;
intTop--;
}
}
else if (op == OpCode.BOOL_ROT) {
// rotate [top - 3, top - 2, top - 1] into -> [top - 2, top - 1, top - 3]
if (boolTop > 2) {
(boolStack[boolTop - 1], boolStack[boolTop - 2], boolStack[boolTop - 3]) = (boolStack[boolTop - 3], boolStack[boolTop - 1], boolStack[boolTop - 2]);
}
}
else if (op == OpCode.BOOL_SHOVE) {
// Task: Inserts the top BOOLEAN "deep" in the stack, at the position indexed by the top INTEGER.
// Questions: what does "inserts" mean? does it mean that we pop top value and then insert it
// at index? does it mean that array size does not change?
// what happens with the previous element at this index?
// is it correct that this element stays in array?
// should it be moved in top or bottom direction from index (index + || -)?
// does it mean that we need to reindex the whole stack?
// is the simplest solution to reindex to iterate through the whole stack?
// do we really want have such loops?
}
else if (op == OpCode.BOOL_YANK) {
// Task: Removes an indexed item from "deep" in the stack and pushes it on top of the stack. The index is taken from the INTEGER stack.
// Same question as for SHOVE is valid here.
// For example, in our implementation currently we are not really poping
// element from an array in POP, we only adjusting the counter.
// For removing index we would need to loop over the array to reindex.
}
else if (op == OpCode.BOOL_YANKDUP) {
// Task: Pushes a copy of an indexed item "deep" in the stack onto the top of the stack, without removing the deep item. The index is taken from the INTEGER stack.
uint256 index = uint256(intStack[intTop - 1]);
if (boolTop > 2) {
// pop int stack
intTop--;
// index in range (reverse array indexes)
index++; // adjust as boolTop is length, highest index is boolTop - 1
if (index > boolTop) {
index = 0;
} else {
index = boolTop - index;
}
// push value on bool stack
boolStack[boolTop] = boolStack[index];
boolTop++;
}
}
else if (op == OpCode.BOOL_DEFINE) {
// NAME is not implemented yet
}
else if (op == OpCode.BOOL_RAND) {
// Pushes a random BOOLEAN.
uint256 randomNum = uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, msg.sender)));
boolStack[boolTop] = extractBool(randomNum);
boolTop++;
}
}
}
else if (tag == CodeTag.INT_LITERAL) { else if (tag == CodeTag.INT_LITERAL) {
int32 val = extractInt32(topDesc); int32 val = extractInt32(topDesc);
intStack[intTop] = int256(val); intStack[intTop] = int256(val);
intTop++; intTop++;
} }
else if (tag == CodeTag.BOOL_LITERAL) {
bool val = extractBool(topDesc);
boolStack[boolTop] = val;
boolTop++;
}
else if (tag == CodeTag.SUBLIST) { else if (tag == CodeTag.SUBLIST) {
// parse sublist => push in reverse // parse sublist => push in reverse
uint32 off = getOffset(topDesc); uint32 off = getOffset(topDesc);
@@ -527,8 +330,8 @@ contract Push3Interpreter {
if (off + len <= code.length) { if (off + len <= code.length) {
uint256[] memory parsed = parseSublist(code, off, len); uint256[] memory parsed = parseSublist(code, off, len);
for (uint256 p = 0; p < parsed.length; p++) { // index 0 1 2 for (uint256 p = 0; p < parsed.length; p++) {
execStack[execTop] = parsed[parsed.length - 1 - p]; // [0x01, 0x20, 0x0a] execStack[execTop] = parsed[parsed.length - 1 - p];
execTop++; execTop++;
} }
} }
@@ -553,10 +356,5 @@ contract Push3Interpreter {
for (uint256 i = 0; i < intTop; i++) { for (uint256 i = 0; i < intTop; i++) {
finalIntStack[i] = intStack[i]; finalIntStack[i] = intStack[i];
} }
finalBoolStack = new bool[](boolTop);
for (uint256 i = 0; i < boolTop; i++) {
finalBoolStack[i] = boolStack[i];
}
} }
} }
+4 -4
View File
@@ -28,7 +28,7 @@ contract Push3IntAdapterTest is Test {
* (like a NOOP). * (like a NOOP).
*/ */
function test_run1In1Out_noop() public view { function test_run1In1Out_noop() public view {
bytes memory code = hex"04000100"; bytes memory code = hex"03000100";
int256 inputVal = 123; int256 inputVal = 123;
int256 result = adapter.run1In1Out(code, inputVal); int256 result = adapter.run1In1Out(code, inputVal);
@@ -41,7 +41,7 @@ contract Push3IntAdapterTest is Test {
* @notice Test run2In1Out. We'll push 5, 7, do an op that leaves 12. * @notice Test run2In1Out. We'll push 5, 7, do an op that leaves 12.
*/ */
function test_run2In1Out_plus() public view { function test_run2In1Out_plus() public view {
bytes memory code = hex"04000105"; bytes memory code = hex"03000101";
int256 in1 = 5; int256 in1 = 5;
int256 in2 = 7; int256 in2 = 7;
@@ -55,7 +55,7 @@ contract Push3IntAdapterTest is Test {
* For now, we do a placeholder code. * For now, we do a placeholder code.
*/ */
function test_run1In2Out() public view { function test_run1In2Out() public view {
bytes memory code = hex"0400050200000003"; bytes memory code = hex"0300050200000003";
int256 inVal = 42; int256 inVal = 42;
(int256 top, int256 secondTop) = adapter.run1In2Out(code, inVal); (int256 top, int256 secondTop) = adapter.run1In2Out(code, inVal);
@@ -70,7 +70,7 @@ contract Push3IntAdapterTest is Test {
*/ */
function test_run0InNOut() public view { function test_run0InNOut() public view {
bytes memory code = hex"04000A02000000030200000005"; bytes memory code = hex"03000A02000000030200000005";
int256[] memory stackResult = adapter.run0InNOut(code); int256[] memory stackResult = adapter.run0InNOut(code);
+42 -573
View File
@@ -23,30 +23,30 @@ contract Push3InterpreterTest is Test {
* 3) Calls INTEGER_PLUS * 3) Calls INTEGER_PLUS
* *
* We'll store everything in a single SUBLIST: * We'll store everything in a single SUBLIST:
* 0x04 => SUBLIST * 0x03 => SUBLIST
* 0x00 0x0B => subLen=11 * 0x00 0x0B => subLen=11
* Then the next 11 bytes are: * Then the next 11 bytes are:
* 0x02 + (00 00 00 0A) => INT_LITERAL(10) * 0x02 + (00 00 00 0A) => INT_LITERAL(10)
* 0x02 + (00 00 00 20) => INT_LITERAL(32) * 0x02 + (00 00 00 20) => INT_LITERAL(32)
* 0x05 => INTEGER_PLUS * 0x01 => INTEGER_PLUS
* *
* So total = 1 + 2 + (1+4 + 1+4 + 1) = 14 bytes * So total = 1 + 2 + (1+4 + 1+4 + 1) = 14 bytes
* => 0x04 00 0B 02 00 00 00 0A 02 00 00 00 20 05 * => 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01
*/ */
function test_LiteralsAndPlus() public view { function test_LiteralsAndPlus() public view {
// The final code array (14 bytes): // The final code array (14 bytes):
// [0] = 0x04 // [0] = 0x03
// [1..2] = 0x000B // [1..2] = 0x000B
// [3] = 0x02 // [3] = 0x02
// [4..7] = 0x0000000A => 10 // [4..7] = 0x0000000A => 10
// [8] = 0x02 // [8] = 0x02
// [9..12] = 0x00000020 => 32 // [9..12] = 0x00000020 => 32
// [13] = 0x05 => plus // [13] = 0x01 => plus
bytes memory code = hex"04000B020000000A020000002005"; bytes memory code = hex"03000B020000000A020000002001";
// Or fully concatenated: // Or fully concatenated:
// 0x04 00 0B 02 00 00 00 0A 02 00 00 00 20 05 // 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01
// We'll parse offset=0, length=14 // We'll parse offset=0, length=14
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
@@ -61,15 +61,13 @@ contract Push3InterpreterTest is Test {
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run it // Run it
( (
, ,
, ,
int256[] memory finalIntStack int256[] memory finalIntStack
, ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Should produce finalIntStack = [42] // Should produce finalIntStack = [42]
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
@@ -80,25 +78,25 @@ contract Push3InterpreterTest is Test {
* @notice Test pushing literal 10, literal 3, then INTEGER_MINUS => (10 - 3) = 7 * @notice Test pushing literal 10, literal 3, then INTEGER_MINUS => (10 - 3) = 7
* *
* The plan: * The plan:
* 0x04 => SUBLIST token * 0x03 => SUBLIST token
* 0x00 0x0B => subLen=11 * 0x00 0x0B => subLen=11
* *
* Then 11 bytes: * Then 11 bytes:
* 0x02 + (00 00 00 0A) => INT_LITERAL(10) * 0x02 + (00 00 00 0A) => INT_LITERAL(10)
* 0x02 + (00 00 00 03) => INT_LITERAL(3) * 0x02 + (00 00 00 03) => INT_LITERAL(3)
* 0x06 => INTEGER_MINUS (assuming 0x06 is assigned to minus) * 0x04 => INTEGER_MINUS (assuming 0x04 is assigned to minus)
*/ */
function test_Minus() public view { function test_Minus() public view {
// We'll produce 14 total bytes, same structure as the plus example // We'll produce 14 total bytes, same structure as the plus example
// Format: [0x04, 0x000B, 0x02(10), 0x03(3), 0x06 => minus] // Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x04 => minus]
bytes memory code = hex"04000B020000000A020000000306"; bytes memory code = hex"03000B020000000A020000000304";
// Breaking it down: // Breaking it down:
// 0x04 => SUBLIST // 0x03 => SUBLIST
// 0x00 0x0B => length=11 // 0x00 0x0B => length=11
// 0x02 0000000A => INT_LITERAL(10) // 0x02 0000000A => INT_LITERAL(10)
// 0x02 00000003 => INT_LITERAL(3) // 0x02 00000003 => INT_LITERAL(3)
// 0x06 => INTEGER_MINUS // 0x04 => INTEGER_MINUS
// We'll parse offset=0, length=14 // We'll parse offset=0, length=14
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
@@ -113,15 +111,13 @@ contract Push3InterpreterTest is Test {
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run // Run
( (
, ,
, ,
int256[] memory finalIntStack int256[] memory finalIntStack
, ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be [7] // Check final result => should be [7]
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
@@ -132,25 +128,25 @@ contract Push3InterpreterTest is Test {
* @notice Test pushing literal 10, literal 3, then INTEGER_MULT => (10 * 3) = 30 * @notice Test pushing literal 10, literal 3, then INTEGER_MULT => (10 * 3) = 30
* *
* The plan: * The plan:
* 0x04 => SUBLIST token * 0x03 => SUBLIST token
* 0x00 0x0B => subLen=11 * 0x00 0x0B => subLen=11
* *
* Then 11 bytes: * Then 11 bytes:
* 0x02 + (00 00 00 0A) => INT_LITERAL(10) * 0x02 + (00 00 00 0A) => INT_LITERAL(10)
* 0x02 + (00 00 00 03) => INT_LITERAL(3) * 0x02 + (00 00 00 03) => INT_LITERAL(3)
* 0x07 => INTEGER_MULT * 0x05 => INTEGER_MULT
*/ */
function test_Mult() public view { function test_Mult() public view {
// We'll produce 14 total bytes, same structure as the plus example // We'll produce 14 total bytes, same structure as the plus example
// Format: [0x04, 0x000B, 0x02(10), 0x02(3), 0x07 => mul] // Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x05 => mul]
bytes memory code = hex"04000B020000000A020000000307"; bytes memory code = hex"03000B020000000A020000000305";
// Breaking it down: // Breaking it down:
// 0x04 => SUBLIST // 0x03 => SUBLIST
// 0x00 0x0B => length=11 // 0x00 0x0B => length=11
// 0x02 0000000A => INT_LITERAL(10) // 0x02 0000000A => INT_LITERAL(10)
// 0x02 00000003 => INT_LITERAL(3) // 0x02 00000003 => INT_LITERAL(3)
// 0x07 => INTEGER_MUL // 0x05 => INTEGER_MUL
// We'll parse offset=0, length=14 // We'll parse offset=0, length=14
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
@@ -165,15 +161,13 @@ contract Push3InterpreterTest is Test {
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run // Run
( (
, ,
, ,
int256[] memory finalIntStack int256[] memory finalIntStack
, ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be [30] // Check final result => should be [30]
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1"); assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
@@ -182,12 +176,12 @@ contract Push3InterpreterTest is Test {
function test_Dup() public view { function test_Dup() public view {
bytes memory code = hex"040009020000000A0803010A"; bytes memory code = hex"030006020000000A06";
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST, Push3Interpreter.CodeTag.SUBLIST,
0, 0,
12, 9,
0 0
); );
@@ -196,30 +190,26 @@ contract Push3InterpreterTest is Test {
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run // Run
( (
, ,
, ,
int256[] memory finalIntStack, int256[] memory finalIntStack
bool[] memory finalBoolStack ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
assertEq(finalIntStack.length, 2, "finalIntStack should have length 1"); assertEq(finalIntStack.length, 2, "finalIntStack should have length 1");
assertEq(finalIntStack[0], finalIntStack[1], "The top 2 values on the int stack should be equal."); assertEq(finalIntStack[0], finalIntStack[1], "The top 2 values on the stack should be equal.");
assertEq(finalBoolStack.length, 2, "finalBoolStack should have length 2");
assertEq(finalBoolStack[0], finalBoolStack[1], "The top 2 values on the bool stack should be equal.");
} }
function test_Pop() public view { function test_Pop() public view {
bytes memory code = hex"040009020000000A0903010B"; bytes memory code = hex"030006020000000A07";
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST, Push3Interpreter.CodeTag.SUBLIST,
0, 0,
12, 9,
0 0
); );
@@ -228,18 +218,15 @@ contract Push3InterpreterTest is Test {
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run // Run
( (
, ,
, ,
int256[] memory finalIntStack, int256[] memory finalIntStack
bool[] memory finalBoolStack ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
assertEq(finalIntStack.length, 0, "finalIntStack should be empty"); assertEq(finalIntStack.length, 0, "finalIntStack should be empty");
assertEq(finalBoolStack.length, 0, "finalBoolStack should be empty");
} }
/** /**
@@ -250,10 +237,10 @@ contract Push3InterpreterTest is Test {
*/ */
function test_UnknownToken() public view { function test_UnknownToken() public view {
// We'll create a 5-byte code: // We'll create a 5-byte code:
// 0x04 => SUBLIST // 0x03 => SUBLIST
// 0x00 0x01 => length=1 // 0x00 0x01 => length=1
// 0xF0 => unknown token // 0xF0 => unknown token
bytes memory code = hex"040001F0"; bytes memory code = hex"030001F0";
// We'll parse offset=0, length=4 // We'll parse offset=0, length=4
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
@@ -268,14 +255,12 @@ contract Push3InterpreterTest is Test {
uint256[] memory initExecStack = new uint256[](1); uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
( (
, ,
, ,
int256[] memory finalIntStack int256[] memory finalIntStack
, ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// finalIntStack should remain empty => no literal was pushed // finalIntStack should remain empty => no literal was pushed
assertEq(finalIntStack.length, 0, "Expected finalIntStack to remain empty with unknown token => NOOP"); assertEq(finalIntStack.length, 0, "Expected finalIntStack to remain empty with unknown token => NOOP");
@@ -287,14 +272,14 @@ contract Push3InterpreterTest is Test {
* when trying to read 4 bytes for the INT_LITERAL. * when trying to read 4 bytes for the INT_LITERAL.
*/ */
function test_OutOfRangeLiteral() public view { function test_OutOfRangeLiteral() public view {
// 0x04 => SUBLIST // 0x03 => SUBLIST
// 0x00 0x04 => length=4 // 0x00 0x04 => length=4
// 0x02 => INT_LITERAL token // 0x02 => INT_LITERAL token
// Then we have only 1 byte left instead of 4 => insufficient // Then we have only 1 byte left instead of 4 => insufficient
// //
// total = 1 + 2 + 1 + 1 = 5 bytes // total = 1 + 2 + 1 + 1 = 5 bytes
// The parser tries to read 4 bytes after seeing 0x04, fails, hits break. // The parser tries to read 4 bytes after seeing 0x04, fails, hits break.
bytes memory code = hex"04000402FF"; bytes memory code = hex"03000402FF";
// parse offset=0, length=5 // parse offset=0, length=5
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
@@ -308,14 +293,12 @@ contract Push3InterpreterTest is Test {
uint256[] memory initExecStack = new uint256[](1); uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
( (
, ,
, ,
int256[] memory finalIntStack int256[] memory finalIntStack
, ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Because the parse breaks early, we expect finalIntStack is empty or unchanged // Because the parse breaks early, we expect finalIntStack is empty or unchanged
assertEq(finalIntStack.length, 0, "Expected parse to break on out-of-range literal => no ints pushed"); assertEq(finalIntStack.length, 0, "Expected parse to break on out-of-range literal => no ints pushed");
@@ -327,12 +310,12 @@ contract Push3InterpreterTest is Test {
* In the main loop, it sees intTop < 2 => it does nothing. * In the main loop, it sees intTop < 2 => it does nothing.
*/ */
function test_NotEnoughIntArgs() public view { function test_NotEnoughIntArgs() public view {
bytes memory code = hex"040006020000000307"; bytes memory code = hex"030006020000000305";
// Breaking it down: // Breaking it down:
// 0x04 => SUBLIST // 0x03 => SUBLIST
// 0x00 0x06 => length=6 // 0x00 0x06 => length=6
// 0x02 00000003 => INT_LITERAL(3) // 0x02 00000003 => INT_LITERAL(3)
// 0x07 => INTEGER_MUL // 0x05 => INTEGER_MUL
uint256 sublistDesc = interpreter.makeDescriptor( uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST, Push3Interpreter.CodeTag.SUBLIST,
@@ -346,14 +329,12 @@ contract Push3InterpreterTest is Test {
uint256[] memory initExecStack = new uint256[](1); uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
( (
, ,
, ,
int256[] memory finalIntStack int256[] memory finalIntStack
, ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// We expect finalIntStack => [5], // We expect finalIntStack => [5],
// because the PLUS instruction sees only 1 int => does nothing => 5 remains // because the PLUS instruction sees only 1 int => does nothing => 5 remains
@@ -382,526 +363,14 @@ contract Push3InterpreterTest is Test {
uint256[] memory initExecStack = new uint256[](1); uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc; initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0); int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
( (
, ,
, ,
int256[] memory finalIntStack int256[] memory finalIntStack
, ) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Expect no items pushed => finalIntStack empty // Expect no items pushed => finalIntStack empty
assertEq(finalIntStack.length, 0, "Expected empty stack if sublist length is out of range => parse break"); assertEq(finalIntStack.length, 0, "Expected empty stack if sublist length is out of range => parse break");
} }
// BOOL STACK
/**
* @notice Test pushing literal True, literal False, then BOOL_SWAP => [False, True]
*
* The plan:
* 0x04 => SUBLIST token
* 0x00 0x05 => subLen=5
*
* Then 5 bytes:
* 0x03 + (01) => BOOL_LITERAL(True)
* 0x03 + (00) => BOOL_LITERAL(False)
* 0x0C => BOOL_SWAP
*/
function test_BoolSwap() public view {
// We'll produce 8 total bytes, same structure
// Format: [0x04, 0x0005, 0x03(01), 0x03(00), 0x0C => bool_swap]
bytes memory code = hex"040005030103000C";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x05 => length=5
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x00 => BOOL_LITERAL(False)
// 0x0C => BOOL_SWAP
// We'll parse offset=0, length=8
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
8, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be [True, False] swapped into [False, True]
assertEq(finalBoolStack.length, 2, "finalBoolStack should have length 2");
assertEq(finalBoolStack[0], false, "Should be False");
assertEq(finalBoolStack[1], true, "Should be True");
}
/**
* @notice Test pushing literal True, literal False, then BOOL_FLUSH => []
* Result should be empty stack.
*/
function test_BoolFlush() public view {
// We'll produce 8 total bytes, same structure
// Format: [0x04, 0x0005, 0x03(01), 0x03(00), 0x0D => bool_flush]
bytes memory code = hex"040005030103000D";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x05 => length=5
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x00 => BOOL_LITERAL(False)
// 0x0D => BOOL_FLUSH
// We'll parse offset=0, length=8
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
8, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be empty bool stack
assertEq(finalBoolStack.length, 0, "finalBoolStack should have length 0");
}
/**
* @notice Test pushing literal True, literal False, then BOOL_STACKDEPTH => intStack[2]
* Result should be number of pushed literals as intStack top.
*/
function test_BoolStackDepth() public view {
// We'll produce 8 total bytes, same structure
// Format: [0x04, 0x0005, 0x03(01), 0x03(00), 0x0E => bool_stackdepth]
bytes memory code = hex"040005030103000E";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x05 => length=5
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x00 => BOOL_LITERAL(False)
// 0x0E => BOOL_STACKDEPTH
// We'll parse offset=0, length=8
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
8, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
int256[] memory finalIntStack,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be empty bool stack
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
assertEq(finalBoolStack.length, 2, "finalBoolStack should have length 2");
assertEq(finalIntStack[0], 2, "IntStack top value should be 2, as we pushed 2 literals onto bool stack");
}
/**
* @notice Test pushing literal True, then BOOL_NOT => boolStack[False]
* Result should be False on top of bool stack.
*/
function test_BoolNot() public view {
// We'll produce 6 total bytes, same structure
// Format: [0x04, 0x0003, 0x03(01), 0x0F => bool_not]
bytes memory code = hex"04000303010F";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x03 => length=3
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x0F => BOOL_NOT
// We'll parse offset=0, length=6
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
6, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be False on top of bool stack
assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1");
assertEq(finalBoolStack[0], false, "Expected to be False after logical NOT on True");
}
/**
* @notice Test pushing literal True, literal True, then BOOL_AND => boolStack[True]
* Result should be True on top of bool stack.
*/
function test_BoolAnd() public view {
// We'll produce 8 total bytes, same structure
// Format: [0x04, 0x0005, 0x03(01), 0x03(01) 0x10 => bool_and]
bytes memory code = hex"0400050301030110";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x05 => length=5
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x10 => BOOL_AND
// We'll parse offset=0, length=8
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
8, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be True on top of bool stack
assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1");
assertEq(finalBoolStack[0], true, "Expected to be True after logical AND on True and True");
}
/**
* @notice Test pushing literal True, literal False, then BOOL_OR => boolStack[True]
* Result should be True on top of bool stack.
*/
function test_BoolOr() public view {
// We'll produce 8 total bytes, same structure
// Format: [0x04, 0x0005, 0x03(01), 0x03(00) 0x11 => bool_or]
bytes memory code = hex"0400050301030011";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x05 => length=5
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x00 => BOOL_LITERAL(False)
// 0x11 => BOOL_OR
// We'll parse offset=0, length=8
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
8, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be True on top of bool stack
assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1");
assertEq(finalBoolStack[0], true, "Expected to be True after logical OR on True and False");
}
/**
* @notice Test pushing literal True, literal False, then BOOL_EQ => boolStack[False]
* Result should be False on top of bool stack.
*/
function test_BoolEq() public view {
// We'll produce 8 total bytes, same structure
// Format: [0x04, 0x0005, 0x03(01), 0x03(00) 0x12 => bool_eq]
bytes memory code = hex"0400050301030012";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x05 => length=5
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x00 => BOOL_LITERAL(False)
// 0x12 => BOOL_EQ
// We'll parse offset=0, length=8
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
8, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be False on top of bool stack
assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1");
assertEq(finalBoolStack[0], false, "Expected to be False as True is not equal False");
}
/**
* @notice Test pushing literal 0x2145, then BOOL_FROMINTEGER => boolStack[True]
* Result should be True on top of bool stack.
*/
function test_BoolFromInteger() public view {
// We'll produce 9 total bytes, same structure
// Format: [0x04, 0x0004, 0x02(0x00002145), 0x14 => bool_frominteger]
bytes memory code = hex"040006020000214514";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x04 => length=6
// 0x02 0x00002145 => INT_LITERAL(0x2145)
// 0x14 => BOOL_FROMINTEGER
// We'll parse offset=0, length=9
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
9, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
int256[] memory finalIntStack,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be True on bool stack
assertEq(finalIntStack.length, 0, "finalIntStack should have length 0");
assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1");
assertEq(finalBoolStack[0], true, "boolStack top value should be True, as we pushed non-zero literal onto int stack");
}
/**
* @notice Test pushing literal True, literal False, literal True, then BOOL_ROT => boolStack[False, True, True]
* Result should be rotated bool stack from [True, False, True] into [False, True, True].
*/
function test_BoolRot() public view {
// We'll produce 10 total bytes, same structure
// Format: [0x04, 0x0007, 0x03(01), 0x03(00), 0x03(01), 0x15 => bool_rot]
bytes memory code = hex"04000703010300030115";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x07 => length=7
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x00 => BOOL_LITERAL(False)
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x15 => BOOL_ROT
// We'll parse offset=0, length=10
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
10, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be [False, True, True] bool stack
assertEq(finalBoolStack.length, 3, "finalBoolStack should have length 3");
assertEq(finalBoolStack[0], false, "Expected to be False as value from middle position moved to bottom of the stack");
assertEq(finalBoolStack[1], true, "Expected to be True as value from top position moved to middle of the stack");
assertEq(finalBoolStack[2], true, "Expected to be True as value from bottom position moved to top of the stack");
}
/**
* @notice Test pushing literal 0x02, literal True, literal False, literal True, then BOOL_YANKDUP => boolStack[True, False, True, True]
* Result should be copied True on top of bool stack [True, False, True, True]
*/
function test_BoolYankDup() public view {
// We'll produce 15 total bytes, same structure
// Format: [0x04, 0x000C, 0x02(00 00 00 02) 0x03(01), 0x03(00), 0x03(01), 0x18 => bool_yankdup]
bytes memory code = hex"04000C020000000203010300030118";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x0C => length=12
// 0x02 0x00000002 => INT_LITERAL(0x02)
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x03 0x00 => BOOL_LITERAL(False)
// 0x03 0x01 => BOOL_LITERAL(True)
// 0x18 => BOOL_YANKDUP
// We'll parse offset=0, length=15
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
15, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be [True, False, True, True] bool stack
assertEq(finalBoolStack.length, 4, "finalBoolStack should have length 4");
assertEq(finalBoolStack[0], true, "Expected to be True as first value from bool literal");
assertEq(finalBoolStack[1], false, "Expected to be False as second value from bool literal");
assertEq(finalBoolStack[2], true, "Expected to be True as third value from bool literal");
assertEq(finalBoolStack[3], true, "Expected to be True as copied value at index 2, which is index 0 for an array");
}
/**
* @notice Test pushing BOOL_RAND => boolStack[True] or boolStack[False]
* Result should be either False or True on top of bool stack.
*/
function test_BoolRand() public {
// We'll produce 4 total bytes, same structure
// Format: [0x04, 0x0001, 0x1A => bool_rand]
bytes memory code = hex"0400011A";
// Breaking it down:
// 0x04 => SUBLIST
// 0x00 0x01 => length=1
// 0x1A => BOOL_RAND
// We'll parse offset=0, length=4
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
4, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
bool[] memory initBoolStack = new bool[](0);
// Run
(
,
,
,
bool[] memory finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
// Check final result => should be random bool on top of bool stack
assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1");
assertEq(finalBoolStack[0], false, "Expected to be False for default prevrandao, timestamp and msg.sender");
// move to another timestamp to get different random result
vm.warp(1736055577);
(
,
,
,
finalBoolStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack, initBoolStack);
assertEq(finalBoolStack.length, 1, "finalBoolStack should have length 1");
assertEq(finalBoolStack[0], true, "Expected to be True for configured timestamp and default prevrandao, msg.sender");
}
} }
+24 -4
View File
@@ -41,13 +41,33 @@ An agent is defined as an entity capable of perceiving its environment, processi
--- ---
## **Theoretical Framework for Decentralized Algorithm Evolution** ## **Theoretical Framework for Decentralized Genetic Programming**
A protocol providing agents as a service could leverage genetic algorithms to address complex problems within blockchain environments. Read through this [real-life usecase explaining the applicability of agents to DeFi](example.md). Central to such a system would be the development of a Push 3 Virtual Machine (VM), enabling efficient execution of genetic algorithms. An "Optimizer Pool", an network of nodes could collaboratively train and evolve these algorithms offchain. These nodes would compete to improve the fitness of the algorithms. Once an improvement, a measuruable better solution to the fitness function is found in off-chain computation, a node would submit it to be accepted into the population of solutions registered on chain, in exchange for a rewards payment by the protocol. The nodes of the optimizer pool would run under an incentivization model simira to proof-of-work systems. A protocol providing agents as a service could leverage genetic programming to address complex problems within blockchain environments. Central to such a system would be the development of a Push 3 Virtual Machine (VM), enabling efficient execution of genetic algorithms. Furthermore, this protocol would combines on-chain smart contracts with off-chain optimization to evolve and deploy efficient algorithms for DeFi tasks.
### On-Chain: Tasks and Deployment
1. **Task Creation**: Optimization problems are registered as "Tasks" in the "Registry." Each Task includes:
- A fitness function (goal criteria).
- Evaluation parameters (data and conditions).
- A population of algorithms (initially empty).
2. **Agent Deployment**: The best-performing algorithm from the population is selected and deployed to the relevant smart contract. Read through this [real-life usecase explaining the applicability of agents to DeFi](example.md). When better algorithms are discovered, the on-chain agent is updated.
### Off-Chain: Evolution and Validation
1. **Task Selection**: Optimizers (network participants) fetch a Task from the Registry.
2. **Algorithm Evolution**:
- Optimizers run simulations locally using synthetic or historical blockchain data.
- Genetic algorithms are applied to evolve better solutions, improving fitness scores.
3. **Submission and Validation**: Improved algorithms are submitted to a "Validator Pool," which verifies the results. Validators vote on whether to accept the new algorithm into the population, replacing weaker ones.
4. **Incentives**: Successful submissions earn rewards for the Optimizer.
### Key Components
- **Registry (on-chain)**: Manages Tasks and stores algorithm populations.
- **Optimizers (off-chain)**: Compete to evolve better solutions.
- **Validator Pool (off-chain)**: Ensures the quality and legitimacy of submissions.
![Components](/components.png) ![Components](/components.png)
To evaluate the effectiveness of distributed evolution, a "Council" mechanism needs to be created. This Council would consist of validators who execute the latest evolved algorithms and assess their performance against a predefined target fitness function. Validators would then cast votes on whether the new algorithms deliver measurable improvements. This transparent voting process ensures that only the most effective solutions are rewarded and integrated, fostering trust and collaboration across the Optimizer Pool. By aligning incentives with measurable progress, such a framework provides a reliable and decentralized method for advancing algorithmic evolution. This framework integrates off-chain computation with on-chain validation and deployment, ensuring scalable, transparent optimization. By decentralizing the process, it aligns incentives and fosters continuous improvement tailored to real-world DeFi needs. Ultimately, this system creates a foundation for scalable agent development, aligning with the broader concept of agents as a service, where users and protocols in DeFi can deploy and leverage optimized, evolving AI-agents tailored to specific tasks.
Ultimately, this system creates a foundation for scalable agent development, aligning with the broader concept of agents as a service, where users and protocols in DeFi can deploy and leverage optimized, evolving algorithms tailored to specific tasks.