diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..53811172
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @b-j-roberts
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..902a2cce
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+
+
+- [ ] issue #
+- [ ] follows contribution [guide](https://github.com/keep-starknet-strange/shinigami/blob/main/CONTRIBUTING.md)
+- [ ] code change includes tests
+
+
diff --git a/.github/linter/base_style.rb b/.github/linter/base_style.rb
new file mode 100644
index 00000000..8d981050
--- /dev/null
+++ b/.github/linter/base_style.rb
@@ -0,0 +1,4 @@
+all
+# lame rules
+exclude_rule 'MD002'
+exclude_rule 'MD041'
diff --git a/.github/linter/readme_style.rb b/.github/linter/readme_style.rb
new file mode 100644
index 00000000..02e6b483
--- /dev/null
+++ b/.github/linter/readme_style.rb
@@ -0,0 +1,12 @@
+all
+# allow inline HTML for README fmt
+exclude_rule 'MD033'
+# badges trigger rule
+exclude_rule 'MD034'
+# README img serves as 'First Header'
+exclude_rule 'MD002'
+exclude_rule 'MD041'
+# TODO: disable/enable not working for all-contribs
+exclude_rule 'MD013'
+# Allow no endline at the end
+exclude_rule 'MD047'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..a46b0608
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,18 @@
+name: build
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ pull_request:
+permissions: read-all
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: asdf-vm/actions/install@v3
+ - run: scarb fmt --check
+ - run: scarb build
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
new file mode 100644
index 00000000..1ceaa74f
--- /dev/null
+++ b/.github/workflows/check.yml
@@ -0,0 +1,27 @@
+name: check
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ pull_request:
+permissions: read-all
+
+jobs:
+ markdown:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: |
+ sudo gem install mdl
+ mdl -s .github/linter/readme_style.rb README.md
+ mdl -s .github/linter/base_style.rb .github
+ mdl -s .github/linter/book_style.rb book
+
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: asdf-vm/actions/install@v3
+ - run: scarb test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..aa071c5b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+# Sys files
+**/.DS_Store
+
+# Scarb and Starknet Foundry
+**/target
+**/.snfoundry_cache/
+
+# Starkli
+**/account.json
+**/keystore.json
+
+# All Contributors CLI
+/package.json
+/yarn.lock
+node_modules/
+
+# Development
+**/TODO
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..305a4542
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,65 @@
+## 🛠️ Contributing to shinigami 🛠️
+
+Welcome, contributing to `shinigami` is easy!
+
+1. Submit or comment your intent on an issue
+1. We will try to respond quickly
+1. Fork this repo
+1. Submit your PR against `main`
+1. Address PR Reviews
+
+### Issue
+
+Project tracking is done via GitHub [issues](https://github.com/keep-starknet-strange/shinigami/issues)
+First look at open issues to see if your request is already submitted.
+If it is comment on the issue requesting assignment, if not open an issue.
+
+We use 3 issue labels for development:
+
+- `feat` -> suggest new feature
+- `bug` -> create a reproducible bug report
+- `dev` -> non-functional repository changes
+
+These labels are used as prefixes as follows for `issue`, `branch name`, `pr title`:
+
+- `[feat]` -> `feat/{issue #}-{issue name}` -> `feat:`
+- `[bug]` -> `bug/{issue #}-{issue name}` -> `bug:`
+- `[dev]` -> `dev/{issue #}-{issue name}` -> `dev:`
+
+#### TODO
+
+If your PR includes a `TODO` comment please open an issue and comment the relevant
+code with `TODO(#ISSUE_NUM):`.
+
+### Submit PR
+
+Ensure your code is well formatted, well tested and well documented. A core contributor
+will review your work. Address changes, ensure ci passes,
+and voilà you're a `shinigami` contributor.
+
+Markdown [linter](https://github.com/markdownlint/markdownlint?tab=readme-ov-file#markdown-lint-tool):
+
+```bash
+mdl -s .github/linter/readme_style.rb README.md
+```
+
+Scarb linter:
+
+```bash
+scarb fmt
+```
+
+### Additional Resources
+
+- [Cairo Book](https://book.cairo-lang.org/)
+- [Starknet Book](https://book.starknet.io/)
+- [Starknet Foundry Book](https://foundry-rs.github.io/starknet-foundry/)
+- [Starknet By Example](https://starknet-by-example.voyager.online/)
+- [Starkli Book](https://book.starkli.rs/)
+- [Bitcoin Wiki](https://en.bitcoin.it/wiki/Script)
+- [Golang Bitcoin Implementation](https://github.com/btcsuite/btcd)
+- [Syncing a Fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork)
+
+##
+
+Thank you for your contribution!
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..ffa320ad
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+
+

+
+ ***Bitcoin Script VM in Cairo***
+
+ [](https://github.com/keep-starknet-strange/shinigami/actions/workflows/check.yml)
+ [](https://github.com/keep-starknet-strange/shinigami/actions/workflows/build.yml)
+
+ [](https://github.com/keep-starknet-strange)
+
+
+
+## Overview
+
+`shinigami` is a library enabling Bitcoin Script VM execution in Cairo, thus allowing the generation of STARK proofs of generic Bitcoin Script computation.
+
+Key features :
+ - Bitcoin script interpretation and execution
+ - Easily configurable VM ( enable different op-codes )
+
+### Running
+
+```bash
+scarb cairo-run --available-gas=200000000
+```
+
+This will run the provided Bitcoin Script in Cairo.
+
+### Building
+
+```bash
+scarb build
+```
+
+This will compile all the components.
+
+### Testing
+
+```bash
+scarb test
+```
+
+This will run the test-suite for all op-codes, integration, and test Bitcoin Scripts.
+
+## References
+
+- [Bitcoin Script Wiki](https://en.bitcoin.it/wiki/Script)
+- [Telegram]()
+- [OnlyDust]()
+
+## Contributors ✨
+
+Thanks goes to these wonderful people. Follow the [contributors guide](https://github.com/keep-starknet-strange/art-peace/blob/main/CONTRIBUTING.md) if you'd like to take part.
diff --git a/Scarb.lock b/Scarb.lock
new file mode 100644
index 00000000..42bea520
--- /dev/null
+++ b/Scarb.lock
@@ -0,0 +1,6 @@
+# Code generated by scarb DO NOT EDIT.
+version = 1
+
+[[package]]
+name = "shinigami"
+version = "0.1.0"
diff --git a/Scarb.toml b/Scarb.toml
new file mode 100644
index 00000000..f2f161c9
--- /dev/null
+++ b/Scarb.toml
@@ -0,0 +1,8 @@
+[package]
+name = "shinigami"
+version = "0.1.0"
+edition = "2023_11"
+
+# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html
+
+[dependencies]
diff --git a/resources/how-to-add-an-opcode.md b/resources/how-to-add-an-opcode.md
new file mode 100644
index 00000000..466124e1
--- /dev/null
+++ b/resources/how-to-add-an-opcode.md
@@ -0,0 +1,13 @@
+## How to add an opcode to "Shinigami"
+
+1. Understand how the opcode works by looking at documentation:
+ - [Bitcoin Script Wiki](https://en.bitcoin.it/wiki/Script)
+ - [Reference implementation in BTCD](https://github.com/btcsuite/btcd/blob/b161cd6a199b4e35acec66afc5aad221f05fe1e3/txscript/opcode.go#L312)
+ - In BTCD find the function that matches the last element in `opcodeArray` for the specified opcode, that will be the reference implementation.
+1. Add the Opcode to `src/opcodes/opcodes.cairo`
+ - Add the Opcode byte const like `pub const OP_ADD: u8 = 147;`
+ - Create the function implementing the opcode like `fn opcode_add(ref engine: Engine) {`
+ - Add the function to the `execute` method like `147 => opcode_add(ref engine),`
+1. Add the Opcode to the compiler dict at `src/compiler.cairo` like `opcodes.insert('OP_ADD', Opcode::OP_ADD);`.
+1. Create a test for your opcode at `src/opcodes/tests/test_opcodes.cairo` and ensure all the logic works as expected.
+1. Create a PR, ensure CI passes, and await review.
diff --git a/resources/shinigami-logo/shinigami.png b/resources/shinigami-logo/shinigami.png
new file mode 100644
index 00000000..c2e77f71
Binary files /dev/null and b/resources/shinigami-logo/shinigami.png differ
diff --git a/src/compiler.cairo b/src/compiler.cairo
new file mode 100644
index 00000000..9cff19a8
--- /dev/null
+++ b/src/compiler.cairo
@@ -0,0 +1,73 @@
+use shinigami::opcodes::Opcode;
+
+// Compiler that takes a Bitcoin Script program and compiles it into a bytecode
+#[derive(Destruct)]
+pub struct Compiler {
+ // Dict containing opcode names to their bytecode representation
+ opcodes: Felt252Dict
+}
+
+pub trait CompilerTrait {
+ // Create a compiler, initializing the opcode dict
+ fn new() -> Compiler;
+ // Compiles a program like "OP_1 OP_2 OP_ADD" into a bytecode run by the Engine.
+ fn compile(self: Compiler, script: ByteArray) -> ByteArray;
+}
+
+pub impl CompilerTraitImpl of CompilerTrait {
+ fn new() -> Compiler {
+ let mut opcodes = Default::default();
+ // Add the opcodes to the dict
+ opcodes.insert('OP_0', Opcode::OP_0);
+ opcodes.insert('OP_1', Opcode::OP_1);
+ opcodes.insert('OP_ADD', Opcode::OP_ADD);
+ Compiler { opcodes }
+ }
+
+ // TODO: Why self is mutable?
+ fn compile(mut self: Compiler, script: ByteArray) -> ByteArray {
+ let mut bytecode = "";
+ let seperator = ' ';
+ let byte_shift = 256;
+ let max_word_size = 31;
+ let mut current: felt252 = '';
+ let mut i = 0;
+ let mut word_len = 0;
+ let mut current_word: felt252 = '';
+ while i < script.len() {
+ let char = script[i].into();
+ if char == seperator {
+ let opcode = self.opcodes.get(current);
+ current_word = current_word * byte_shift + opcode.into();
+ word_len += 1;
+ if word_len >= max_word_size {
+ // Add the current word to the bytecode representation
+ bytecode.append_word(current_word, max_word_size);
+ word_len = 0;
+ }
+ current = '';
+ } else {
+ // Add the char to the bytecode representation
+ current = current * byte_shift + char;
+ }
+ i += 1;
+ };
+ // Handle the last opcode
+ if current != '' {
+ let opcode = self.opcodes.get(current);
+ current_word = current_word * byte_shift + opcode.into();
+ word_len += 1;
+ if word_len >= max_word_size {
+ // Add the current word to the bytecode representation
+ bytecode.append_word(current_word, max_word_size);
+ word_len = 0;
+ }
+ }
+ if word_len > 0 {
+ // Add the current word to the bytecode representation
+ bytecode.append_word(current_word, word_len);
+ }
+
+ bytecode
+ }
+}
diff --git a/src/engine.cairo b/src/engine.cairo
new file mode 100644
index 00000000..c96d5810
--- /dev/null
+++ b/src/engine.cairo
@@ -0,0 +1,94 @@
+use shinigami::stack::{ScriptStack, ScriptStackImpl};
+use shinigami::opcodes::opcodes::Opcode::execute;
+
+// Represents the VM that executes Bitcoin scripts
+#[derive(Destruct)]
+pub struct Engine {
+ // The script to execute
+ script: @ByteArray,
+ // Program counter within the current script
+ opcode_idx: usize,
+ // Primary data stack
+ pub dstack: ScriptStack,
+ // Alternate data stack
+ pub astack: ScriptStack,
+ // TODO
+ // ...
+}
+
+pub trait EngineTrait {
+ // Create a new Engine with the given script
+ fn new(script: ByteArray) -> Engine;
+ fn get_dstack(ref self: Engine) -> Span;
+ fn get_astack(ref self: Engine) -> Span;
+ // Executes the next instruction in the script
+ fn step(ref self: Engine) -> bool; // TODO return type w/ error handling
+ // Executes the entire script and returns top of stack or error if script fails
+ fn execute(ref self: Engine) -> Result;
+}
+
+pub impl EngineTraitImpl of EngineTrait {
+ fn new(script: ByteArray) -> Engine {
+ Engine {
+ script: @script,
+ opcode_idx: 0,
+ dstack: ScriptStackImpl::new(),
+ astack: ScriptStackImpl::new(),
+ }
+ }
+
+ fn get_dstack(ref self: Engine) -> Span {
+ return self.dstack.stack_to_span();
+ }
+
+ fn get_astack(ref self: Engine) -> Span {
+ return self.astack.stack_to_span();
+ }
+
+ fn step(ref self: Engine) -> bool {
+ if self.opcode_idx >= self.script.len() {
+ return false;
+ }
+
+ let opcode = self.script[self.opcode_idx];
+ execute(opcode, ref self);
+ self.opcode_idx += 1;
+ return true;
+ }
+
+ fn execute(ref self: Engine) -> Result {
+ while self.opcode_idx < self.script.len() {
+ let opcode = self.script[self.opcode_idx];
+ execute(opcode, ref self);
+ self.opcode_idx += 1;
+ // TODO: remove debug
+ // self.dstack.print();
+ // println!("==================");
+ };
+
+ // TODO: CheckErrorCondition
+ if self.dstack.len() < 1 {
+ return Result::Err('Stack empty at end of script');
+ } else if self.dstack.len() > 1 {
+ return Result::Err('Stack must contain item');
+ } else {
+ // TODO: pop bool
+ let top_stack = self.dstack.pop_byte_array();
+ let ret_val = top_stack.clone();
+ let mut is_ok = false;
+ let mut i = 0;
+ while i < top_stack.len() {
+ if top_stack[i] != 0 {
+ is_ok = true;
+ break;
+ }
+ i += 1;
+ };
+ if is_ok {
+ return Result::Ok(ret_val);
+ } else {
+ return Result::Err('Script failed');
+ }
+ }
+ }
+}
diff --git a/src/lib.cairo b/src/lib.cairo
new file mode 100644
index 00000000..04a4e86b
--- /dev/null
+++ b/src/lib.cairo
@@ -0,0 +1,13 @@
+pub mod compiler;
+pub mod opcodes {
+ pub mod opcodes;
+ mod tests {
+ #[cfg(test)]
+ mod test_opcodes;
+ }
+ pub(crate) use opcodes::Opcode;
+}
+pub mod engine;
+pub mod stack;
+
+mod main;
diff --git a/src/main.cairo b/src/main.cairo
new file mode 100644
index 00000000..3fea314b
--- /dev/null
+++ b/src/main.cairo
@@ -0,0 +1,16 @@
+use shinigami::compiler::CompilerTraitImpl;
+use shinigami::engine::EngineTraitImpl;
+
+fn main() {
+ let program = "OP_0 OP_1 OP_ADD";
+ println!("Running Bitcoin Script: {}", program);
+ let mut compiler = CompilerTraitImpl::new();
+ let bytecode = compiler.compile(program);
+ let mut engine = EngineTraitImpl::new(bytecode);
+ let res = engine.execute();
+ if res.is_ok() {
+ println!("Execution successful");
+ } else {
+ println!("Execution failed");
+ }
+}
diff --git a/src/opcodes/opcodes.cairo b/src/opcodes/opcodes.cairo
new file mode 100644
index 00000000..94383b58
--- /dev/null
+++ b/src/opcodes/opcodes.cairo
@@ -0,0 +1,180 @@
+pub mod Opcode {
+ pub const OP_0: u8 = 0;
+ pub const OP_1: u8 = 81;
+ pub const OP_ADD: u8 = 147;
+
+ use shinigami::engine::Engine;
+ use shinigami::stack::ScriptStackTrait;
+ pub fn execute(opcode: u8, ref engine: Engine) {
+ match opcode {
+ 0 => opcode_false(ref engine),
+ 1 => not_implemented(ref engine),
+ 2 => not_implemented(ref engine),
+ 3 => not_implemented(ref engine),
+ 4 => not_implemented(ref engine),
+ 5 => not_implemented(ref engine),
+ 6 => not_implemented(ref engine),
+ 7 => not_implemented(ref engine),
+ 8 => not_implemented(ref engine),
+ 9 => not_implemented(ref engine),
+ 10 => not_implemented(ref engine),
+ 11 => not_implemented(ref engine),
+ 12 => not_implemented(ref engine),
+ 13 => not_implemented(ref engine),
+ 14 => not_implemented(ref engine),
+ 15 => not_implemented(ref engine),
+ 16 => not_implemented(ref engine),
+ 17 => not_implemented(ref engine),
+ 18 => not_implemented(ref engine),
+ 19 => not_implemented(ref engine),
+ 20 => not_implemented(ref engine),
+ 21 => not_implemented(ref engine),
+ 22 => not_implemented(ref engine),
+ 23 => not_implemented(ref engine),
+ 24 => not_implemented(ref engine),
+ 25 => not_implemented(ref engine),
+ 26 => not_implemented(ref engine),
+ 27 => not_implemented(ref engine),
+ 28 => not_implemented(ref engine),
+ 29 => not_implemented(ref engine),
+ 30 => not_implemented(ref engine),
+ 31 => not_implemented(ref engine),
+ 32 => not_implemented(ref engine),
+ 33 => not_implemented(ref engine),
+ 34 => not_implemented(ref engine),
+ 35 => not_implemented(ref engine),
+ 36 => not_implemented(ref engine),
+ 37 => not_implemented(ref engine),
+ 38 => not_implemented(ref engine),
+ 39 => not_implemented(ref engine),
+ 40 => not_implemented(ref engine),
+ 41 => not_implemented(ref engine),
+ 42 => not_implemented(ref engine),
+ 43 => not_implemented(ref engine),
+ 44 => not_implemented(ref engine),
+ 45 => not_implemented(ref engine),
+ 46 => not_implemented(ref engine),
+ 47 => not_implemented(ref engine),
+ 48 => not_implemented(ref engine),
+ 49 => not_implemented(ref engine),
+ 50 => not_implemented(ref engine),
+ 51 => not_implemented(ref engine),
+ 52 => not_implemented(ref engine),
+ 53 => not_implemented(ref engine),
+ 54 => not_implemented(ref engine),
+ 55 => not_implemented(ref engine),
+ 56 => not_implemented(ref engine),
+ 57 => not_implemented(ref engine),
+ 58 => not_implemented(ref engine),
+ 59 => not_implemented(ref engine),
+ 60 => not_implemented(ref engine),
+ 61 => not_implemented(ref engine),
+ 62 => not_implemented(ref engine),
+ 63 => not_implemented(ref engine),
+ 64 => not_implemented(ref engine),
+ 65 => not_implemented(ref engine),
+ 66 => not_implemented(ref engine),
+ 67 => not_implemented(ref engine),
+ 68 => not_implemented(ref engine),
+ 69 => not_implemented(ref engine),
+ 70 => not_implemented(ref engine),
+ 71 => not_implemented(ref engine),
+ 72 => not_implemented(ref engine),
+ 73 => not_implemented(ref engine),
+ 74 => not_implemented(ref engine),
+ 75 => not_implemented(ref engine),
+ 76 => not_implemented(ref engine),
+ 77 => not_implemented(ref engine),
+ 78 => not_implemented(ref engine),
+ 79 => not_implemented(ref engine),
+ 80 => not_implemented(ref engine),
+ 81 => opcode_n(1, ref engine),
+ 82 => not_implemented(ref engine),
+ 83 => not_implemented(ref engine),
+ 84 => not_implemented(ref engine),
+ 85 => not_implemented(ref engine),
+ 86 => not_implemented(ref engine),
+ 87 => not_implemented(ref engine),
+ 88 => not_implemented(ref engine),
+ 89 => not_implemented(ref engine),
+ 90 => not_implemented(ref engine),
+ 91 => not_implemented(ref engine),
+ 92 => not_implemented(ref engine),
+ 93 => not_implemented(ref engine),
+ 94 => not_implemented(ref engine),
+ 95 => not_implemented(ref engine),
+ 96 => not_implemented(ref engine),
+ 97 => not_implemented(ref engine),
+ 98 => not_implemented(ref engine),
+ 99 => not_implemented(ref engine),
+ 100 => not_implemented(ref engine),
+ 101 => not_implemented(ref engine),
+ 102 => not_implemented(ref engine),
+ 103 => not_implemented(ref engine),
+ 104 => not_implemented(ref engine),
+ 105 => not_implemented(ref engine),
+ 106 => not_implemented(ref engine),
+ 107 => not_implemented(ref engine),
+ 108 => not_implemented(ref engine),
+ 109 => not_implemented(ref engine),
+ 110 => not_implemented(ref engine),
+ 111 => not_implemented(ref engine),
+ 112 => not_implemented(ref engine),
+ 113 => not_implemented(ref engine),
+ 114 => not_implemented(ref engine),
+ 115 => not_implemented(ref engine),
+ 116 => not_implemented(ref engine),
+ 117 => not_implemented(ref engine),
+ 118 => not_implemented(ref engine),
+ 119 => not_implemented(ref engine),
+ 120 => not_implemented(ref engine),
+ 121 => not_implemented(ref engine),
+ 122 => not_implemented(ref engine),
+ 123 => not_implemented(ref engine),
+ 124 => not_implemented(ref engine),
+ 125 => not_implemented(ref engine),
+ 126 => not_implemented(ref engine),
+ 127 => not_implemented(ref engine),
+ 128 => not_implemented(ref engine),
+ 129 => not_implemented(ref engine),
+ 130 => not_implemented(ref engine),
+ 131 => not_implemented(ref engine),
+ 132 => not_implemented(ref engine),
+ 133 => not_implemented(ref engine),
+ 134 => not_implemented(ref engine),
+ 135 => not_implemented(ref engine),
+ 136 => not_implemented(ref engine),
+ 137 => not_implemented(ref engine),
+ 138 => not_implemented(ref engine),
+ 139 => not_implemented(ref engine),
+ 140 => not_implemented(ref engine),
+ 141 => not_implemented(ref engine),
+ 142 => not_implemented(ref engine),
+ 143 => not_implemented(ref engine),
+ 144 => not_implemented(ref engine),
+ 145 => not_implemented(ref engine),
+ 146 => not_implemented(ref engine),
+ 147 => opcode_add(ref engine),
+ _ => not_implemented(ref engine)
+ }
+ }
+
+ fn opcode_false(ref engine: Engine) {
+ engine.dstack.push_byte_array("");
+ }
+
+ fn opcode_n(n: i64, ref engine: Engine) {
+ engine.dstack.push_int(n);
+ }
+
+ fn opcode_add(ref engine: Engine) {
+ // TODO: Error handling
+ let a = engine.dstack.pop_int();
+ let b = engine.dstack.pop_int();
+ engine.dstack.push_int(a + b);
+ }
+
+ fn not_implemented(ref engine: Engine) {
+ panic!("Opcode not implemented");
+ }
+}
diff --git a/src/opcodes/tests/test_opcodes.cairo b/src/opcodes/tests/test_opcodes.cairo
new file mode 100644
index 00000000..74df5b84
--- /dev/null
+++ b/src/opcodes/tests/test_opcodes.cairo
@@ -0,0 +1,53 @@
+use shinigami::compiler::CompilerTraitImpl;
+use shinigami::engine::EngineTraitImpl;
+
+#[test]
+fn test_op_0() {
+ let program = "OP_0";
+ let mut compiler = CompilerTraitImpl::new();
+ let bytecode = compiler.compile(program);
+ let mut engine = EngineTraitImpl::new(bytecode);
+ let res = engine.step();
+ assert!(res, "Execution of step failed");
+
+ let dstack = engine.get_dstack();
+ assert_eq!(dstack.len(), 1, "Stack length is not 1");
+
+ let expected_stack = array![""];
+ assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected");
+}
+
+#[test]
+fn test_op_1() {
+ let program = "OP_1";
+ let mut compiler = CompilerTraitImpl::new();
+ let bytecode = compiler.compile(program);
+ let mut engine = EngineTraitImpl::new(bytecode);
+ let res = engine.step();
+ assert!(res, "Execution of step failed");
+
+ let dstack = engine.get_dstack();
+ assert_eq!(dstack.len(), 1, "Stack length is not 1");
+
+ // TODO: Is this the correct representation of 1?
+ let expected_stack = array!["\0\0\0\0\0\0\0\x01"];
+ assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected");
+}
+
+#[test]
+fn test_op_add() {
+ let program = "OP_1 OP_1 OP_ADD";
+ let mut compiler = CompilerTraitImpl::new();
+ let bytecode = compiler.compile(program);
+ let mut engine = EngineTraitImpl::new(bytecode);
+ let _ = engine.step();
+ let _ = engine.step();
+ let res = engine.step();
+ assert!(res, "Execution of run failed");
+
+ let dstack = engine.get_dstack();
+ assert_eq!(dstack.len(), 1, "Stack length is not 1");
+
+ let expected_stack = array!["\0\0\0\0\0\0\0\x02"];
+ assert_eq!(dstack, expected_stack.span(), "Stack is not equal to expected");
+}
diff --git a/src/stack.cairo b/src/stack.cairo
new file mode 100644
index 00000000..a3ce5bdb
--- /dev/null
+++ b/src/stack.cairo
@@ -0,0 +1,105 @@
+use core::nullable::NullableTrait;
+use core::dict::Felt252DictEntryTrait;
+
+#[derive(Destruct)]
+pub struct ScriptStack {
+ data: Felt252Dict>,
+ len: usize,
+}
+
+#[generate_trait()]
+pub impl ScriptStackImpl of ScriptStackTrait {
+ fn new() -> ScriptStack {
+ ScriptStack {
+ data: Default::default(),
+ len: 0,
+ }
+ }
+
+ fn push_byte_array(ref self: ScriptStack, value: ByteArray) {
+ self.data.insert(self.len.into(), NullableTrait::new(value));
+ self.len += 1;
+ }
+
+ fn push_int(ref self: ScriptStack, value: i64) {
+ let mut bytes = "";
+ bytes.append_word(value.into(), 8);
+ self.push_byte_array(bytes);
+ }
+
+ fn pop_byte_array(ref self: ScriptStack) -> ByteArray {
+ if self.len == 0 {
+ // TODO
+ panic!("pop_byte_array: stack underflow");
+ }
+ self.len -= 1;
+ let (entry, bytes) = self.data.entry(self.len.into());
+ self.data = entry.finalize(NullableTrait::new(""));
+ bytes.deref()
+ }
+
+ fn pop_int(ref self: ScriptStack) -> i64 {
+ let bytes = self.pop_byte_array();
+ // TODO: Error handling & MakeScriptNum
+ let bytes_len = bytes.len();
+ if bytes_len == 0 {
+ return 0;
+ }
+ let mut value: i64 = 0;
+ let mut i = 0;
+ if bytes_len < 8 {
+ while i < bytes_len {
+ value = value * 256 + bytes.at(i).unwrap().into();
+ i += 1;
+ };
+ return value;
+ } else {
+ while i < 8 {
+ value = value * 256 + bytes.at(bytes_len - 8 + i).unwrap().into();
+ i += 1;
+ };
+ return value;
+ }
+ }
+
+ fn len(ref self: ScriptStack) -> usize {
+ self.len
+ }
+
+ fn depth(ref self: ScriptStack) -> usize {
+ self.len
+ }
+
+ fn print_element(ref self: ScriptStack, idx: usize) {
+ let (entry, arr) = self.data.entry(idx.into());
+ let arr = arr.deref();
+ if arr.len() == 0 {
+ println!("stack[{}]: null", idx);
+ } else {
+ println!("stack[{}]: {}", idx, arr);
+ }
+ self.data = entry.finalize(NullableTrait::new(arr));
+ }
+
+ fn print(ref self: ScriptStack) {
+ let mut i = self.len;
+ while i > 0 {
+ i -= 1;
+ self.print_element(i.into());
+ }
+ }
+
+ fn stack_to_span(ref self: ScriptStack) -> Span {
+ let mut result = array![];
+ let mut i = self.len;
+ while i > 0 {
+ i -= 1;
+ let (entry, arr) = self.data.entry(i.into());
+ let arr = arr.deref();
+ result.append(arr.clone());
+ self.data = entry.finalize(NullableTrait::new(arr));
+ };
+
+ return result.span();
+ }
+}
diff --git a/tests/test.cairo b/tests/test.cairo
new file mode 100644
index 00000000..1d15337c
--- /dev/null
+++ b/tests/test.cairo
@@ -0,0 +1,12 @@
+use shinigami::compiler::CompilerTraitImpl;
+use shinigami::engine::EngineTraitImpl;
+
+#[test]
+fn execution_test() {
+ let program = "OP_0 OP_1 OP_ADD";
+ let mut compiler = CompilerTraitImpl::new();
+ let bytecode = compiler.compile(program);
+ let mut engine = EngineTraitImpl::new(bytecode);
+ let res = engine.execute();
+ assert!(res.is_ok(), "Execution failed");
+}