5 Commits
6 changed files with 170 additions and 138 deletions
+3
View File
@@ -1,2 +1,5 @@
onchain/cache/ onchain/cache/
onchain/out/ onchain/out/
out/
cache/
lib/forge-std
+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

+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.