diff --git a/Cargo.lock b/Cargo.lock index 104aec538..6d1cb2779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -868,12 +868,9 @@ dependencies = [ "camino", "eyre", "futures-util", - "hex", "rand 0.8.5", "reqwest 0.12.9", "serde", - "starknet", - "starknet-crypto 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio", "tokio-util", "tracing", diff --git a/contracts/context-config/src/mutate.rs b/contracts/context-config/src/mutate.rs index cdc82fe75..b1e9bdc60 100644 --- a/contracts/context-config/src/mutate.rs +++ b/contracts/context-config/src/mutate.rs @@ -251,6 +251,12 @@ impl ContextConfigs { .expect("unable to update member list") .priviledges() .grant(identity), + Capability::Proxy => context + .proxy + .get(signer_id) + .expect("unable to update proxy") + .priviledges() + .grant(identity), }; env::log_str(&format!( @@ -289,6 +295,12 @@ impl ContextConfigs { .expect("unable to update member list") .priviledges() .revoke(&identity), + Capability::Proxy => context + .proxy + .get(signer_id) + .expect("unable to update proxy") + .priviledges() + .revoke(&identity), }; env::log_str(&format!( diff --git a/crates/context/Cargo.toml b/crates/context/Cargo.toml index afcad728d..ebe2494e6 100644 --- a/crates/context/Cargo.toml +++ b/crates/context/Cargo.toml @@ -10,7 +10,6 @@ license.workspace = true camino = { workspace = true, features = ["serde1"] } eyre.workspace = true futures-util.workspace = true -hex.workspace = true rand.workspace = true reqwest = { workspace = true, features = ["stream"] } serde.workspace = true @@ -18,9 +17,6 @@ tokio = { workspace = true, features = ["sync", "macros"] } tokio-util.workspace = true tracing.workspace = true -starknet-crypto = { workspace = true } -starknet = { workspace = true } - calimero-context-config = { path = "./config", features = ["client"] } calimero-blobstore = { path = "../store/blobs" } calimero-primitives = { path = "../primitives", features = ["borsh", "rand"] } diff --git a/crates/context/config/src/client/env/config/mutate.rs b/crates/context/config/src/client/env/config/mutate.rs index e1e34b7e0..ecbc5454e 100644 --- a/crates/context/config/src/client/env/config/mutate.rs +++ b/crates/context/config/src/client/env/config/mutate.rs @@ -11,7 +11,7 @@ use crate::client::protocol::near::Near; use crate::client::protocol::starknet::Starknet; use crate::client::transport::Transport; use crate::client::{CallClient, ClientError, Operation}; -use crate::repr::{Repr, ReprBytes, ReprTransmute}; +use crate::repr::{Repr, ReprTransmute}; use crate::types::Signed; use crate::{ContextIdentity, Request, RequestKind}; pub mod methods; @@ -76,15 +76,11 @@ impl<'a> Method for Mutate<'a> { let user_key_bytes = user_key.to_bytes(); // Create Repr wrapped ContextIdentity instances - let signer_id = Repr::new(ContextIdentity::from_bytes(|bytes| { - bytes.copy_from_slice(&verifying_key_bytes); - Ok(32) - })?); + let signer_id = verifying_key_bytes.rt::().expect("Infallible conversion"); + let signer_id = Repr::new(signer_id); - let user_id = Repr::new(ContextIdentity::from_bytes(|bytes| { - bytes.copy_from_slice(&user_key_bytes); - Ok(32) - })?); + let user_id = user_key_bytes.rt::().expect("Infallible conversion"); + let user_id = Repr::new(user_id); // Create the Request structure using into() conversions let request = StarknetRequest { @@ -121,8 +117,7 @@ impl<'a> Method for Mutate<'a> { Ok(bytes) } - fn decode(response: Vec) -> eyre::Result { - println!("decode {:?}", response); + fn decode(_response: Vec) -> eyre::Result { Ok(()) } } diff --git a/crates/context/config/src/client/env/config/query/application.rs b/crates/context/config/src/client/env/config/query/application.rs index 851f56db2..f167fce76 100644 --- a/crates/context/config/src/client/env/config/query/application.rs +++ b/crates/context/config/src/client/env/config/query/application.rs @@ -1,11 +1,13 @@ use serde::Serialize; use starknet_crypto::Felt; +use crate::client::env::config::types::starknet::Application as StarknetApplication; use crate::client::env::Method; use crate::client::protocol::near::Near; use crate::client::protocol::starknet::Starknet; -use crate::repr::{Repr, ReprBytes, ReprTransmute}; +use crate::repr::{Repr, ReprBytes}; use crate::types::{Application, ApplicationMetadata, ApplicationSource, ContextId}; +use starknet::core::codec::Decode; #[derive(Copy, Clone, Debug, Serialize)] pub(super) struct ApplicationRequest { @@ -62,49 +64,23 @@ impl Method for ApplicationRequest { return Err(eyre::eyre!("No application found")); } + // Convert bytes to Felts + let mut felts = Vec::new(); + for chunk in response.chunks(32) { + if chunk.len() == 32 { + felts.push(Felt::from_bytes_be(chunk.try_into().unwrap())); + } + } + // Check if it's a None response (single zero Felt) - if response.len() == 32 && response.iter().all(|&x| x == 0) { + if felts.len() == 1 && felts[0] == Felt::ZERO { return Err(eyre::eyre!("No application found")); } - // First 32 bytes is the flag/version (0x0), skip it - let response = &response[32..]; - - // Next two Felts are application id (high/low) - let mut id_bytes = [0u8; 32]; - id_bytes[..16].copy_from_slice(&response[16..32]); // high part - id_bytes[16..].copy_from_slice(&response[48..64]); // low part - let id = Repr::new(id_bytes.rt()?); - - // Next two Felts are blob id (high/low) - let mut blob_bytes = [0u8; 32]; - blob_bytes[..16].copy_from_slice(&response[80..96]); // high part - blob_bytes[16..].copy_from_slice(&response[112..128]); // low part - let blob = Repr::new(blob_bytes.rt()?); - - // Next Felt is size (0x1af25) - let size = u64::from_be_bytes(response[152..160].try_into()?); - - // Source string starts after the length Felt (0x2) - let mut source_bytes = Vec::new(); - let mut i = 192; // Start after length Felt - while i < response.len() { - let chunk = &response[i..]; - if chunk.iter().take(32).all(|&b| b == 0) { - break; - } - source_bytes.extend(chunk.iter().take(32).filter(|&&b| b != 0)); - i += 32; - } - let source = ApplicationSource(String::from_utf8(source_bytes)?.into()); - - // Find metadata after source string (look for 0.0.1) - let metadata_bytes: Vec = response - .windows(5) - .find(|window| window == b"0.0.1") - .map(|_| b"0.0.1".to_vec()) - .unwrap_or_default(); - let metadata = ApplicationMetadata(Repr::new(metadata_bytes.into())); - Ok(Application::new(id, blob, size, source, metadata)) + // Skip the flag/version felt and decode the application + let application = StarknetApplication::decode(&felts[1..]) + .map_err(|e| eyre::eyre!("Failed to decode application: {:?}", e))?; + + Ok(application.into()) } } diff --git a/crates/context/config/src/client/env/config/query/members.rs b/crates/context/config/src/client/env/config/query/members.rs index 5b724cd74..672211d26 100644 --- a/crates/context/config/src/client/env/config/query/members.rs +++ b/crates/context/config/src/client/env/config/query/members.rs @@ -4,11 +4,12 @@ use serde::Serialize; use starknet::core::codec::Encode; use starknet_crypto::Felt; -use crate::client::env::config::types::starknet::StarknetMembersRequest; +use crate::client::env::config::types::starknet::{StarknetMembers, StarknetMembersRequest}; use crate::client::env::Method; use crate::client::protocol::near::Near; use crate::client::protocol::starknet::Starknet; -use crate::repr::{Repr, ReprBytes, ReprTransmute}; +use starknet::core::codec::Decode; +use crate::repr::Repr; use crate::types::{ContextId, ContextIdentity}; #[derive(Copy, Clone, Debug, Serialize)] @@ -65,27 +66,23 @@ impl Method for MembersRequest { return Ok(Vec::new()); } - // First 32 bytes contain the count, skip it - let response = &response[32..]; - - let members: Result, _> = response - .chunks_exact(64) - .map(|chunk| { - let felt1 = Felt::from_bytes_be_slice(&chunk[..32]); - let felt2 = Felt::from_bytes_be_slice(&chunk[32..]); + // Convert bytes to Felts + let mut felts = Vec::new(); + for chunk in response.chunks(32) { + let mut padded_chunk = [0u8; 32]; + padded_chunk[..chunk.len()].copy_from_slice(chunk); + felts.push(Felt::from_bytes_be(&padded_chunk)); + } - let felt1_bytes = felt1.to_bytes_be(); - let felt2_bytes = felt2.to_bytes_be(); + // Check if it's a None response (single zero Felt) + if felts.len() == 1 && felts[0] == Felt::ZERO { + return Ok(Vec::new()); + } - ContextIdentity::from_bytes(|bytes| { - bytes[..16].copy_from_slice(&felt1_bytes[16..]); - bytes[16..].copy_from_slice(&felt2_bytes[16..]); - Ok(32) - }) - }) - .collect(); + // Decode directly from the felts slice - the Decode trait will handle the array structure + let members = StarknetMembers::decode(&felts) + .map_err(|e| eyre::eyre!("Failed to decode members: {:?}", e))?; - let members = members.map_err(|e| eyre::eyre!("Failed to decode members: {:?}", e))?; - Ok(members) + Ok(members.into()) } } diff --git a/crates/context/config/src/client/env/config/query/privileges.rs b/crates/context/config/src/client/env/config/query/privileges.rs index 3b044c310..6e916ef6d 100644 --- a/crates/context/config/src/client/env/config/query/privileges.rs +++ b/crates/context/config/src/client/env/config/query/privileges.rs @@ -3,7 +3,9 @@ use std::collections::BTreeMap; use serde::Serialize; use starknet_crypto::Felt; +use starknet::core::codec::Decode; +use crate::client::env::config::types::starknet::StarknetPrivileges; use crate::client::env::Method; use crate::client::protocol::near::Near; use crate::client::protocol::starknet::Starknet; @@ -89,39 +91,27 @@ impl<'a> Method for PrivilegesRequest<'a> { } fn decode(response: Vec) -> eyre::Result { - // if response.is_empty() { - // return Ok(BTreeMap::new()); - // } - - // let mut result = BTreeMap::new(); - // let mut offset = 0; - - // // First felt is array length - // let array_len = u64::from_be_bytes(response[24..32].try_into()?); - // offset += 32; - - // // Process each (identity, capabilities) pair - // for _ in 0..array_len { - // // Read identity from 2 felts (32 bytes each) - // let identity = SignerId::from_bytes(|bytes| { - // // Take the second felt (low part) for SignerId - // bytes.copy_from_slice(&response[offset + 32..offset + 64]); - // Ok(32) // Return successful result with number of bytes written - // })?; - // offset += 64; // Skip both felts (high and low parts) - - // // Read capability (1 felt) - // let cap_value = u64::from_be_bytes(response[offset + 24..offset + 32].try_into()?); - // offset += 32; + if response.is_empty() { + return Ok(BTreeMap::new()); + } - // // Create capabilities vector with single capability - // let capabilities = vec![Capability::from(cap_value)]; + // Convert bytes to Felts + let mut felts = Vec::new(); + for chunk in response.chunks(32) { + if chunk.len() == 32 { + felts.push(Felt::from_bytes_be(chunk.try_into().unwrap())); + } + } - // result.insert(identity, capabilities); - // } + // Check if it's a None response (single zero Felt) + if felts.len() == 1 && felts[0] == Felt::ZERO { + return Ok(BTreeMap::new()); + } - // Ok(result) + // Skip the flag/version felt and decode the privileges + let privileges = StarknetPrivileges::decode(&felts[1..]) + .map_err(|e| eyre::eyre!("Failed to decode privileges: {:?}", e))?; - todo!() + Ok(privileges.into()) } } diff --git a/crates/context/config/src/client/env/config/query/proxy_contract.rs b/crates/context/config/src/client/env/config/query/proxy_contract.rs index 53e4188ae..a0ddcdc8a 100644 --- a/crates/context/config/src/client/env/config/query/proxy_contract.rs +++ b/crates/context/config/src/client/env/config/query/proxy_contract.rs @@ -48,7 +48,6 @@ impl Method for ProxyContractRequest { } fn decode(response: Vec) -> eyre::Result { - println!("response {:?}", response); if response.is_empty() { return Err(eyre::eyre!("No proxy contract found")); } @@ -58,9 +57,10 @@ impl Method for ProxyContractRequest { return Err(eyre::eyre!("No proxy contract found")); } - // Convert the Felt to a hex string representing the contract address - let hex_string = format!("0x{}", hex::encode(&response)); - - Ok(hex_string) + // Parse bytes as Felt + let felt = Felt::from_bytes_be_slice(&response); + + // Format felt as hex string with 0x prefix + Ok(format!("0x{:x}", felt)) } } diff --git a/crates/context/config/src/client/env/config/types/starknet.rs b/crates/context/config/src/client/env/config/types/starknet.rs index e8e3e5de2..c69e8fb2f 100644 --- a/crates/context/config/src/client/env/config/types/starknet.rs +++ b/crates/context/config/src/client/env/config/types/starknet.rs @@ -1,88 +1,135 @@ use hex; -use starknet::core::codec::{Encode, Error, FeltWriter}; +use starknet::core::codec::{Encode, Decode, Error, FeltWriter}; use starknet::core::types::Felt; - use crate::repr::{Repr, ReprBytes}; use crate::types::SignerId; +use crate::repr::ReprTransmute; +use std::collections::BTreeMap; -#[derive(Debug, Clone, Encode)] -pub struct ContextId { +// Base type for all Starknet Felt pairs +#[derive(Debug, Clone, Copy, Encode, Decode)] +pub struct FeltPair { pub high: Felt, pub low: Felt, } -impl From for ContextId { - fn from(value: crate::ContextId) -> Self { - let bytes = value.as_bytes(); +// Newtype pattern following types.rs style +#[derive(Debug, Clone, Copy, Encode, Decode)] +pub struct ContextId(FeltPair); + +#[derive(Debug, Clone, Copy, Encode, Decode)] +pub struct ContextIdentity(FeltPair); + +#[derive(Debug, Clone, Copy, Encode, Decode)] +pub struct ApplicationId(FeltPair); + +#[derive(Debug, Clone, Copy, Encode, Decode)] +pub struct ApplicationBlob(FeltPair); + +// Single conversion trait +pub trait IntoFeltPair { + fn into_felt_pair(self) -> (Felt, Felt); +} + +// Implement for our base types +impl IntoFeltPair for crate::types::ContextId { + fn into_felt_pair(self) -> (Felt, Felt) { + let bytes = self.as_bytes(); let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextId { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } + ( + Felt::from_bytes_be_slice(high_bytes), + Felt::from_bytes_be_slice(low_bytes), + ) } } -// Add this implementation for Repr wrapped type -impl From> for ContextId { - fn from(value: Repr) -> Self { - let bytes = value.as_bytes(); +impl IntoFeltPair for crate::types::ContextIdentity { + fn into_felt_pair(self) -> (Felt, Felt) { + let bytes = self.as_bytes(); let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextId { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } + ( + Felt::from_bytes_be_slice(high_bytes), + Felt::from_bytes_be_slice(low_bytes), + ) } } -// Context Member ID -#[derive(Debug, Clone, Encode)] -pub struct ContextIdentity { - pub high: Felt, - pub low: Felt, +impl IntoFeltPair for crate::types::ApplicationId { + fn into_felt_pair(self) -> (Felt, Felt) { + let bytes = self.as_bytes(); + let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); + ( + Felt::from_bytes_be_slice(high_bytes), + Felt::from_bytes_be_slice(low_bytes), + ) + } } -impl From for ContextIdentity { - fn from(value: crate::ContextIdentity) -> Self { - let bytes = value.as_bytes(); +impl IntoFeltPair for crate::types::BlobId { + fn into_felt_pair(self) -> (Felt, Felt) { + let bytes = self.as_bytes(); let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextIdentity { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } + ( + Felt::from_bytes_be_slice(high_bytes), + Felt::from_bytes_be_slice(low_bytes), + ) } } -// Add this implementation for Repr wrapped type -impl From> for ContextIdentity { - fn from(value: Repr) -> Self { - let bytes = value.as_bytes(); +// Add IntoFeltPair implementation for SignerId +impl IntoFeltPair for SignerId { + fn into_felt_pair(self) -> (Felt, Felt) { + let bytes = self.as_bytes(); let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextIdentity { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } + ( + Felt::from_bytes_be_slice(high_bytes), + Felt::from_bytes_be_slice(low_bytes), + ) } } +// From implementations for our Starknet types +impl From for ContextId { + fn from(value: crate::types::ContextId) -> Self { + let (high, low) = value.into_felt_pair(); + Self(FeltPair { high, low }) + } +} + +impl From for ContextIdentity { + fn from(value: crate::types::ContextIdentity) -> Self { + let (high, low) = value.into_felt_pair(); + Self(FeltPair { high, low }) + } +} + +impl From for ApplicationId { + fn from(value: crate::types::ApplicationId) -> Self { + let (high, low) = value.into_felt_pair(); + Self(FeltPair { high, low }) + } +} + +impl From for ApplicationBlob { + fn from(value: crate::types::BlobId) -> Self { + let (high, low) = value.into_felt_pair(); + Self(FeltPair { high, low }) + } +} + +// Add From for ContextIdentity impl From for ContextIdentity { fn from(value: SignerId) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextIdentity { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } + let (high, low) = value.into_felt_pair(); + Self(FeltPair { high, low }) } } -impl From> for ContextIdentity { - fn from(value: Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextIdentity { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } +// Add From> for ContextIdentity +impl From> for ContextIdentity { + fn from(value: Repr) -> Self { + let (high, low) = value.into_inner().into_felt_pair(); + Self(FeltPair { high, low }) } } @@ -108,46 +155,12 @@ pub struct ContextRequest { impl From> for ContextRequest { fn from(value: crate::ContextRequest<'_>) -> Self { ContextRequest { - context_id: value.context_id.into(), + context_id: (*value.context_id).into(), kind: value.kind.to_owned().into(), } } } -#[derive(Debug, Encode)] -pub struct Signed { - pub payload: Vec, - pub signature_r: Felt, - pub signature_s: Felt, -} - -impl From> for Signed { - fn from(value: crate::types::Signed) -> Self { - // Convert the payload bytes to Felts - let payload = value - .payload - .into_inner() - .chunks_exact(32) - .map(|chunk| { - let chunk_array: [u8; 32] = chunk.try_into().expect("chunk should be 32 bytes"); - Felt::from_bytes_be(&chunk_array) - }) - .collect(); - // Extract r and s from the signature - let sig_bytes = value.signature.as_bytes(); - let (r_bytes, s_bytes) = sig_bytes.split_at(32); - // Convert slices to fixed arrays - let r_array: [u8; 32] = r_bytes.try_into().expect("r should be 32 bytes"); - let s_array: [u8; 32] = s_bytes.try_into().expect("s should be 32 bytes"); - - Signed { - payload, - signature_r: Felt::from_bytes_be(&r_array), - signature_s: Felt::from_bytes_be(&s_array), - } - } -} - #[derive(Debug, Encode)] pub struct Request { pub kind: RequestKind, @@ -156,24 +169,11 @@ pub struct Request { pub nonce: u64, } -impl From> for Request { - fn from(value: crate::Request<'_>) -> Self { - Request { - kind: value.kind.into(), - signer_id: value.signer_id.into(), - user_id: ContextIdentity { - high: Felt::ZERO, - low: Felt::ZERO, - }, - nonce: 0, - } - } -} - -#[derive(Debug, Encode)] +#[derive(Debug, Encode, Decode, Copy, Clone)] pub enum Capability { ManageApplication, ManageMembers, + ProxyCode, } impl From<&crate::Capability> for Capability { @@ -181,6 +181,7 @@ impl From<&crate::Capability> for Capability { match value { crate::Capability::ManageApplication => Capability::ManageApplication, crate::Capability::ManageMembers => Capability::ManageMembers, + crate::Capability::Proxy => Capability::ProxyCode, } } } @@ -205,57 +206,35 @@ pub enum ContextRequestKind { impl From> for ContextRequestKind { fn from(value: crate::ContextRequestKind<'_>) -> Self { match value { - crate::ContextRequestKind::Add { - author_id, - application, - } => ContextRequestKind::Add(author_id.into(), application.into()), - crate::ContextRequestKind::UpdateApplication { application } => { - ContextRequestKind::UpdateApplication(application.into()) - } - crate::ContextRequestKind::AddMembers { members } => { - ContextRequestKind::AddMembers(members.into_iter().map(|m| m.into()).collect()) - } - crate::ContextRequestKind::RemoveMembers { members } => { - ContextRequestKind::RemoveMembers(members.into_iter().map(|m| m.into()).collect()) - } - crate::ContextRequestKind::Grant { capabilities } => ContextRequestKind::Grant( - capabilities - .into_iter() + crate::ContextRequestKind::Add { author_id, application } => + ContextRequestKind::Add(author_id.into_inner().into(), application.into()), + crate::ContextRequestKind::UpdateApplication { application } => + ContextRequestKind::UpdateApplication(application.into()), + crate::ContextRequestKind::AddMembers { members } => + ContextRequestKind::AddMembers(members.into_iter().map(|m| m.into_inner().into()).collect()), + crate::ContextRequestKind::RemoveMembers { members } => + ContextRequestKind::RemoveMembers(members.into_iter().map(|m| m.into_inner().into()).collect()), + crate::ContextRequestKind::Grant { capabilities } => + ContextRequestKind::Grant(capabilities.into_iter() .map(|(id, cap)| CapabilityAssignment { - member: id.into(), + member: id.into_inner().into(), capability: cap.into(), }) - .collect(), - ), - crate::ContextRequestKind::Revoke { capabilities } => ContextRequestKind::Revoke( - capabilities - .into_iter() + .collect()), + crate::ContextRequestKind::Revoke { capabilities } => + ContextRequestKind::Revoke(capabilities.into_iter() .map(|(id, cap)| CapabilityAssignment { - member: id.into(), + member: id.into_inner().into(), capability: cap.into(), }) - .collect(), - ), - crate::ContextRequestKind::UpdateProxyContract => { - ContextRequestKind::UpdateProxyContract - } + .collect()), + crate::ContextRequestKind::UpdateProxyContract => + ContextRequestKind::UpdateProxyContract, } } } -#[derive(Debug, Clone, Encode)] -pub struct ApplicationId { - pub high: Felt, - pub low: Felt, -} - -#[derive(Debug, Clone, Encode)] -pub struct ApplicationBlob { - pub high: Felt, - pub low: Felt, -} - -#[derive(Debug, Clone, Encode)] +#[derive(Debug, Clone, Encode, Decode)] pub struct Application { pub id: ApplicationId, pub blob: ApplicationBlob, @@ -266,21 +245,9 @@ pub struct Application { impl From> for Application { fn from(value: crate::Application<'_>) -> Self { - let id_bytes = value.id.as_bytes(); - let (id_high, id_low) = id_bytes.split_at(id_bytes.len() / 2); - - let blob_bytes = value.blob.as_bytes(); - let (blob_high, blob_low) = blob_bytes.split_at(blob_bytes.len() / 2); - Application { - id: ApplicationId { - high: Felt::from_bytes_be_slice(id_high), - low: Felt::from_bytes_be_slice(id_low), - }, - blob: ApplicationBlob { - high: Felt::from_bytes_be_slice(blob_high), - low: Felt::from_bytes_be_slice(blob_low), - }, + id: (*value.id).into(), + blob: (*value.blob).into(), size: value.size, source: value.source.into(), metadata: value.metadata.into(), @@ -288,69 +255,14 @@ impl From> for Application { } } -impl From for ApplicationId { - fn from(value: crate::types::ApplicationId) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ApplicationId { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } - } -} - -impl From> for ApplicationId { - fn from(value: Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ApplicationId { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } - } -} - -impl From<&Repr> for ApplicationId { - fn from(value: &Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ApplicationId { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } - } -} - -// Similar implementations for ApplicationBlob -impl From for ApplicationBlob { - fn from(value: crate::types::BlobId) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ApplicationBlob { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } - } -} - -impl From> for ApplicationBlob { - fn from(value: Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ApplicationBlob { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } - } -} - -impl From<&Repr> for ApplicationBlob { - fn from(value: &Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ApplicationBlob { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), +impl<'a> From for crate::Application<'a> { + fn from(value: Application) -> Self { + crate::Application { + id: Repr::new(value.id.into()), + blob: Repr::new(value.blob.into()), + size: value.size, + source: value.source.into(), + metadata: value.metadata.into(), } } } @@ -370,7 +282,6 @@ impl From> for EncodableString { } } -// Optional: Add this if you need to convert from string references impl From<&str> for EncodableString { fn from(value: &str) -> Self { EncodableString(value.to_string()) @@ -382,14 +293,11 @@ impl Encode for EncodableString { const WORD_SIZE: usize = 31; let bytes = self.0.as_bytes(); - // Calculate full words and pending word let full_words_count = bytes.len() / WORD_SIZE; let pending_len = bytes.len() % WORD_SIZE; - // Write number of full words writer.write(Felt::from(full_words_count)); - // Write full words (31 chars each) for i in 0..full_words_count { let start = i * WORD_SIZE; let word_bytes = &bytes[start..start + WORD_SIZE]; @@ -397,7 +305,6 @@ impl Encode for EncodableString { writer.write(Felt::from_hex(&format!("0x{}", word_hex)).unwrap()); } - // Write pending word if exists if pending_len > 0 { let pending_bytes = &bytes[full_words_count * WORD_SIZE..]; let pending_hex = hex::encode(pending_bytes); @@ -406,13 +313,55 @@ impl Encode for EncodableString { writer.write(Felt::ZERO); } - // Write pending word length writer.write(Felt::from(pending_len)); Ok(()) } } +impl<'a> Decode<'a> for EncodableString { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator + { + const WORD_SIZE: usize = 31; + + // First felt is full_words_count + let full_words_count = iter.next() + .ok_or_else(Error::input_exhausted)? + .to_bytes_be()[31] as usize; + + let mut bytes = Vec::with_capacity(full_words_count * WORD_SIZE); + + // Read each full word (31 bytes each) + for _ in 0..full_words_count { + let word = iter.next() + .ok_or_else(Error::input_exhausted)? + .to_bytes_be(); + bytes.extend_from_slice(&word[1..WORD_SIZE + 1]); // Take exactly WORD_SIZE bytes, skipping first byte + } + + // Read pending bytes (if any) + let pending = iter.next() + .ok_or_else(Error::input_exhausted)?; + + let pending_len = iter.next() + .ok_or_else(Error::input_exhausted)? + .to_bytes_be()[31] as usize; + + if pending_len > 0 { + let pending_bytes = pending.to_bytes_be(); + bytes.extend_from_slice(&pending_bytes[1..pending_len + 1]); + } + + // Convert bytes to string + let string = String::from_utf8(bytes) + .map_err(|_| Error::custom("Invalid UTF-8"))?; + + Ok(EncodableString(string)) + } +} + #[derive(Debug, Encode)] pub struct StarknetMembersRequest { pub context_id: ContextId, @@ -423,62 +372,142 @@ pub struct StarknetMembersRequest { impl From for StarknetMembersRequest { fn from(value: crate::client::env::config::query::members::MembersRequest) -> Self { StarknetMembersRequest { - context_id: value.context_id.into(), + context_id: (*value.context_id).into(), offset: value.offset as u32, length: value.length as u32, } } } -// Add these implementations for reference types -impl From<&Repr> for ContextIdentity { - fn from(value: &Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextIdentity { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), +#[derive(Debug, Encode)] +pub struct StarknetApplicationRevisionRequest { + pub context_id: ContextId, +} + +impl From + for StarknetApplicationRevisionRequest +{ + fn from(value: crate::client::env::config::query::application_revision::ApplicationRevisionRequest) -> Self { + StarknetApplicationRevisionRequest { + context_id: (*value.context_id).into(), } } } -impl From<&Repr> for ContextIdentity { - fn from(value: &Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextIdentity { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } +#[derive(Debug, Encode)] +pub struct Signed { + pub payload: Vec, + pub signature_r: Felt, + pub signature_s: Felt, +} + +// Add reverse conversions for IDs +impl From for crate::types::ApplicationId { + fn from(value: ApplicationId) -> Self { + let FeltPair { high, low } = value.0; + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") } } -impl From<&Repr> for ContextId { - fn from(value: &Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - ContextId { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } +impl From for crate::types::ContextId { + fn from(value: ContextId) -> Self { + let FeltPair { high, low } = value.0; + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") } } -// Add this new struct -#[derive(Debug, Encode)] -pub struct StarknetApplicationRevisionRequest { - pub context_id: ContextId, +impl From for crate::types::BlobId { + fn from(value: ApplicationBlob) -> Self { + let FeltPair { high, low } = value.0; + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") + } } -// Add From implementation -impl From - for StarknetApplicationRevisionRequest -{ - fn from( - value: crate::client::env::config::query::application_revision::ApplicationRevisionRequest, - ) -> Self { - StarknetApplicationRevisionRequest { - context_id: value.context_id.into(), +impl<'a> From for crate::types::ApplicationSource<'a> { + fn from(value: EncodableString) -> Self { + crate::types::ApplicationSource(std::borrow::Cow::Owned(value.0)) + } +} + +impl<'a> From for crate::types::ApplicationMetadata<'a> { + fn from(value: EncodableString) -> Self { + crate::types::ApplicationMetadata(Repr::new(std::borrow::Cow::Owned(value.0.into_bytes()))) + } +} + +#[derive(Debug, Decode)] +pub struct StarknetPrivilegeEntry { + pub identity: ContextIdentity, + pub capabilities: Vec +} + +#[derive(Debug, Decode)] +pub struct StarknetPrivileges { + pub privileges: Vec +} + +impl From for BTreeMap> { + fn from(value: StarknetPrivileges) -> Self { + value.privileges + .into_iter() + .map(|entry| (entry.identity.into(), + entry.capabilities.into_iter().map(Into::into).collect())) + .collect() + } +} + +// Add conversion from Starknet Capability to domain Capability +impl From for crate::Capability { + fn from(value: Capability) -> Self { + match value { + Capability::ManageApplication => crate::Capability::ManageApplication, + Capability::ManageMembers => crate::Capability::ManageMembers, + Capability::ProxyCode => crate::Capability::Proxy, } } } + +// Add conversion from Starknet ContextIdentity to SignerId +impl From for SignerId { + fn from(value: ContextIdentity) -> Self { + let FeltPair { high, low } = value.0; + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") + } +} + +#[derive(Debug, Decode)] +pub struct StarknetMembers { + pub members: Vec +} + +impl From for Vec { + fn from(value: StarknetMembers) -> Self { + value.members + .into_iter() + .map(|id| id.into()) + .collect() + } +} + +// Add conversion from Starknet ContextIdentity to domain ContextIdentity +impl From for crate::types::ContextIdentity { + fn from(value: ContextIdentity) -> Self { + let FeltPair { high, low } = value.0; + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") + } +} diff --git a/crates/context/config/src/client/env/proxy/mutate.rs b/crates/context/config/src/client/env/proxy/mutate.rs index 5314042cc..726e9ef4a 100644 --- a/crates/context/config/src/client/env/proxy/mutate.rs +++ b/crates/context/config/src/client/env/proxy/mutate.rs @@ -9,8 +9,8 @@ use crate::client::protocol::near::Near; use crate::client::protocol::starknet::Starknet; use crate::client::transport::Transport; use crate::client::{CallClient, ClientError, Operation}; -use crate::repr::ReprBytes; -use crate::types::{ProposalId, Signed, SignerId}; +use crate::types::Signed; +use crate::repr::ReprTransmute; use crate::{ProposalWithApprovals, ProxyMutateRequest, Repr}; pub mod methods; @@ -64,10 +64,8 @@ impl Method for Mutate { let verifying_key_bytes = verifying_key.to_bytes_be(); // Create signer_id from ECDSA verifying key for signature verification - let signer_id = Repr::new(SignerId::from_bytes(|bytes| { - bytes.copy_from_slice(&verifying_key_bytes); - Ok(32) - })?); + let signer_id = verifying_key_bytes.rt().expect("Infallible conversion"); + let signer_id = Repr::new(signer_id); // Create request with signer_id let request = StarknetProxyMutateRequest::from((signer_id, self.raw_request)); @@ -104,12 +102,10 @@ impl Method for Mutate { let response = &response[32..]; // Get proposal_id from the next 64 bytes (32 for high, 32 for low) - let proposal_id = Repr::new(ProposalId::from_bytes(|bytes| { - // Take 16 bytes from high and 16 bytes from low - bytes[..16].copy_from_slice(&response[16..32]); // Last 16 bytes of high - bytes[16..].copy_from_slice(&response[48..64]); // Last 16 bytes of low - Ok(32) - })?); + let mut proposal_bytes = [0u8; 32]; + proposal_bytes[..16].copy_from_slice(&response[16..32]); // Last 16 bytes of high + proposal_bytes[16..].copy_from_slice(&response[48..64]); // Last 16 bytes of low + let proposal_id = Repr::new(proposal_bytes.rt()?); // Get num_approvals from the last 32 bytes let num_approvals = u32::from_be_bytes(response[64..][28..32].try_into()?) as usize; diff --git a/crates/context/config/src/client/env/proxy/query/proposal.rs b/crates/context/config/src/client/env/proxy/query/proposal.rs index 487ee3cc6..aef1fafe9 100644 --- a/crates/context/config/src/client/env/proxy/query/proposal.rs +++ b/crates/context/config/src/client/env/proxy/query/proposal.rs @@ -38,10 +38,10 @@ impl Method for ProposalRequest { // Convert ProposalId to StarknetProposalId let starknet_id: StarknetProposalId = self.proposal_id.into(); - // Encode both high and low parts + // Encode both parts (0 is FeltPair) let mut encoded = Vec::new(); - encoded.extend_from_slice(&starknet_id.high.to_bytes_be()); - encoded.extend_from_slice(&starknet_id.low.to_bytes_be()); + encoded.extend_from_slice(&starknet_id.0.high.to_bytes_be()); + encoded.extend_from_slice(&starknet_id.0.low.to_bytes_be()); Ok(encoded) } diff --git a/crates/context/config/src/client/env/proxy/query/proposal_approvals.rs b/crates/context/config/src/client/env/proxy/query/proposal_approvals.rs index 390c2e131..0e4141888 100644 --- a/crates/context/config/src/client/env/proxy/query/proposal_approvals.rs +++ b/crates/context/config/src/client/env/proxy/query/proposal_approvals.rs @@ -42,8 +42,8 @@ impl Method for ProposalApprovalsRequest { // Encode both high and low parts let mut encoded = Vec::new(); - encoded.extend_from_slice(&starknet_id.high.to_bytes_be()); - encoded.extend_from_slice(&starknet_id.low.to_bytes_be()); + encoded.extend_from_slice(&starknet_id.0.high.to_bytes_be()); + encoded.extend_from_slice(&starknet_id.0.low.to_bytes_be()); Ok(encoded) } diff --git a/crates/context/config/src/client/env/proxy/query/proposal_approvers.rs b/crates/context/config/src/client/env/proxy/query/proposal_approvers.rs index 650b95f2e..8a21278da 100644 --- a/crates/context/config/src/client/env/proxy/query/proposal_approvers.rs +++ b/crates/context/config/src/client/env/proxy/query/proposal_approvers.rs @@ -5,6 +5,7 @@ use starknet::core::codec::Decode; use starknet::core::types::Felt; use super::ProposalId; +use crate::client::env::proxy::starknet::FeltPair; use crate::client::env::proxy::types::starknet::{StarknetApprovers, StarknetProposalId}; use crate::client::env::Method; use crate::client::protocol::near::Near; @@ -59,14 +60,15 @@ impl Method for ProposalApproversRequest { high[16..].copy_from_slice(high_bytes); // Put in last 16 bytes low[16..].copy_from_slice(low_bytes); // Put in last 16 bytes - let starknet_id = StarknetProposalId { + let starknet_id = StarknetProposalId(FeltPair { high: Felt::from_bytes_be(&high), low: Felt::from_bytes_be(&low), - }; + }); + // Encode exactly as in mutate response let mut encoded = Vec::new(); - encoded.extend_from_slice(&starknet_id.high.to_bytes_be()); - encoded.extend_from_slice(&starknet_id.low.to_bytes_be()); + encoded.extend_from_slice(&starknet_id.0.high.to_bytes_be()); + encoded.extend_from_slice(&starknet_id.0.low.to_bytes_be()); Ok(encoded) } diff --git a/crates/context/config/src/client/env/proxy/query/proposals.rs b/crates/context/config/src/client/env/proxy/query/proposals.rs index 706f48ec6..b53bc56d1 100644 --- a/crates/context/config/src/client/env/proxy/query/proposals.rs +++ b/crates/context/config/src/client/env/proxy/query/proposals.rs @@ -2,7 +2,7 @@ use serde::Serialize; use starknet::core::codec::Decode; use starknet_crypto::Felt; -use crate::client::env::proxy::types::starknet::StarknetProposal; +use crate::client::env::proxy::starknet::StarknetProposals; use crate::client::env::Method; use crate::client::protocol::near::Near; use crate::client::protocol::starknet::Starknet; @@ -48,60 +48,18 @@ impl Method for ProposalsRequest { } fn decode(response: Vec) -> eyre::Result { - // First, convert bytes back to Felts + // Convert bytes to Felts let mut felts = Vec::new(); for chunk in response.chunks(32) { if chunk.len() == 32 { felts.push(Felt::from_bytes_be(chunk.try_into().unwrap())); } } - // First felt is array length - let array_len = felts[0].to_bytes_be()[31] as usize; - // println!("Array length: {}", array_len); - let mut proposals = Vec::with_capacity(array_len); - let mut offset = 1; // Skip the length felt + // Skip version felt and decode the array + let proposals = StarknetProposals::decode(&felts[1..]) + .map_err(|e| eyre::eyre!("Failed to decode proposals: {:?}", e))?; - for i in 0..array_len { - // Each proposal starts with: - // - proposal_id: 2 felts (high, low) - // - author_id: 2 felts (high, low) - // - action variant: 1 felt - let variant = felts[offset + 4].to_bytes_be()[31]; - // println!("Proposal {} at offset {}, variant {}", i, offset, variant); - - let proposal_end = match variant { - 0 => { - // ExternalFunctionCall - let calldata_len = felts[offset + 7].to_bytes_be()[31] as usize; - offset + 8 + calldata_len // base + contract + selector + len + calldata - } - 1 => { - // Transfer - offset + 8 // base + amount(2) + receiver - } - 2 => { - // SetNumApprovals - offset + 6 // base + num - } - 3 => { - // SetActiveProposalsLimit - offset + 6 // base + limit - } - 4 => { - // SetContextValue - let key_len = felts[offset + 5].to_bytes_be()[31] as usize; - let value_len = felts[offset + 6 + key_len].to_bytes_be()[31] as usize; - offset + 7 + key_len + value_len // base + key_len + value_len + key + value - } - _ => return Err(eyre::eyre!("Unknown action variant: {}", variant)), - }; - - let proposal = StarknetProposal::decode(&felts[offset..proposal_end]) - .map_err(|e| eyre::eyre!("Failed to decode proposal {}: {:?}", i, e))?; - proposals.push(proposal.into()); - offset = proposal_end; - } - Ok(proposals) + Ok(proposals.into()) } } diff --git a/crates/context/config/src/client/env/proxy/types/starknet.rs b/crates/context/config/src/client/env/proxy/types/starknet.rs index 3b5aeb3f0..b669ba5db 100644 --- a/crates/context/config/src/client/env/proxy/types/starknet.rs +++ b/crates/context/config/src/client/env/proxy/types/starknet.rs @@ -1,44 +1,36 @@ use starknet::core::codec::{Decode, Encode}; use starknet::core::types::{Felt, U256}; - -use crate::repr::{Repr, ReprBytes, ReprTransmute}; +use crate::repr::{Repr, ReprTransmute, ReprBytes}; use crate::types::{ContextIdentity, ProposalId, SignerId}; -use crate::{ - Proposal, ProposalAction, ProposalApprovalWithSigner, ProposalWithApprovals, ProxyMutateRequest, -}; +use crate::{Proposal, ProposalAction, ProposalApprovalWithSigner, ProposalWithApprovals, ProxyMutateRequest}; #[derive(Debug, Encode, Decode)] -pub struct StarknetProposalId { +pub struct FeltPair { pub high: Felt, pub low: Felt, } -impl From> for StarknetProposalId { - fn from(value: Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - StarknetProposalId { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } - } -} +#[derive(Debug, Encode, Decode)] +pub struct StarknetIdentity(pub FeltPair); #[derive(Debug, Encode, Decode)] -pub struct StarknetIdentity { - pub high: Felt, - pub low: Felt, +pub struct StarknetProposalId(pub FeltPair); + +#[derive(Debug, Encode, Decode)] +pub struct StarknetU256(pub FeltPair); + +#[derive(Debug, Encode, Decode)] +pub struct StarknetProposal { + pub proposal_id: StarknetProposalId, + pub author_id: StarknetIdentity, + pub actions: StarknetProposalActionWithArgs, } -impl From> for StarknetIdentity { - fn from(value: Repr) -> Self { - let bytes = value.as_bytes(); - let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); - StarknetIdentity { - high: Felt::from_bytes_be_slice(high_bytes), - low: Felt::from_bytes_be_slice(low_bytes), - } - } +#[derive(Debug, Encode, Decode)] +pub struct StarknetConfirmationRequest { + pub proposal_id: StarknetProposalId, + pub signer_id: StarknetIdentity, + pub added_timestamp: Felt, } #[derive(Debug, Encode, Decode)] @@ -54,57 +46,114 @@ pub enum StarknetProxyMutateRequestKind { } #[derive(Debug, Encode, Decode)] -pub struct StarknetProposal { - pub proposal_id: StarknetProposalId, - pub author_id: StarknetIdentity, - pub actions: StarknetProposalActionWithArgs, +pub enum StarknetProposalActionWithArgs { + ExternalFunctionCall(Felt, Felt, Vec), + Transfer(Felt, StarknetU256), + SetNumApprovals(Felt), + SetActiveProposalsLimit(Felt), + SetContextValue(Vec, Vec), } #[derive(Debug, Encode, Decode)] -pub struct StarknetConfirmationRequest { +pub struct StarknetSignedRequest { + pub payload: Vec, + pub signature_r: Felt, + pub signature_s: Felt, +} + +#[derive(Debug, Decode)] +pub struct StarknetProposalWithApprovals { pub proposal_id: StarknetProposalId, - pub signer_id: StarknetIdentity, - pub added_timestamp: Felt, // u64 in contract + pub num_approvals: Felt, } -#[derive(Debug, Encode, Decode)] -pub struct StarknetU256 { - pub high: Felt, - pub low: Felt, +#[derive(Debug, Decode)] +pub struct StarknetApprovers { + pub approvers: Vec, +} + +#[derive(Debug, Decode)] +pub struct StarknetProposals { + pub proposals: Vec +} + +impl From for Vec { + fn from(value: StarknetProposals) -> Self { + value.proposals.into_iter().map(Into::into).collect() + } +} + +// Conversions for StarknetIdentity +impl From> for StarknetIdentity { + fn from(value: Repr) -> Self { + let bytes = value.as_bytes(); + let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); + StarknetIdentity(FeltPair { + high: Felt::from_bytes_be_slice(high_bytes), + low: Felt::from_bytes_be_slice(low_bytes), + }) + } +} + +impl From for SignerId { + fn from(value: StarknetIdentity) -> Self { + let FeltPair { high, low } = value.0; + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") + } } +// Conversions for ProposalId +impl From> for StarknetProposalId { + fn from(value: Repr) -> Self { + let bytes = value.as_bytes(); + let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2); + StarknetProposalId(FeltPair { + high: Felt::from_bytes_be_slice(high_bytes), + low: Felt::from_bytes_be_slice(low_bytes), + }) + } +} + +impl From for ProposalId { + fn from(value: StarknetProposalId) -> Self { + let FeltPair { high, low } = value.0; + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") + } +} + +// Conversions for U256 impl From for StarknetU256 { fn from(value: U256) -> Self { - StarknetU256 { - high: Felt::from(value.high()), // Get high 128 bits - low: Felt::from(value.low()), // Get low 128 bits - } + StarknetU256(FeltPair { + high: Felt::from(value.high()), + low: Felt::from(value.low()), + }) } } impl From for StarknetU256 { fn from(value: u128) -> Self { - StarknetU256 { + StarknetU256(FeltPair { high: Felt::ZERO, low: Felt::from(value), - } + }) } } -#[derive(Debug, Encode, Decode)] -pub enum StarknetProposalActionWithArgs { - ExternalFunctionCall(Felt, Felt, Vec), - Transfer(Felt, StarknetU256), - SetNumApprovals(Felt), - SetActiveProposalsLimit(Felt), - SetContextValue(Vec, Vec), -} - -#[derive(Debug, Encode, Decode)] -pub struct StarknetSignedRequest { - pub payload: Vec, - pub signature_r: Felt, - pub signature_s: Felt, +// Conversions for ProxyMutateRequest +impl From<(Repr, ProxyMutateRequest)> for StarknetProxyMutateRequest { + fn from((signer_id, request): (Repr, ProxyMutateRequest)) -> Self { + StarknetProxyMutateRequest { + signer_id: signer_id.into(), + kind: request.into(), + } + } } impl From for StarknetProxyMutateRequestKind { @@ -120,6 +169,7 @@ impl From for StarknetProxyMutateRequestKind { } } +// Conversions for Proposal impl From for StarknetProposal { fn from(proposal: Proposal) -> Self { StarknetProposal { @@ -130,6 +180,17 @@ impl From for StarknetProposal { } } +impl From for Proposal { + fn from(value: StarknetProposal) -> Self { + Proposal { + id: Repr::new(value.proposal_id.into()), + author_id: Repr::new(value.author_id.into()), + actions: vec![value.actions.into()], + } + } +} + +// Conversions for ProposalApproval impl From for StarknetConfirmationRequest { fn from(approval: ProposalApprovalWithSigner) -> Self { StarknetConfirmationRequest { @@ -140,47 +201,20 @@ impl From for StarknetConfirmationRequest { } } -impl From<(Repr, ProxyMutateRequest)> for StarknetProxyMutateRequest { - fn from((signer_id, request): (Repr, ProxyMutateRequest)) -> Self { - StarknetProxyMutateRequest { - signer_id: signer_id.into(), - kind: request.into(), - } - } -} - +// Conversions for Actions impl From> for StarknetProposalActionWithArgs { fn from(actions: Vec) -> Self { - let action = actions - .into_iter() - .next() - .expect("At least one action required"); - + let action = actions.into_iter().next().expect("At least one action required"); match action { - ProposalAction::ExternalFunctionCall { - receiver_id, - method_name, - args, - deposit, - gas, - } => { - // Parse the args string (which is a JSON array of strings) into Felts - let args_vec: Vec = - serde_json::from_str(&args).unwrap_or_else(|_| Vec::new()); - - let felt_args: Vec = args_vec - .iter() - .map(|arg| { - // If the arg starts with "0x", parse as hex - if arg.starts_with("0x") { - // Use from_hex_unchecked for hex strings - Felt::from_hex_unchecked(arg) - } else { - // Otherwise parse as bytes - Felt::from_bytes_be_slice(arg.as_bytes()) - } - }) - .collect(); + ProposalAction::ExternalFunctionCall { receiver_id, method_name, args, .. } => { + let args_vec: Vec = serde_json::from_str(&args).unwrap_or_default(); + let felt_args = args_vec.iter().map(|arg| { + if arg.starts_with("0x") { + Felt::from_hex_unchecked(arg) + } else { + Felt::from_bytes_be_slice(arg.as_bytes()) + } + }).collect(); StarknetProposalActionWithArgs::ExternalFunctionCall( Felt::from_bytes_be_slice(receiver_id.as_bytes()), @@ -188,56 +222,28 @@ impl From> for StarknetProposalActionWithArgs { felt_args, ) } - ProposalAction::Transfer { - receiver_id, - amount, - } => { + ProposalAction::Transfer { receiver_id, amount } => { StarknetProposalActionWithArgs::Transfer( Felt::from_bytes_be_slice(receiver_id.as_bytes()), - amount.into(), // converts to StarknetU256 + amount.into(), ) } ProposalAction::SetNumApprovals { num_approvals } => { StarknetProposalActionWithArgs::SetNumApprovals(Felt::from(num_approvals)) } - ProposalAction::SetActiveProposalsLimit { - active_proposals_limit, - } => StarknetProposalActionWithArgs::SetActiveProposalsLimit(Felt::from( - active_proposals_limit, - )), + ProposalAction::SetActiveProposalsLimit { active_proposals_limit } => { + StarknetProposalActionWithArgs::SetActiveProposalsLimit(Felt::from(active_proposals_limit)) + } ProposalAction::SetContextValue { key, value } => { StarknetProposalActionWithArgs::SetContextValue( - key.chunks(16) - .map(|chunk| Felt::from_bytes_be_slice(chunk)) - .collect(), - value - .chunks(16) - .map(|chunk| Felt::from_bytes_be_slice(chunk)) - .collect(), + key.chunks(16).map(Felt::from_bytes_be_slice).collect(), + value.chunks(16).map(Felt::from_bytes_be_slice).collect(), ) } } } } -impl From for Proposal { - fn from(sp: StarknetProposal) -> Self { - let mut proposal_id = [0u8; 32]; - proposal_id[..16].copy_from_slice(&sp.proposal_id.high.to_bytes_be()[16..]); - proposal_id[16..].copy_from_slice(&sp.proposal_id.low.to_bytes_be()[16..]); - - let mut author_id = [0u8; 32]; - author_id[..16].copy_from_slice(&sp.author_id.high.to_bytes_be()[16..]); - author_id[16..].copy_from_slice(&sp.author_id.low.to_bytes_be()[16..]); - - Proposal { - id: proposal_id.rt().expect("infallible conversion"), - author_id: author_id.rt().expect("infallible conversion"), - actions: vec![sp.actions.into()], - } - } -} - impl From for ProposalAction { fn from(action: StarknetProposalActionWithArgs) -> Self { match action { @@ -245,8 +251,7 @@ impl From for ProposalAction { ProposalAction::ExternalFunctionCall { receiver_id: format!("0x{}", hex::encode(contract.to_bytes_be())), method_name: format!("0x{}", hex::encode(selector.to_bytes_be())), - args: calldata - .iter() + args: calldata.iter() .map(|felt| format!("0x{}", hex::encode(felt.to_bytes_be()))) .collect::>() .join(","), @@ -255,27 +260,21 @@ impl From for ProposalAction { } } StarknetProposalActionWithArgs::Transfer(receiver, amount) => { + let FeltPair { high, low } = amount.0; ProposalAction::Transfer { receiver_id: format!("0x{}", hex::encode(receiver.to_bytes_be())), - amount: u128::from_be_bytes( - amount.low.to_bytes_be()[16..32].try_into().unwrap(), - ) + (u128::from_be_bytes( - amount.high.to_bytes_be()[16..32].try_into().unwrap(), - ) << 64), + amount: u128::from_be_bytes(low.to_bytes_be()[16..32].try_into().unwrap()) + + (u128::from_be_bytes(high.to_bytes_be()[16..32].try_into().unwrap()) << 64), } } StarknetProposalActionWithArgs::SetNumApprovals(num) => { ProposalAction::SetNumApprovals { - num_approvals: u32::from_be_bytes( - num.to_bytes_be()[28..32].try_into().unwrap(), - ), + num_approvals: u32::from_be_bytes(num.to_bytes_be()[28..32].try_into().unwrap()), } } StarknetProposalActionWithArgs::SetActiveProposalsLimit(limit) => { ProposalAction::SetActiveProposalsLimit { - active_proposals_limit: u32::from_be_bytes( - limit.to_bytes_be()[28..32].try_into().unwrap(), - ), + active_proposals_limit: u32::from_be_bytes(limit.to_bytes_be()[28..32].try_into().unwrap()), } } StarknetProposalActionWithArgs::SetContextValue(key, value) => { @@ -284,55 +283,28 @@ impl From for ProposalAction { value: value.iter().flat_map(|felt| felt.to_bytes_be()).collect(), } } - _ => panic!("Unsupported action type"), } } } -#[derive(Debug, Decode)] -pub struct StarknetProposalWithApprovals { - pub proposal_id: StarknetProposalId, - pub num_approvals: Felt, -} - impl From for ProposalWithApprovals { - fn from(spa: StarknetProposalWithApprovals) -> Self { + fn from(value: StarknetProposalWithApprovals) -> Self { ProposalWithApprovals { - proposal_id: Repr::new( - ProposalId::from_bytes(|bytes| { - let mut full_bytes = Vec::with_capacity(64); - full_bytes.extend_from_slice(&spa.proposal_id.high.to_bytes_be()); - full_bytes.extend_from_slice(&spa.proposal_id.low.to_bytes_be()); - bytes.copy_from_slice(&full_bytes); - Ok(64) - }) - .expect("Valid proposal ID"), - ), - num_approvals: u32::from_be_bytes( - spa.num_approvals.to_bytes_be()[28..32].try_into().unwrap(), - ) as usize, + proposal_id: Repr::new(value.proposal_id.into()), + num_approvals: u32::from_be_bytes(value.num_approvals.to_bytes_be()[28..32].try_into().unwrap()) as usize, } } } -#[derive(Debug, Decode)] -pub struct StarknetApprovers { - pub approvers: Vec, -} - impl From for Vec { - fn from(sa: StarknetApprovers) -> Self { - sa.approvers + fn from(value: StarknetApprovers) -> Self { + value.approvers .into_iter() .map(|identity| { - ContextIdentity::from_bytes(|bytes| { - let mut combined = [0u8; 32]; - combined[..16].copy_from_slice(&identity.high.to_bytes_be()[16..]); - combined[16..].copy_from_slice(&identity.low.to_bytes_be()[16..]); // Changed this line - bytes.copy_from_slice(&combined); - Ok(32) - }) - .expect("Valid identity") + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&identity.0.high.to_bytes_be()[16..]); + bytes[16..].copy_from_slice(&identity.0.low.to_bytes_be()[16..]); + bytes.rt().expect("Infallible conversion") }) .collect() } diff --git a/crates/context/config/src/client/protocol/starknet.rs b/crates/context/config/src/client/protocol/starknet.rs index 2d75ac10e..e5f4a295c 100644 --- a/crates/context/config/src/client/protocol/starknet.rs +++ b/crates/context/config/src/client/protocol/starknet.rs @@ -250,7 +250,6 @@ impl Network { |result| { Ok(result .into_iter() - // Remove the skip(1) here .map(|felt| felt.to_bytes_be()) .flatten() .collect::>()) @@ -368,8 +367,8 @@ impl Network { reason: format!("Failed to decode event: {:?}", e), })?; let mut encoded = vec![0u8; 32]; - encoded.extend_from_slice(&result.proposal_id.high.to_bytes_be()); - encoded.extend_from_slice(&result.proposal_id.low.to_bytes_be()); + encoded.extend_from_slice(&result.proposal_id.0.high.to_bytes_be()); + encoded.extend_from_slice(&result.proposal_id.0.low.to_bytes_be()); encoded.extend_from_slice(&result.num_approvals.to_bytes_be()); return Ok(encoded); } diff --git a/crates/context/config/src/types.rs b/crates/context/config/src/types.rs index df868e263..a193e3547 100644 --- a/crates/context/config/src/types.rs +++ b/crates/context/config/src/types.rs @@ -307,12 +307,13 @@ impl ReprBytes for VerifyingKey { pub enum Capability { ManageApplication, ManageMembers, + Proxy, } #[derive(Eq, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Signed { - pub payload: Repr>, - pub signature: Repr, + payload: Repr>, + signature: Repr, #[serde(skip)] _priv: PhantomData, diff --git a/crates/context/src/lib.rs b/crates/context/src/lib.rs index 122bcaadf..bd64e770b 100644 --- a/crates/context/src/lib.rs +++ b/crates/context/src/lib.rs @@ -368,19 +368,9 @@ impl ContextManager { None }; - let context = match self + let context = self .internal_sync_context_config(context_id, config.as_mut()) - .await - { - Ok(ctx) => { - println!("Successfully got context from internal_sync"); - ctx - } - Err(e) => { - println!("Error in internal_sync_context_config: {:?}", e); - return Err(e); - } - }; + .await?; if !handle.has(&identity_key)? { bail!("unable to join context: not a member, invalid invitation?") diff --git a/crates/node/src/sync.rs b/crates/node/src/sync.rs index 9ad04327d..0f7b066bd 100644 --- a/crates/node/src/sync.rs +++ b/crates/node/src/sync.rs @@ -106,7 +106,7 @@ impl Node { chosen_peer: PeerId, ) -> EyreResult<()> { let mut context = self.ctx_manager.sync_context_config(context_id).await?; - println!("initiating sync for context: {:?}", context); + let Some(application) = self.ctx_manager.get_application(&context.application_id)? else { bail!("application not found: {}", context.application_id); }; @@ -123,7 +123,6 @@ impl Node { .await?; if !self.ctx_manager.has_blob_available(application.blob)? { - println!("blob not available, initiating blob share process"); self.initiate_blob_share_process( &context, our_identity, diff --git a/crates/server/src/admin/handlers/proposals.rs b/crates/server/src/admin/handlers/proposals.rs index 53d3777a4..2b81080a1 100644 --- a/crates/server/src/admin/handlers/proposals.rs +++ b/crates/server/src/admin/handlers/proposals.rs @@ -15,7 +15,7 @@ use crate::AdminState; //todo split it up into separate files -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Copy, Clone)] #[serde(rename_all = "camelCase")] pub enum ActionType { ExternalFunctionCall,