Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoC of the Miden SDK and rollup account implementation #75

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"frontend-wasm",
"tests/rust-apps/*",
"tests/integration",
"sdk",
]
exclude = ["tests/rust-apps-wasm"]

Expand Down
4 changes: 3 additions & 1 deletion hir/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,9 @@ impl Module {
/// Unlinks the given function from this module
pub fn unlink(&mut self, id: Ident) -> Box<Function> {
let mut cursor = self.cursor_mut_at(id);
cursor.remove().expect("invalid function id")
cursor
.remove()
.expect(format!("invalid function id {}", id).as_str())
}

/// Append `function` to the end of this module's body, returning the [FuncId]
Expand Down
14 changes: 14 additions & 0 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "miden"
description = "Miden SDK"
version.workspace = true
rust-version.workspace = true
authors.workspace = true
repository.workspace = true
categories.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
edition.workspace = true

[dependencies]
16 changes: 16 additions & 0 deletions sdk/src/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::asset::Asset;

extern "C" {
#[link_name = "miden::sat::account::add_asset"]
pub fn add_asset_inner(asset: Asset) -> Asset;
#[link_name = "miden::sat::account::remove_asset"]
pub fn remove_asset_inner(asset: Asset) -> Asset;
}

pub fn add_asset(asset: Asset) -> Asset {
unsafe { add_asset_inner(asset) }
}

pub fn remove_asset(asset: Asset) -> Asset {
unsafe { remove_asset_inner(asset) }
}
10 changes: 10 additions & 0 deletions sdk/src/asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crate::felt::Word;

#[repr(transparent)]
pub struct Asset(Word);

impl From<Word> for Asset {
fn from(value: Word) -> Self {
Self(value)
}
}
14 changes: 14 additions & 0 deletions sdk/src/felt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Number of field elements in a word.
pub const WORD_SIZE: usize = 4;

/// A group of four field elements in the Miden base field.
pub type Word = [Felt; WORD_SIZE];

#[repr(transparent)]
pub struct Felt(u64);

impl From<u64> for Felt {
fn from(value: u64) -> Self {
Self(value)
}
}
8 changes: 8 additions & 0 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![no_std]
#![deny(warnings)]

pub mod account;
pub mod asset;
pub mod felt;
pub mod note;
pub mod tx;
26 changes: 26 additions & 0 deletions sdk/src/note.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::felt::Felt;
use crate::felt::Word;

#[repr(transparent)]
pub struct Recipient(Word);

impl From<Word> for Recipient {
fn from(value: Word) -> Self {
Self(value)
}
}

#[repr(transparent)]
pub struct Tag(Felt);

impl Tag {
pub fn new(value: u64) -> Self {
Self(Felt::from(value))
}
}

impl From<Felt> for Tag {
fn from(value: Felt) -> Self {
Self(value)
}
}
12 changes: 12 additions & 0 deletions sdk/src/tx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::asset::Asset;
use crate::note::Recipient;
use crate::note::Tag;

extern "C" {
#[link_name = "miden::sat::tx::create_note"]
pub fn create_note_inner(asset: Asset, tag: Tag, recipient: Recipient);
}

pub fn create_note(asset: Asset, tag: Tag, recipient: Recipient) {
unsafe { create_note_inner(asset, tag, recipient) }
}
2 changes: 0 additions & 2 deletions tests/integration/expected/fib.hir
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ module noname
const $0 = 0x00100000;

global external @__stack_pointer : i32 = $0 { id = 0 };
global external @gv1 : i32 = $0 { id = 1 };
global external @gv2 : i32 = $0 { id = 2 };

pub fn fib(i32) -> i32 {
block0(v0: i32):
Expand Down
4 changes: 0 additions & 4 deletions tests/integration/expected/fib.wat
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global $__stack_pointer (;0;) (mut i32) i32.const 1048576)
(global (;1;) i32 i32.const 1048576)
(global (;2;) i32 i32.const 1048576)
(export "memory" (memory 0))
(export "fib" (func $fib))
(export "__data_end" (global 1))
(export "__heap_base" (global 2))
)
219 changes: 219 additions & 0 deletions tests/integration/expected/sdk_account.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
(module
(type (;0;) (func (param i32 i32)))
(type (;1;) (func (param i32 i64 i32)))
(type (;2;) (func (param i32 i32 i32)))
(type (;3;) (func (param i64) (result i64)))
(import "env" "miden::sat::account::add_asset" (func $miden::sat::account::add_asset (;0;) (type 0)))
(import "env" "miden::sat::account::remove_asset" (func $miden::sat::account::remove_asset (;1;) (type 0)))
(import "env" "miden::sat::tx::create_note" (func $miden::sat::tx::create_note (;2;) (type 1)))
(func $receive_asset (;3;) (type 0) (param i32 i32)
Copy link
Contributor Author

@greenhat greenhat Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bobbinth @bitwalker This Wasm is compiled from https://github.com/0xPolygonMiden/miden-ir/blob/b27a75e13810fc55a115d9cdee9f0cab7e6f16e5/tests/rust-apps-wasm/sdk-account/src/lib.rs#L1-L29 which uses the SDK skeleton I drafted (see https://github.com/0xPolygonMiden/compiler/tree/b27a75e13810fc55a115d9cdee9f0cab7e6f16e5/sdk/src )

The calling convention is defined at https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md

Side note: To get the above calling convention, I switched to wasm32-wasi target because wasm32-unknown-unknown actually produces a different calling convention - the one inspired by wasm-bindgen. See https://users.rust-lang.org/t/how-are-structs-represented-in-web-assembly/57317/6 for more details.

The main challenge I see and focus on is that structs are passed by reference (address on the linear stack) while callers and callees are always expecting function parameters on the stack.
In the case of imported miden::sat::account::add_asset and others, I can naively imagine we could rewrite them in miden-base to expect a pointer to the struct instead of the struct itself and then load it on the stack. But in case of exported functions like receive_asset and send_asset it's not even possible since they are supposed to be called via call (see miden-base) meaning we can only pass values on stack and/or via advice provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the account methods can be externally called only from notes, I'm also bringing the note code into this PoC.

I plan to use macros to "wrap" note to account calls in serialization/deserialization code to pass arguments and return values via stack and advice provider. Initially, though I'll write this glue code manually and implement its generation via macros.

Let's see what it might look like for the MyWallet::receive_asset method:
The original user's MyWallet::receive_asset was renamed to receive_asset_original. The note code calls via call the created MyWallet::receive_asset which serializes the arguments and calls MyWallet::receive_asset_extern which is an extern "C" to the account's Miden code. The corresponding Miden code for MyWallet::receive_asset_extern is provided by the account's MyWallet::receive_asset_impl which deserializes the arguments and calls MyWallet::receive_asset_original which is the user-written original method. The return value is serialized by MyWallet::receive_asset_impl and returned to the note code deserialized in MyWallet::receive_asset.

Or in a diagram:
Note
->
call
->
MyWallet::recieve_asset do args serialization
->
extern "C" MyWallet::receive_asset_extern
->
exec MyWallet::receive_asset_impl exported from the account's code does args deserialization.
->
exec MyWallet::receive_asset_original
->
MyWallet::receive_asset_impl does return value serialization.
->
MyWallet::receive_asset does return value deserialization.
->
Note

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a stab at MyWallet::receive_assets at the scheme above and got quite far - https://github.com/0xPolygonMiden/miden-ir/blob/57cb25537964986a50e115291658b6c4a20a961b/tests/rust-apps-wasm/sdk-basic-wallet/src/lib.rs#L24-L82
There are a lot of todos in the code that macros will generate, but the idea seems to be viable.
Note that receive_assets_arg_ser and receive_assets_arg_deser should have the same name since it's imported func and its implementation, I just have not figured out how to do it yet.
MyWallet::receive_asset is called from the note's code. It packs the arguments and passes it either via stack or advice provider and calls receive_assets_arg_ser, which should end up as call.my_wallet::receive_assets_arg_deser in the note's MASM code. Which in turn deserializes args (stack/advice) and calls the user's MyWallet::receive_assets.

(local i32)
global.get $__stack_pointer
i32.const 32
i32.sub
local.tee 2
global.set $__stack_pointer
local.get 2
local.get 1
call $miden::account::add_asset
local.get 2
i32.const 32
i32.add
global.set $__stack_pointer
)
(func $send_asset (;4;) (type 2) (param i32 i32 i32)
(local i32)
global.get $__stack_pointer
i32.const 32
i32.sub
local.tee 3
global.set $__stack_pointer
local.get 3
local.get 1
call $miden::account::remove_asset
local.get 3
i64.const 4
call $miden::note::Tag::new
local.get 2
call $miden::tx::create_note
local.get 3
i32.const 32
i32.add
global.set $__stack_pointer
)
(func $miden::account::add_asset (;5;) (type 0) (param i32 i32)
(local i32)
global.get $__stack_pointer
i32.const 32
i32.sub
local.tee 2
global.set $__stack_pointer
local.get 2
i32.const 24
i32.add
local.get 1
i32.const 24
i32.add
i64.load
i64.store
local.get 2
i32.const 16
i32.add
local.get 1
i32.const 16
i32.add
i64.load
i64.store
local.get 2
i32.const 8
i32.add
local.get 1
i32.const 8
i32.add
i64.load
i64.store
local.get 2
local.get 1
i64.load
i64.store
local.get 0
local.get 2
call $miden::sat::account::add_asset
local.get 2
i32.const 32
i32.add
global.set $__stack_pointer
)
(func $miden::account::remove_asset (;6;) (type 0) (param i32 i32)
(local i32)
global.get $__stack_pointer
i32.const 32
i32.sub
local.tee 2
global.set $__stack_pointer
local.get 2
i32.const 24
i32.add
local.get 1
i32.const 24
i32.add
i64.load
i64.store
local.get 2
i32.const 16
i32.add
local.get 1
i32.const 16
i32.add
i64.load
i64.store
local.get 2
i32.const 8
i32.add
local.get 1
i32.const 8
i32.add
i64.load
i64.store
local.get 2
local.get 1
i64.load
i64.store
local.get 0
local.get 2
call $miden::sat::account::remove_asset
local.get 2
i32.const 32
i32.add
global.set $__stack_pointer
)
(func $miden::note::Tag::new (;7;) (type 3) (param i64) (result i64)
local.get 0
)
(func $miden::tx::create_note (;8;) (type 1) (param i32 i64 i32)
(local i32)
global.get $__stack_pointer
i32.const 64
i32.sub
local.tee 3
global.set $__stack_pointer
local.get 3
i32.const 32
i32.add
i32.const 24
i32.add
local.get 0
i32.const 24
i32.add
i64.load
i64.store
local.get 3
i32.const 32
i32.add
i32.const 16
i32.add
local.get 0
i32.const 16
i32.add
i64.load
i64.store
local.get 3
i32.const 32
i32.add
i32.const 8
i32.add
local.get 0
i32.const 8
i32.add
i64.load
i64.store
local.get 3
local.get 0
i64.load
i64.store offset=32
local.get 3
i32.const 8
i32.add
local.get 2
i32.const 8
i32.add
i64.load
i64.store
local.get 3
i32.const 16
i32.add
local.get 2
i32.const 16
i32.add
i64.load
i64.store
local.get 3
i32.const 24
i32.add
local.get 2
i32.const 24
i32.add
i64.load
i64.store
local.get 3
local.get 2
i64.load
i64.store
local.get 3
i32.const 32
i32.add
local.get 1
local.get 3
call $miden::sat::tx::create_note
local.get 3
i32.const 64
i32.add
global.set $__stack_pointer
)
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global $__stack_pointer (;0;) (mut i32) i32.const 1048576)
(export "memory" (memory 0))
(export "receive_asset" (func $receive_asset))
(export "send_asset" (func $send_asset))
)
Loading
Loading