Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 0 additions & 6 deletions Cargo.lock

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

3 changes: 1 addition & 2 deletions zk/abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ host = [
"dep:vprogs-scheduling-scheduler",
"dep:vprogs-storage-types",
]
std = ["borsh/std", "rkyv/std"]
std = ["borsh/std"]

[dependencies]
borsh = { workspace = true, default-features = false }
rkyv = { workspace = true, default-features = false }
vprogs-core-types = { workspace = true, default-features = false }
vprogs-l1-types = { workspace = true, optional = true }
vprogs-scheduling-scheduler = { workspace = true, optional = true }
Expand Down
72 changes: 66 additions & 6 deletions zk/abi/src/account.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,71 @@
use alloc::vec::Vec;

use rkyv::{Archive, Serialize};
use vprogs_core_types::ResourceId;

/// A snapshot of a single account's state at execution time.
#[derive(Clone, Debug, Archive, Serialize)]
pub struct Account {
pub resource_id: ResourceId,
pub data: Vec<u8>,
/// A mutable view of a single account's data within a decoded witness buffer.
///
/// Data starts as a borrowed slice into the wire buffer (`backing`). If the caller needs more
/// space than the original slice provides, [`grow`](Account::grow) promotes to a
/// heap-allocated `Vec` (`promoted`). Reads and writes always go through the active buffer.
pub struct Account<'a> {
resource_id: ResourceId,
is_new: bool,
backing: &'a mut [u8],
promoted: Option<Vec<u8>>,
dirty: bool,
deleted: bool,
}

impl<'a> Account<'a> {
/// Creates a new `Account` borrowing into the given slice.
pub fn new(resource_id: ResourceId, is_new: bool, backing: &'a mut [u8]) -> Self {
Self { resource_id, is_new, backing, promoted: None, dirty: false, deleted: false }
}

pub fn resource_id(&self) -> &ResourceId {
&self.resource_id
}

pub fn is_new(&self) -> bool {
self.is_new
}

/// Returns a shared reference to the current data (backing or promoted).
pub fn data(&self) -> &[u8] {
self.promoted.as_deref().unwrap_or(self.backing)
}

/// Returns a mutable reference to the current data (backing or promoted) and marks dirty.
pub fn data_mut(&mut self) -> &mut [u8] {
self.dirty = true;
match self.promoted {
Some(ref mut v) => v.as_mut_slice(),
None => self.backing,
}
}

/// Promotes to a heap-allocated buffer of `new_len` bytes (copies existing data, pads with
/// zeros). Subsequent borrows use the promoted buffer.
pub fn grow(&mut self, new_len: usize) {
let src = self.promoted.as_deref().unwrap_or(self.backing);
let mut buf = Vec::with_capacity(new_len);
let copy_len = src.len().min(new_len);
buf.extend_from_slice(&src[..copy_len]);
buf.resize(new_len, 0);
self.promoted = Some(buf);
self.dirty = true;
}

pub fn mark_deleted(&mut self) {
self.deleted = true;
self.dirty = true;
}

pub fn is_dirty(&self) -> bool {
self.dirty
}

pub fn is_deleted(&self) -> bool {
self.deleted
}
}
9 changes: 0 additions & 9 deletions zk/abi/src/batch_metadata.rs

This file was deleted.

10 changes: 5 additions & 5 deletions zk/abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
extern crate alloc;

mod account;
mod batch_metadata;
mod metadata;
mod storage_op;
mod transaction_context;

pub use account::{Account, ArchivedAccount};
pub use batch_metadata::{ArchivedBatchMetadata, BatchMetadata};
pub use storage_op::{ArchivedStorageOp, StorageOp};
pub use transaction_context::{ArchivedTransactionContext, TransactionContext};
pub use account::Account;
pub use metadata::Metadata;
pub use storage_op::{StorageOp, StorageOpRef};
pub use transaction_context::TransactionContext;
7 changes: 7 additions & 0 deletions zk/abi/src/metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// Immutable transaction metadata from the wire header.
pub struct Metadata<'a> {
pub tx_index: u32,
pub tx_bytes: &'a [u8],
pub block_hash: [u8; 32],
pub blue_score: u64,
}
72 changes: 70 additions & 2 deletions zk/abi/src/storage_op.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,80 @@
use alloc::vec::Vec;

use borsh::{BorshDeserialize, BorshSerialize};
use rkyv::{Archive, Deserialize, Serialize};

/// A storage mutation produced by executing a transaction, addressed by account index.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Archive, Serialize, Deserialize)]
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub enum StorageOp {
Create(Vec<u8>),
Update(Vec<u8>),
Delete,
}

/// A borrowing variant of [`StorageOp`] that serializes with the **same** borsh wire format.
///
/// This lets the guest stream account data directly from backing/promoted buffers without
/// cloning. The host deserializes as `Vec<Option<StorageOp>>` unchanged.
pub enum StorageOpRef<'a> {
Create(&'a [u8]),
Update(&'a [u8]),
Delete,
}

impl BorshSerialize for StorageOpRef<'_> {
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
match self {
StorageOpRef::Create(data) => {
// Variant index 0
writer.write_all(&[0])?;
// Length-prefixed bytes (same as Vec<u8> borsh format)
writer.write_all(&(data.len() as u32).to_le_bytes())?;
writer.write_all(data)?;
}
StorageOpRef::Update(data) => {
// Variant index 1
writer.write_all(&[1])?;
writer.write_all(&(data.len() as u32).to_le_bytes())?;
writer.write_all(data)?;
}
StorageOpRef::Delete => {
// Variant index 2
writer.write_all(&[2])?;
}
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn storage_op_ref_borsh_compat() {
let data = vec![1u8, 2, 3, 4, 5];

// Create
let owned = StorageOp::Create(data.clone());
let borrowed = StorageOpRef::Create(&data);
assert_eq!(borsh::to_vec(&owned).unwrap(), borsh::to_vec(&borrowed).unwrap());

// Update
let owned = StorageOp::Update(data.clone());
let borrowed = StorageOpRef::Update(&data);
assert_eq!(borsh::to_vec(&owned).unwrap(), borsh::to_vec(&borrowed).unwrap());

// Delete
let owned = StorageOp::Delete;
let borrowed = StorageOpRef::Delete;
assert_eq!(borsh::to_vec(&owned).unwrap(), borsh::to_vec(&borrowed).unwrap());

// Option<StorageOp> compat
let owned_some: Option<StorageOp> = Some(StorageOp::Update(data.clone()));
let borrowed_some: Option<StorageOpRef> = Some(StorageOpRef::Update(&data));
assert_eq!(borsh::to_vec(&owned_some).unwrap(), borsh::to_vec(&borrowed_some).unwrap());

let owned_none: Option<StorageOp> = None;
let borrowed_none: Option<StorageOpRef> = None;
assert_eq!(borsh::to_vec(&owned_none).unwrap(), borsh::to_vec(&borrowed_none).unwrap());
}
}
Loading