Skip to content

Commit

Permalink
feat: Context and Proxy Starknet contract integration into Calimero (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
alenmestrov authored Nov 26, 2024
1 parent 55c6121 commit 805cc18
Show file tree
Hide file tree
Showing 28 changed files with 2,775 additions and 1,147 deletions.
2,081 changes: 1,038 additions & 1,043 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,6 @@ multiple_crate_versions = "allow" # Cannot resolve all these
missing_errors_doc = "allow" # TODO: Remove later once documentation has been added
missing_panics_doc = "allow" # TODO: Remove later once documentation has been added
future_not_send = "allow" # TODO: Remove later once Send is implemented

[patch.crates-io]
starknet = { git = "https://github.com/xJonathanLEI/starknet-rs", rev = "5c676a6" }
12 changes: 12 additions & 0 deletions contracts/context-config/src/mutate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -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!(
Expand Down
1 change: 1 addition & 0 deletions crates/context/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ borsh = { workspace = true, features = ["derive"] }
ed25519-dalek.workspace = true
either = { workspace = true, optional = true }
eyre = { workspace = true, optional = true }
hex.workspace = true
near-crypto = { workspace = true, optional = true }
near-jsonrpc-client = { workspace = true, optional = true }
near-jsonrpc-primitives = { workspace = true, optional = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/context/config/src/client/env/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::client::{CallClient, Environment};

mod mutate;
mod query;

mod types;
use mutate::ContextConfigMutate;
use query::ContextConfigQuery;

Expand Down
80 changes: 66 additions & 14 deletions crates/context/config/src/client/env/config/mutate.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use std::fmt::Debug;

use ed25519_dalek::{Signer, SigningKey};
use starknet::core::codec::Encode;
use starknet::signers::SigningKey as StarknetSigningKey;
use starknet_crypto::{poseidon_hash_many, Felt};

use super::types::starknet::{Request as StarknetRequest, Signed as StarknetSigned};
use crate::client::env::{utils, Method};
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::ReprTransmute;
use crate::repr::{Repr, ReprTransmute};
use crate::types::Signed;
use crate::{Request, RequestKind};

use crate::{ContextIdentity, Request, RequestKind};
pub mod methods;

#[derive(Debug)]
Expand All @@ -25,6 +30,7 @@ pub struct ContextConfigMutateRequest<'a, T> {
#[derive(Debug)]
struct Mutate<'a> {
pub(crate) signing_key: [u8; 32],
pub(crate) nonce: u64,
pub(crate) kind: RequestKind<'a>,
}

Expand Down Expand Up @@ -56,31 +62,77 @@ impl<'a> Method<Near> for Mutate<'a> {

impl<'a> Method<Starknet> for Mutate<'a> {
type Returns = ();

const METHOD: &'static str = "mutate";

fn encode(self) -> eyre::Result<Vec<u8>> {
// sign the params, encode it and return
// since you will have a `Vec<Felt>` here, you can
// `Vec::with_capacity(32 * calldata.len())` and then
// extend the `Vec` with each `Felt::to_bytes_le()`
// when this `Vec<u8>` makes it to `StarknetTransport`,
// reconstruct the `Vec<Felt>` from it
todo!()
// Derive ecdsa key from private key
let secret_scalar = Felt::from_bytes_be(&self.signing_key);
let signing_key = StarknetSigningKey::from_secret_scalar(secret_scalar);
let verifying_key = signing_key.verifying_key().scalar();
let verifying_key_bytes = verifying_key.to_bytes_be();

// Derive ed25519 key from private key
let user_key = SigningKey::from_bytes(&self.signing_key).verifying_key();
let user_key_bytes = user_key.to_bytes();

// Create Repr wrapped ContextIdentity instances
let signer_id = verifying_key_bytes
.rt::<ContextIdentity>()
.map_err(|e| eyre::eyre!("Failed to convert verifying key: {}", e))?;
let signer_id = Repr::new(signer_id);

let user_id = user_key_bytes
.rt::<ContextIdentity>()
.map_err(|e| eyre::eyre!("Failed to convert user key: {}", e))?;
let user_id = Repr::new(user_id);

// Create the Request structure using into() conversions
let request = StarknetRequest {
signer_id: signer_id.into(),
user_id: user_id.into(),
nonce: self.nonce,
kind: self.kind.into(),
};

// Serialize the request
let mut serialized_request = vec![];
request.encode(&mut serialized_request)?;

// Hash the serialized request
let hash = poseidon_hash_many(&serialized_request);

// Sign the hash with the signing key
let signature = signing_key.sign(&hash)?;

let signed_request = StarknetSigned {
payload: serialized_request,
signature_r: signature.r,
signature_s: signature.s,
};

let mut signed_request_serialized = vec![];
signed_request.encode(&mut signed_request_serialized)?;

let bytes: Vec<u8> = signed_request_serialized
.iter()
.flat_map(|felt| felt.to_bytes_be())
.collect();

Ok(bytes)
}

fn decode(_response: Vec<u8>) -> eyre::Result<Self::Returns> {
todo!()
Ok(())
}
}

impl<'a, T: Transport> ContextConfigMutateRequest<'a, T> {
impl<'a, T: Transport + Debug> ContextConfigMutateRequest<'a, T> {
pub async fn send(self, signing_key: [u8; 32]) -> Result<(), ClientError<T>> {
let request = Mutate {
signing_key,
// todo! when nonces are implemented in context
// todo! config contract, we fetch it here first
// nonce: _,
nonce: 0,
kind: self.kind,
};

Expand Down
49 changes: 46 additions & 3 deletions crates/context/config/src/client/env/config/query/application.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use serde::Serialize;
use starknet::core::codec::{Decode, Encode};
use starknet_crypto::Felt;

use crate::client::env::config::types::starknet::{
Application as StarknetApplication, CallData, FeltPair,
};
use crate::client::env::Method;
use crate::client::protocol::near::Near;
use crate::client::protocol::starknet::Starknet;
Expand Down Expand Up @@ -41,10 +46,48 @@ impl Method<Starknet> for ApplicationRequest {
const METHOD: &'static str = "application";

fn encode(self) -> eyre::Result<Vec<u8>> {
todo!()
let felt_pair: FeltPair = self.context_id.into();
let mut call_data = CallData::default();
felt_pair.encode(&mut call_data)?;
Ok(call_data.0)
}

fn decode(_response: Vec<u8>) -> eyre::Result<Self::Returns> {
todo!()
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
if response.is_empty() {
return Err(eyre::eyre!("No application found"));
}

if response.len() % 32 != 0 {
return Err(eyre::eyre!(
"Invalid response length: {} bytes is not a multiple of 32",
response.len()
));
}

// Convert bytes to Felts
let mut felts = Vec::new();
let chunks = response.chunks_exact(32);

// Verify no remainder
if !chunks.remainder().is_empty() {
return Err(eyre::eyre!("Response length is not a multiple of 32 bytes"));
}

for chunk in chunks {
let chunk_array: [u8; 32] = chunk
.try_into()
.map_err(|e| eyre::eyre!("Failed to convert chunk to array: {}", e))?;
felts.push(Felt::from_bytes_be(&chunk_array));
}

if felts.is_empty() {
return Err(eyre::eyre!("No felts decoded from response"));
}

// Skip 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())
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use serde::Serialize;
use starknet::core::codec::Encode;

use crate::client::env::config::types::starknet::{CallData, FeltPair};
use crate::client::env::Method;
use crate::client::protocol::near::Near;
use crate::client::protocol::starknet::Starknet;
use crate::repr::Repr;
use crate::types::{ContextId, Revision};

#[derive(Copy, Clone, Debug, Serialize)]
pub(super) struct ApplicationRevisionRequest {
pub(super) context_id: Repr<ContextId>,
pub struct ApplicationRevisionRequest {
pub context_id: Repr<ContextId>,
}

impl Method<Near> for ApplicationRevisionRequest {
Expand All @@ -31,10 +33,24 @@ impl Method<Starknet> for ApplicationRevisionRequest {
const METHOD: &'static str = "application_revision";

fn encode(self) -> eyre::Result<Vec<u8>> {
todo!()
let felt_pair: FeltPair = self.context_id.into();
let mut call_data = CallData::default();
felt_pair.encode(&mut call_data)?;
Ok(call_data.0)
}

fn decode(_response: Vec<u8>) -> eyre::Result<Self::Returns> {
todo!()
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
if response.len() != 32 {
return Err(eyre::eyre!(
"Invalid response length: expected 32 bytes, got {}",
response.len()
));
}

// Response should be a single u64 in the last 8 bytes of a felt
let revision_bytes = &response[24..32]; // Take last 8 bytes
let revision = u64::from_be_bytes(revision_bytes.try_into()?);

Ok(revision)
}
}
37 changes: 34 additions & 3 deletions crates/context/config/src/client/env/config/query/has_member.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use serde::Serialize;
use starknet::core::codec::Encode;

use crate::client::env::config::types::starknet::{CallData, FeltPair};
use crate::client::env::Method;
use crate::client::protocol::near::Near;
use crate::client::protocol::starknet::Starknet;
Expand Down Expand Up @@ -32,10 +34,39 @@ impl Method<Starknet> for HasMemberRequest {
const METHOD: &'static str = "has_member";

fn encode(self) -> eyre::Result<Vec<u8>> {
todo!()
let mut call_data = CallData::default();

// Encode context_id
let context_pair: FeltPair = self.context_id.into();
context_pair.encode(&mut call_data)?;

// Encode identity
let identity_pair: FeltPair = self.identity.into();
identity_pair.encode(&mut call_data)?;

Ok(call_data.0)
}

fn decode(_response: Vec<u8>) -> eyre::Result<Self::Returns> {
todo!()
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
if response.len() != 32 {
return Err(eyre::eyre!(
"Invalid response length: expected 32 bytes, got {}",
response.len()
));
}

// Check if all bytes except the last one are zero
if !response[..31].iter().all(|&b| b == 0) {
return Err(eyre::eyre!(
"Invalid response format: non-zero bytes in prefix"
));
}

// Check the last byte is either 0 or 1
match response[31] {
0 => Ok(false),
1 => Ok(true),
v => Err(eyre::eyre!("Invalid boolean value: {}", v)),
}
}
}
Loading

0 comments on commit 805cc18

Please sign in to comment.