Author SHA1 Message Date
JulesCrown e3b6352eb8 tokenomics draft 2024-12-27 15:40:35 +01:00
giteadminandJulesCrown 2a4f01481b merge interpreter.md into readme (#4)
Co-authored-by: JulesCrown <admin@noip.localhost>
Reviewed-on: http://gitea.loseyourip.com:4000/giteadmin/SoLUSH-3/pulls/4
2024-12-26 10:30:26 +00:00
giteadmin 1079129e48 Merge pull request 'scaffolding' (#1) from scaffolding into main
reviewed scaffolding
2024-12-24 12:02:50 +00:00
giteadmin e668fcf9a0 Merge pull request 'interpreter-expansion' (#2) from interpreter-expansion into scaffolding
reviewed bunch of new int operations
2024-12-24 12:02:08 +00:00
giteadmin a1890e650b Merge pull request 'refactor-dirs' (#3) from refactor-dirs into interpreter-expansion
reviewed logic move to dir
2024-12-24 12:00:57 +00:00
zjkree 50288eb30c forge install: forge-std
v1.9.5
2024-12-24 06:04:03 +01:00
zjkree 818da97ea5 Move onchain logic to speperate dir. 2024-12-24 06:03:14 +01:00
zjkree 9f3636d232 Remove forge-std. 2024-12-24 06:00:54 +01:00
zjkree dadfb7983a Add integer dup and pop. 2024-12-24 05:46:09 +01:00
zjkree 6b874bf8ba Make test view to avoid warnings. 2024-12-24 05:45:54 +01:00
zjkree fce9ae4d6f Add int adapter. 2024-12-24 05:28:57 +01:00
zjkree 40172b8eaf Imrove test coverage. 2024-12-24 04:22:24 +01:00
zjkree 92e353dcda Add INTEGER_MINUS and INETEGER_MULT. 2024-12-24 03:58:50 +01:00
zjkree 4cc20edf96 forge install: forge-std
v1.9.5
2024-12-23 07:10:20 +01:00
zjkree 268823c130 Remove forge-std to readd it as proper dependency. 2024-12-23 07:10:04 +01:00
zjkree a5248b3bd4 Add Interpreter.md. 2024-12-23 07:01:40 +01:00
zjkree 817c4f8f44 Add Push3Interpreter and a simple test. 2024-12-23 06:45:36 +01:00
zjkree 7b9aac5f97 Add .gitignore. 2024-12-23 03:14:26 +01:00
zjkree cb3aa7cb77 Foundry init. 2024-12-23 03:07:37 +01:00
zjkree 9d4b4151fc Add forge-std. 2024-12-23 03:07:19 +01:00
zjkree 34f0d4e647 Add flake.nix. 2024-12-23 02:59:11 +01:00
14 changed files with 1454 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
onchain/cache/
onchain/out/
out/
cache/
lib/forge-std
+4
View File
@@ -0,0 +1,4 @@
[submodule "onchain/lib/forge-std"]
path = onchain/lib/forge-std
url = https://github.com/foundry-rs/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
-->
+95 -9
View File
@@ -1,6 +1,14 @@
# **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
@@ -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).
### 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
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:
```bash
git clone https://github.com/your-username/SoLUSH.git
cd SoLUSH
```
2. Install dependencies (if applicable).
3. Review the examples and test cases to understand how Push 3 programs are executed on the VM.
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.
### How to contribute
Check the [Issues](../../../issues) section to find tasks to tackle.
## 🛠️ Roadmap
- **Core Features:**
Generated
+111
View File
@@ -0,0 +1,111 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"foundry": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1733217107,
"narHash": "sha256-T5rE+F85pRWkG7efabISw7ZNLgCBhnAg6egu5uGT4Bg=",
"owner": "shazow",
"repo": "foundry.nix",
"rev": "b0534fca03a058756b4a405aacd5f5059a879673",
"type": "github"
},
"original": {
"owner": "shazow",
"ref": "monthly",
"repo": "foundry.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1666753130,
"narHash": "sha256-Wff1dGPFSneXJLI2c0kkdWTgxnQ416KE6X4KnFkgPYQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f540aeda6f677354f1e7144ab04352f61aaa0118",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1734435836,
"narHash": "sha256-kMBQ5PRiFLagltK0sH+08aiNt3zGERC2297iB6vrvlU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4989a246d7a390a859852baddb1013f825435cee",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"foundry": "foundry",
"nixpkgs": "nixpkgs_2",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+34
View File
@@ -0,0 +1,34 @@
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
utils.url = "github:numtide/flake-utils";
foundry.url = "github:shazow/foundry.nix/monthly"; # Use monthly branch for permanent releases
};
outputs = { self, nixpkgs, utils, foundry }:
utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ foundry.overlay ];
};
in {
devShell = with pkgs; mkShell {
buildInputs = [
# From the foundry overlay
# Note: Can also be referenced without overlaying as: foundry.defaultPackage.${system}
foundry-bin
# ... any other dependencies we need
solc
];
# Decorative prompt override so we know when we're in a dev shell
shellHook = ''
export PS1="[dev] $PS1"
'';
};
});
}
+6
View File
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
+19
View File
@@ -0,0 +1,19 @@
/* // SPDX-License-Identifier: UNLICENSED */
/* pragma solidity ^0.8.13; */
/* import {Script, console} from "forge-std/Script.sol"; */
/* import {Counter} from "../src/Counter.sol"; */
/* contract CounterScript is Script { */
/* Counter public counter; */
/* function setUp() public {} */
/* function run() public { */
/* vm.startBroadcast(); */
/* counter = new Counter(); */
/* vm.stopBroadcast(); */
/* } */
/* } */
+152
View File
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/console.sol";
import {Push3Interpreter} from "../src/Push3Interpreter.sol";
/**
* @title Push3IntAdapter
* @dev An adapter contract for our Push3Interpreter that provides
* various integer-only I/O function overloads. We now create
* a SUBLIST descriptor for the entire code array, placing it
* on the EXEC stack. We ignore finalCodeStack/finalExecStack
* to avoid compiler warnings.
*/
contract Push3IntAdapter {
Push3Interpreter public interpreter;
constructor(address _interpreter) {
interpreter = Push3Interpreter(_interpreter);
}
/**
* @notice Utility function to build a single sublist descriptor
* covering the entire `code` array, offset=0, length=code.length.
*/
function _makeFullSublistDescriptor(bytes calldata code)
internal
view
returns (uint256)
{
// CodeTag.SUBLIST is typically enum = 3, but
// let's fetch it via
// (uint8)Push3Interpreter.CodeTag.SUBLIST
// or we can directly pass 3 if we know the numeric value.
// We'll do:
return interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST, // the tag
0, // offset
uint32(code.length), // length of entire code array
0 // leftover bits=0
);
}
/**
* @notice Single int input, single int output.
* 1) Build a sublist descriptor for the entire code,
* 2) Place that descriptor in the EXEC stack,
* 3) Preload `in1` on the integer stack,
* 4) Run the interpreter,
* 5) Return top of final int stack.
*/
function run1In1Out(bytes calldata code, int256 in1)
external
view
returns (int256 out1)
{
// 1) Build the sublist descriptor
uint256 fullDesc = _makeFullSublistDescriptor(code);
// 2) EXEC stack of size 1
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = fullDesc;
// No code stack
uint256[] memory initCodeStack = new uint256[](0);
// 3) Preload int stack
int256[] memory initIntStack = new int256[](1);
initIntStack[0] = in1;
// 4) Run
(, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// 5) Return top of final int stack
require(finalIntStack.length > 0, "No integer output produced");
out1 = finalIntStack[finalIntStack.length - 1];
}
/**
* @notice Two ints input, single int output
*/
function run2In1Out(bytes calldata code, int256 in1, int256 in2)
external
view
returns (int256 out1)
{
uint256 fullDesc = _makeFullSublistDescriptor(code);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = fullDesc;
uint256[] memory initCodeStack = new uint256[](0);
int256[] memory initIntStack = new int256[](2);
initIntStack[0] = in1;
initIntStack[1] = in2;
(, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
require(finalIntStack.length > 0, "No integer output");
out1 = finalIntStack[finalIntStack.length - 1];
}
/**
* @notice Single int input, two int outputs
*/
function run1In2Out(bytes calldata code, int256 in1)
external
view
returns (int256 out1, int256 out2)
{
uint256 fullDesc = _makeFullSublistDescriptor(code);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = fullDesc;
uint256[] memory initCodeStack = new uint256[](0);
int256[] memory initIntStack = new int256[](1);
initIntStack[0] = in1;
(, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
require(finalIntStack.length >= 2, "Not enough outputs");
out1 = finalIntStack[finalIntStack.length - 1];
out2 = finalIntStack[finalIntStack.length - 2];
}
/**
* @notice Zero int input, returns entire final int stack
*/
function run0InNOut(bytes calldata code)
external
view
returns (int256[] memory)
{
uint256 fullDesc = _makeFullSublistDescriptor(code);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = fullDesc;
uint256[] memory initCodeStack = new uint256[](0);
int256[] memory initIntStack = new int256[](0);
(, , int256[] memory finalIntStack) =
interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
return finalIntStack;
}
}
+360
View File
@@ -0,0 +1,360 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/console.sol";
/**
* @title Push3Interpreter
* @dev Demonstrates a minimal Push3-like interpreter that uses
* a token-based bytecode approach.
*/
contract Push3Interpreter {
// -----------------------------------------------------
// 1. ENUMS
// -----------------------------------------------------
enum CodeTag {
NO_TAG, // 0
INSTRUCTION, // 1
INT_LITERAL, // 2
SUBLIST // 3
}
enum OpCode {
NOOP, // 0
INTEGER_PLUS, // 1
INTEGER_MINUS, // 2
INTEGER_MULT, // 3
INTEGER_DUP, // 4
INTEGER_POP // 5
}
// -----------------------------------------------------
// 2. DESCRIPTOR BIT LAYOUT
// -----------------------------------------------------
// [255..248] : tag (8 bits)
// [247..216] : offset (32 bits) (for SUBLIST)
// [215..184] : length (32 bits) (for SUBLIST)
// [183..0] : leftover bits (opcode or literal data)
function getTag(uint256 desc) internal pure returns (CodeTag) {
return CodeTag(uint8(desc >> 248));
}
function getOffset(uint256 desc) internal pure returns (uint32) {
return uint32(desc >> 216);
}
function getLength(uint256 desc) internal pure returns (uint32) {
return uint32(desc >> 184);
}
function getLow184(uint256 desc) internal pure returns (uint256) {
return desc & ((1 << 184) - 1);
}
// Build a descriptor
function makeDescriptor(
CodeTag tag,
uint32 offset,
uint32 length,
uint256 low184
)
public
pure
returns (uint256)
{
require(low184 < (1 << 184), "low184 out of range");
return
(uint256(uint8(tag)) << 248) |
(uint256(offset) << 216) |
(uint256(length) << 184) |
low184;
}
function makeInstruction(OpCode op) internal pure returns (uint256) {
return makeDescriptor(CodeTag.INSTRUCTION, 0, 0, uint256(uint8(op)));
}
function getOpCode(uint256 desc) internal pure returns (OpCode) {
return OpCode(uint8(desc & 0xFF));
}
function makeIntLiteral(int32 val) internal pure returns (uint256) {
// store 32-bit integer in leftover
uint256 masked = uint256(uint32(val));
return makeDescriptor(CodeTag.INT_LITERAL, 0, 0, masked);
}
function extractInt32(uint256 desc) internal pure returns (int32) {
// read the low 32 bits
return int32(uint32(getLow184(desc)));
}
// -----------------------------------------------------
// 3. HELPER READS
// -----------------------------------------------------
/**
* @dev Read 4 bytes from `code` at `start` as uint32.
*/
function readUint32(bytes calldata code, uint32 start) internal pure returns (uint32 val) {
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.
*/
function readUint16(bytes calldata code, uint32 start) internal pure returns (uint16 val) {
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);
}
// -----------------------------------------------------
// 4. SUBLIST PARSING
// -----------------------------------------------------
/**
* Token format:
* 0x00 => NOOP
* 0x01 => INTEGER_PLUS
* 0x02 => INT_LITERAL => read next 4 bytes => int32
* 0x03 => SUBLIST => read next 2 bytes => subLen => parse that
* 0x04 => INTEGER_MINUS
*/
function parseSublist(bytes calldata code, uint32 off, uint32 len)
internal
pure
returns (uint256[] memory descs)
{
uint256[] memory temp = new uint256[](len);
uint256 count = 0;
uint32 endPos = off + len;
uint32 cur = off;
while (cur < endPos) {
if (cur >= code.length) {
break;
}
uint8 tokenType = uint8(code[cur]);
cur++;
if (tokenType == 0x00) {
// NOOP
temp[count] = makeInstruction(OpCode.NOOP);
count++;
}
else if (tokenType == 0x01) {
// INTEGER_PLUS
temp[count] = makeInstruction(OpCode.INTEGER_PLUS);
count++;
}
else if (tokenType == 0x02) {
// INT_LITERAL => 4 bytes
if (cur + 4 <= endPos && (cur + 4) <= code.length) {
uint32 raw = readUint32(code, cur);
cur += 4;
int32 val = int32(raw);
temp[count] = makeIntLiteral(val);
count++;
} else {
break;
}
}
else if (tokenType == 0x03) {
// SUBLIST => read 2 bytes => length
if (cur + 2 <= endPos && (cur + 2) <= code.length) {
uint16 subLen = readUint16(code, cur);
cur += 2;
if (cur + subLen <= endPos && (cur + subLen) <= code.length) {
// create descriptor
temp[count] = makeDescriptor(CodeTag.SUBLIST, cur, subLen, 0);
count++;
// skip over that subLen chunk
cur += subLen;
} else {
break;
}
} else {
break;
}
}
else if (tokenType == 0x04) {
// INTEGER_PLUS
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++;
}
else {
// unknown => NOOP
temp[count] = makeInstruction(OpCode.NOOP);
count++;
}
}
descs = new uint256[](count);
for (uint256 i = 0; i < count; i++) {
descs[i] = temp[i];
}
}
// -----------------------------------------------------
// 5. MAIN INTERPRETER
// -----------------------------------------------------
function runInterpreter(
bytes calldata code,
uint256[] calldata initCodeStack,
uint256[] calldata initExecStack,
int256[] calldata initIntStack
)
external
pure
returns (
uint256[] memory finalCodeStack,
uint256[] memory finalExecStack,
int256[] memory finalIntStack
)
{
// A) CODE STACK
uint256[] memory codeStack = new uint256[](initCodeStack.length + 256);
uint256 codeTop = initCodeStack.length;
for (uint256 i = 0; i < initCodeStack.length; i++) {
codeStack[i] = initCodeStack[i];
}
// B) EXEC STACK
uint256[] memory execStack = new uint256[](initExecStack.length + 256);
uint256 execTop = initExecStack.length;
for (uint256 e = 0; e < initExecStack.length; e++) {
execStack[e] = initExecStack[e];
}
// C) INT STACK
int256[] memory intStack = new int256[](initIntStack.length + 256);
uint256 intTop = initIntStack.length;
for (uint256 k = 0; k < initIntStack.length; k++) {
intStack[k] = initIntStack[k];
}
// D) MAIN LOOP
while (execTop > 0) {
uint256 topDesc = execStack[execTop - 1];
execTop--;
CodeTag tag = getTag(topDesc);
if (tag == CodeTag.INSTRUCTION) {
OpCode op = getOpCode(topDesc);
if (op == OpCode.NOOP) {
// do nothing
}
else if (op == OpCode.INTEGER_PLUS) {
// pop top 2 => sum
if (intTop >= 2) {
int256 a = intStack[intTop - 1];
int256 b = intStack[intTop - 2];
intTop -= 2;
intStack[intTop] = b + a;
intTop++;
}
}
else if (op == OpCode.INTEGER_MINUS) {
// pop top 2 => minus
if (intTop >= 2) {
int256 a = intStack[intTop - 1];
int256 b = intStack[intTop - 2];
intTop -= 2;
intStack[intTop] = b - a;
intTop++;
}
}
else if (op == OpCode.INTEGER_MULT) {
// pop top 2 => minus
if (intTop >= 2) {
int256 a = intStack[intTop - 1];
int256 b = intStack[intTop - 2];
intTop -= 2;
intStack[intTop] = b * a;
intTop++;
}
}
else if (op == OpCode.INTEGER_DUP) {
if (intTop >= 1) {
int256 a = intStack[intTop - 1];
intStack[intTop] = a;
intTop++;
}
}
else if (op == OpCode.INTEGER_POP) {
if (intTop >= 1) {
intTop -= 1;
}
}
}
else if (tag == CodeTag.INT_LITERAL) {
int32 val = extractInt32(topDesc);
intStack[intTop] = int256(val);
intTop++;
}
else if (tag == CodeTag.SUBLIST) {
// parse sublist => push in reverse
uint32 off = getOffset(topDesc);
uint32 len = getLength(topDesc);
if (off + len <= code.length) {
uint256[] memory parsed = parseSublist(code, off, len);
for (uint256 p = 0; p < parsed.length; p++) {
execStack[execTop] = parsed[parsed.length - 1 - p];
execTop++;
}
}
}
else {
// NO_TAG => do nothing
}
}
// E) RETURN
finalCodeStack = new uint256[](codeTop);
for (uint256 i = 0; i < codeTop; i++) {
finalCodeStack[i] = codeStack[i];
}
finalExecStack = new uint256[](execTop);
for (uint256 i = 0; i < execTop; i++) {
finalExecStack[i] = execStack[i];
}
finalIntStack = new int256[](intTop);
for (uint256 i = 0; i < intTop; i++) {
finalIntStack[i] = intStack[i];
}
}
}
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {Push3Interpreter} from "../src/Push3Interpreter.sol";
import {Push3IntAdapter} from "../src/Push3IntAdapter.sol";
/**
* @title Push3IntAdapterTest
* @dev Foundry test for the Push3IntAdapter contract,
* which composes with the Push3Interpreter.
*/
contract Push3IntAdapterTest is Test {
Push3Interpreter public interpreter;
Push3IntAdapter public adapter;
function setUp() public {
// 1) Deploy the interpreter
interpreter = new Push3Interpreter();
// 2) Deploy the adapter, referencing the interpreter address
adapter = new Push3IntAdapter(address(interpreter));
}
/**
* @notice Test run1In1Out with a minimal code array.
* For example, we might do a program that just leaves its input unchanged
* (like a NOOP).
*/
function test_run1In1Out_noop() public view {
bytes memory code = hex"03000100";
int256 inputVal = 123;
int256 result = adapter.run1In1Out(code, inputVal);
// We expect the code does nothing => output == input
assertEq(result, inputVal, "Expected same value out");
}
/**
* @notice Test run2In1Out. We'll push 5, 7, do an op that leaves 12.
*/
function test_run2In1Out_plus() public view {
bytes memory code = hex"03000101";
int256 in1 = 5;
int256 in2 = 7;
int256 result = adapter.run2In1Out(code, in1, in2);
assertEq(result, 12, "Expected 5 + 7 = 12");
}
/**
* @notice Test run1In2Out. Push a constant to a stack with 1.
* For now, we do a placeholder code.
*/
function test_run1In2Out() public view {
bytes memory code = hex"0300050200000003";
int256 inVal = 42;
(int256 top, int256 secondTop) = adapter.run1In2Out(code, inVal);
assertEq(top, 3, "top stack val mismatch");
assertEq(secondTop, 42, "second top stack val mismatch");
}
/**
* @notice Test run0InNOut.
* We'll feed a code that pushes some constants, and see the final stack.
*/
function test_run0InNOut() public view {
bytes memory code = hex"03000A02000000030200000005";
int256[] memory stackResult = adapter.run0InNOut(code);
assertEq(stackResult.length, 2, "Expected 2 final stack items");
assertEq(stackResult[0], 3, "Item 0 mismatch");
assertEq(stackResult[1], 5, "Item 1 mismatch");
}
}
+376
View File
@@ -0,0 +1,376 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import "forge-std/console.sol";
import {Push3Interpreter} from "../src/Push3Interpreter.sol";
/**
* @title Push3InterpreterTest
* @dev Foundry test contract for the updated Push3Interpreter
*/
contract Push3InterpreterTest is Test {
Push3Interpreter public interpreter;
function setUp() public {
interpreter = new Push3Interpreter();
}
/**
* @notice Test a small program that:
* 1) Pushes literal 10 (int32)
* 2) Pushes literal 32 (int32)
* 3) Calls INTEGER_PLUS
*
* We'll store everything in a single SUBLIST:
* 0x03 => SUBLIST
* 0x00 0x0B => subLen=11
* Then the next 11 bytes are:
* 0x02 + (00 00 00 0A) => INT_LITERAL(10)
* 0x02 + (00 00 00 20) => INT_LITERAL(32)
* 0x01 => INTEGER_PLUS
*
* So total = 1 + 2 + (1+4 + 1+4 + 1) = 14 bytes
* => 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01
*/
function test_LiteralsAndPlus() public view {
// The final code array (14 bytes):
// [0] = 0x03
// [1..2] = 0x000B
// [3] = 0x02
// [4..7] = 0x0000000A => 10
// [8] = 0x02
// [9..12] = 0x00000020 => 32
// [13] = 0x01 => plus
bytes memory code = hex"03000B020000000A020000002001";
// Or fully concatenated:
// 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01
// We'll parse offset=0, length=14
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
14, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
// Run it
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// Should produce finalIntStack = [42]
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
assertEq(finalIntStack[0], 42, "Should be 10+32=42");
}
/**
* @notice Test pushing literal 10, literal 3, then INTEGER_MINUS => (10 - 3) = 7
*
* The plan:
* 0x03 => SUBLIST token
* 0x00 0x0B => subLen=11
*
* Then 11 bytes:
* 0x02 + (00 00 00 0A) => INT_LITERAL(10)
* 0x02 + (00 00 00 03) => INT_LITERAL(3)
* 0x04 => INTEGER_MINUS (assuming 0x04 is assigned to minus)
*/
function test_Minus() public view {
// We'll produce 14 total bytes, same structure as the plus example
// Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x04 => minus]
bytes memory code = hex"03000B020000000A020000000304";
// Breaking it down:
// 0x03 => SUBLIST
// 0x00 0x0B => length=11
// 0x02 0000000A => INT_LITERAL(10)
// 0x02 00000003 => INT_LITERAL(3)
// 0x04 => INTEGER_MINUS
// We'll parse offset=0, length=14
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
14, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
// Run
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// Check final result => should be [7]
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
assertEq(finalIntStack[0], 7, "Should be 10 - 3 = 7");
}
/**
* @notice Test pushing literal 10, literal 3, then INTEGER_MULT => (10 * 3) = 30
*
* The plan:
* 0x03 => SUBLIST token
* 0x00 0x0B => subLen=11
*
* Then 11 bytes:
* 0x02 + (00 00 00 0A) => INT_LITERAL(10)
* 0x02 + (00 00 00 03) => INT_LITERAL(3)
* 0x05 => INTEGER_MULT
*/
function test_Mult() public view {
// We'll produce 14 total bytes, same structure as the plus example
// Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x05 => mul]
bytes memory code = hex"03000B020000000A020000000305";
// Breaking it down:
// 0x03 => SUBLIST
// 0x00 0x0B => length=11
// 0x02 0000000A => INT_LITERAL(10)
// 0x02 00000003 => INT_LITERAL(3)
// 0x05 => INTEGER_MUL
// We'll parse offset=0, length=14
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
14, // length
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
// Run
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// Check final result => should be [30]
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
assertEq(finalIntStack[0], 30, "Should be 10 * 3 = 30");
}
function test_Dup() public view {
bytes memory code = hex"030006020000000A06";
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0,
9,
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
// Run
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
assertEq(finalIntStack.length, 2, "finalIntStack should have length 1");
assertEq(finalIntStack[0], finalIntStack[1], "The top 2 values on the stack should be equal.");
}
function test_Pop() public view {
bytes memory code = hex"030006020000000A07";
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0,
9,
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
// Run
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
assertEq(finalIntStack.length, 0, "finalIntStack should be empty");
}
/**
* @notice Test with an unknown token (0xF0).
* We expect the parser to treat it as "unknown => NOOP"
* and produce a NOOP instruction descriptor.
* We'll confirm finalIntStack remains empty (or unchanged).
*/
function test_UnknownToken() public view {
// We'll create a 5-byte code:
// 0x03 => SUBLIST
// 0x00 0x01 => length=1
// 0xF0 => unknown token
bytes memory code = hex"030001F0";
// We'll parse offset=0, length=4
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0, // offset
4, // length
0
);
// init stacks
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// finalIntStack should remain empty => no literal was pushed
assertEq(finalIntStack.length, 0, "Expected finalIntStack to remain empty with unknown token => NOOP");
}
/**
* @notice Test a partial literal: we claim to have 4 bytes, but the code array ends early.
* This should trigger the `else { break; }` branch in parseSublist
* when trying to read 4 bytes for the INT_LITERAL.
*/
function test_OutOfRangeLiteral() public view {
// 0x03 => SUBLIST
// 0x00 0x04 => length=4
// 0x02 => INT_LITERAL token
// Then we have only 1 byte left instead of 4 => insufficient
//
// total = 1 + 2 + 1 + 1 = 5 bytes
// The parser tries to read 4 bytes after seeing 0x04, fails, hits break.
bytes memory code = hex"03000402FF";
// parse offset=0, length=5
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0,
5,
0
);
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// 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");
}
/**
* @notice Test "not enough int args" scenario:
* We'll push only 1 literal, then do INTEGER_PLUS which needs 2 integers on the stack.
* In the main loop, it sees intTop < 2 => it does nothing.
*/
function test_NotEnoughIntArgs() public view {
bytes memory code = hex"030006020000000305";
// Breaking it down:
// 0x03 => SUBLIST
// 0x00 0x06 => length=6
// 0x02 00000003 => INT_LITERAL(3)
// 0x05 => INTEGER_MUL
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0,
9,
0
);
// set up stacks
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// We expect finalIntStack => [5],
// because the PLUS instruction sees only 1 int => does nothing => 5 remains
assertEq(finalIntStack.length, 1, "Should have 1 int left");
assertEq(finalIntStack[0], 3, "Expected stack top=3 after insufficient-args plus");
}
/**
* @notice Test sublist length overflow:
* subLen claims bigger than the code array. Should trigger else { break; }
* in the sublist parsing for that sublist.
*/
function test_SublistLengthOverflow() public view {
bytes memory code = hex"050010FFFFFF";
uint256 sublistDesc = interpreter.makeDescriptor(
Push3Interpreter.CodeTag.SUBLIST,
0,
6,
0
);
// init
uint256[] memory initCodeStack = new uint256[](0);
uint256[] memory initExecStack = new uint256[](1);
initExecStack[0] = sublistDesc;
int256[] memory initIntStack = new int256[](0);
(
,
,
int256[] memory finalIntStack
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
// Expect no items pushed => finalIntStack empty
assertEq(finalIntStack.length, 0, "Expected empty stack if sublist length is out of range => parse break");
}
}
+163
View File
@@ -0,0 +1,163 @@
# Home of Dumplings / \$DPL Tokenomics
## 1. Overview
The Home of Dumplings protocol deploys AI agents that solve optimization problems for DeFi users and protocols, addressing tasks such as liquidity management and parameter tuning. The ecosystem operates through **Tasks** hosted on-chain and populations of solution programs evolved off-chain by the Optimizer Pool.
**\$DPL** is the native token powering this ecosystem. Its primary roles include:
1. **Payment**: Clients pay invocation or subscription fees in \$DPL.
2. **Staking and Governance**: Token holders stake \$DPL to secure the protocol, join the optimizer pool or participate in governance.
3. **Rewards**: Optimizer Pool nodes earn \$DPL for validated contributions to the evolution of programs.
---
## 2. Token Supply and Distribution
### Fixed Supply
- **Total Supply**: \$DPL has a fixed supply (e.g., 100M tokens), minted at genesis.
- **No Inflation**: No further token minting or emission beyond the initial allocation.
### Allocation of Supply
1. **Uniswap V3 Liquidity Pool**
- All of the supply is allocated to a Uniswap V3 liquidity pool over a wide price range.
- Multiple price ranges hold extra liquidity to ensure stable price discovery.
2. **Team**
- A vesting contract purchases a small portion (e.g., 10-15%) of the cheapest tokens from the pool right after deployment and allocates them to the team.
3. **Treasury**
- An NFT minting contract purchases a small portion (e.g., 5-10%) right after the vesting contract before other buyers come in. The tokens are packaged into nice-looking NFTs.
- These NFTs are sold on marketplaces like OpenSea, redeemable for tokens. The proceeds are transferred to the team multisig, forming the treasury.
---
## 3. Revenue Model
### Fee Structure
1. **Invocation Fees**
- Charged for AI agents evolved with synthetic data.
- Paid in \$DPL or other currencies auto-converted into \$DPL and routed to the protocol reward pools.
2. **Subscription Fees**
- Charged for AI agents evolved with synthetic data initially, but are continuously improved by backtesting with real data.
- Periodic payment in \$DPL or other currencies auto-converted into \$DPL and routed to the protocol reward pools.
### Reward Allocation
- A portion of the fees collected funds rewards for the Optimizer Pool nodes that contribute validated solutions to Tasks.
- Governance determines the exact split between:
- Optimizer Pool rewards.
- Treasury for operational funding.
- Potential staking rewards.
---
## 4. Staking
### Types of Stakers
1. **Council Node Stakers**
- Nodes in the Optimizer Pool stake \$DPL to become eligible for selection to the Council.
- Council members validate and vote on improved solutions to Tasks.
- **Slashing Mechanism**: If a Council member votes against the majority decision, they are penalized by slashing a portion of their staked tokens.
2. **Investor Stakers**
- Investors stake \$DPL to earn a share of the protocol’s revenue.
- Revenue is comprised of invocation and subscription fees, a part is distributed as staking rewards.
### Risk Backstop
- Stakers collectively backstop potential risks associated with the AI agents.
- If an AI agent malfunctions (e.g., outputs suboptimal or incorrect decisions due to flaws in the algorithm), affected users may be compensated via the staking pool, reducing the system’s reputational and financial risk.
- Governance defines the conditions for such compensations, ensuring fairness and transparency.
---
## 5. Governance
Voting weight is proportional to the amount of \$DPL staked.
### Governance Mechanics
1. **Voting Rights**
- All stakers can participate in governance, regardless of whether they are nodes or investors.
- Proposals focus on:
- Adjusting fee parameters (e.g., invocation and subscription fees).
- Allocating Optimizer Pool rewards.
- Defining compensation rules for Risk Backstop events.
2. **Council Randomness**
- Optimizer Pool nodes with staked \$DPL can be selected for the Council via a verifiable random selection process.
- The Council validates and votes on improved solutions to Tasks.
---
## 6. NFT-Based Early Bird Mechanism
1. **Purpose**
- Raise initial funding and incentivize early supporters.
2. **Mechanics**
- NFTs represent the right to claim a specified quantity of \$DPL at a discounted rate.
- Sold on marketplaces like OpenSea.
3. **Redemption**
- Upon redemption, the NFT is burned, and the holder receives their allocated \$DPL.
- Vesting or time locks may apply to align with protocol growth.
---
## 7. Fee Usage and Distribution
- **Optimizer Pool Rewards**: A fraction of collected fees funds rewards for Optimizer Pool nodes.
- **Treasury**: Supports operations, development, and marketing initiatives.
- **Stakers**: Receive a share of fees as staking rewards, subject to governance decisions.
---
## 8. Launch and Go-to-Market Strategy
### Initial Liquidity Deployment
- Deploy a majority of \$DPL supply into Uniswap V3 pools.
- Use multiple concentrated price ranges to establish liquidity depth and facilitate market stability.
### Early Bird NFT Sale
- Launch a curated NFT collection for discounted \$DPL rights.
- Drive initial funding and engage early adopters.
### Target Customers
- Position DeFi protocols as key customers for SoLUSH’s optimization services.
- Focus marketing on showcasing cost-saving and performance-enhancing use cases.
---
## 9. Conclusion
The \$DPL tokenomics framework is designed to:
- Provide immediate utility through payment and staking mechanisms.
- Incentivize Optimizer Pool participation via rewards for validated solutions.
- Ensure sustainable growth through fixed supply, governance, and clear fee allocation.
This structure ensures the long-term viability and scalability of the SoLUSH ecosystem, while fostering community involvement and alignment with protocol goals.