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

feat: Context and Proxy Starknet contract integration into Calimero #981

Merged
merged 26 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3498699
init commit for starknet config contract
alenmestrov Nov 6, 2024
164eb6b
linter
alenmestrov Nov 6, 2024
4acd00b
fix: merged master
alenmestrov Nov 20, 2024
c42d235
feat: integration of Context and Proxy Starknet contracts into Calimero
alenmestrov Nov 21, 2024
6c8d38f
fix: linter, parse correctly ProposalID
alenmestrov Nov 22, 2024
659f8d0
fix: linter
alenmestrov Nov 22, 2024
e6c2420
updated types and finalized waiting for execution of transaction
alenmestrov Nov 22, 2024
9558750
fix: lint#
alenmestrov Nov 22, 2024
6c35494
fix: resolved comments from PR
alenmestrov Nov 24, 2024
bf45efe
fix: linter
alenmestrov Nov 24, 2024
9b0efbc
fix: updated contract class for Starknet, improved querying by changi…
alenmestrov Nov 25, 2024
279f1c0
fix: linter
alenmestrov Nov 25, 2024
b00358d
fix: updated String decode method for starkneT
alenmestrov Nov 25, 2024
ca58dd9
fix: linter
alenmestrov Nov 25, 2024
28bd6d1
fix: resolving build errors
alenmestrov Nov 25, 2024
ae355e0
fix: linter
alenmestrov Nov 25, 2024
e2b0544
fix: fixed build issues2
alenmestrov Nov 25, 2024
56ee2b4
fix: linter
alenmestrov Nov 25, 2024
52c530d
fix: build process fixes
alenmestrov Nov 25, 2024
945d69c
fix: linter
alenmestrov Nov 25, 2024
670c21d
fix: resolved PR comments
alenmestrov Nov 26, 2024
c0ddb43
fix: linter
alenmestrov Nov 26, 2024
32de915
fix: dependecy override
alenmestrov Nov 26, 2024
63c1a57
fix: resolved PR comments
alenmestrov Nov 26, 2024
5b758d7
Merge branch 'master' into feat/context_contract_starknet
alenmestrov Nov 26, 2024
a6bedf4
fix: linter
alenmestrov Nov 26, 2024
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
2,082 changes: 1,040 additions & 1,042 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ semver = "1.0.22"
serde = "1.0.196"
serde_json = "1.0.113"
serde_with = "3.9.0"
starknet = "0.12.0"
starknet = { git = "https://github.com/xJonathanLEI/starknet-rs" }
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
starknet-crypto = "0.7.1"
starknet-types-core = "0.1.7"
strum = "0.26.2"
Expand Down
4 changes: 4 additions & 0 deletions crates/context/Cargo.toml
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ 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
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"] }
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
82 changes: 68 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, ReprBytes, 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,28 +62,76 @@ 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 = Repr::new(ContextIdentity::from_bytes(|bytes| {
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
bytes.copy_from_slice(&verifying_key_bytes);
Ok(32)
})?);

let user_id = Repr::new(ContextIdentity::from_bytes(|bytes| {
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
bytes.copy_from_slice(&user_key_bytes);
Ok(32)
})?);

// Create the Request structure using into() conversions
let request = StarknetRequest {
signer_id: signer_id.into(),
user_id: user_id.into(),
nonce: 0,
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
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!()
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
println!("decode {:?}", response);
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,
nonce: 0,
// todo! when nonces are implemented in context
// todo! config contract, we fetch it here first
// nonce: _,
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
68 changes: 64 additions & 4 deletions crates/context/config/src/client/env/config/query/application.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use serde::Serialize;
use starknet_crypto::Felt;

use crate::client::env::Method;
use crate::client::protocol::near::Near;
use crate::client::protocol::starknet::Starknet;
use crate::repr::Repr;
use crate::repr::{Repr, ReprBytes, ReprTransmute};
use crate::types::{Application, ApplicationMetadata, ApplicationSource, ContextId};

#[derive(Copy, Clone, Debug, Serialize)]
Expand Down Expand Up @@ -41,10 +42,69 @@ impl Method<Starknet> for ApplicationRequest {
const METHOD: &'static str = "application";

fn encode(self) -> eyre::Result<Vec<u8>> {
todo!()
// Split context_id into high/low parts
let bytes = self.context_id.as_bytes();
let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2);

// Convert to Felts
let high_felt = Felt::from_bytes_be_slice(high_bytes);
let low_felt = Felt::from_bytes_be_slice(low_bytes);

// Convert both Felts to bytes and concatenate
let mut result = Vec::new();
result.extend_from_slice(&high_felt.to_bytes_be());
result.extend_from_slice(&low_felt.to_bytes_be());
Ok(result)
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
}

fn decode(_response: Vec<u8>) -> eyre::Result<Self::Returns> {
todo!()
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
if response.is_empty() {
return Err(eyre::eyre!("No application found"));
}

// Check if it's a None response (single zero Felt)
if response.len() == 32 && response.iter().all(|&x| x == 0) {
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
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)
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
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)
alenmestrov marked this conversation as resolved.
Show resolved Hide resolved
let metadata_bytes: Vec<u8> = 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))
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use serde::Serialize;
use starknet_crypto::Felt;

use crate::client::env::Method;
use crate::client::protocol::near::Near;
use crate::client::protocol::starknet::Starknet;
use crate::repr::Repr;
use crate::repr::{Repr, ReprBytes};
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 +32,30 @@ impl Method<Starknet> for ApplicationRevisionRequest {
const METHOD: &'static str = "application_revision";

fn encode(self) -> eyre::Result<Vec<u8>> {
todo!()
// Split context_id into high/low parts
let bytes = self.context_id.as_bytes();
let (high_bytes, low_bytes) = bytes.split_at(bytes.len() / 2);

// Convert to Felts
let high_felt = Felt::from_bytes_be_slice(high_bytes);
let low_felt = Felt::from_bytes_be_slice(low_bytes);

// Convert both Felts to bytes and concatenate
let mut result = Vec::new();
result.extend_from_slice(&high_felt.to_bytes_be());
result.extend_from_slice(&low_felt.to_bytes_be());
Ok(result)
}

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!("Empty response"));
}

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

Ok(revision)
}
}
40 changes: 37 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,9 +1,11 @@
use serde::Serialize;
use starknet_crypto::Felt;

use crate::client::env::Method;
use crate::client::protocol::near::Near;
use crate::client::protocol::starknet::Starknet;
use crate::repr::Repr;
use crate::repr::ReprBytes;
use crate::types::{ContextId, ContextIdentity};

#[derive(Copy, Clone, Debug, Serialize)]
Expand Down Expand Up @@ -32,10 +34,42 @@ impl Method<Starknet> for HasMemberRequest {
const METHOD: &'static str = "has_member";

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

// Encode context_id (2 felts)
let context_bytes = self.context_id.as_bytes();
let (context_high, context_low) = context_bytes.split_at(context_bytes.len() / 2);

// Convert to Felts and add to result
let context_high_felt = Felt::from_bytes_be_slice(context_high);
let context_low_felt = Felt::from_bytes_be_slice(context_low);
result.extend_from_slice(&context_high_felt.to_bytes_be());
result.extend_from_slice(&context_low_felt.to_bytes_be());

// Encode member identity (2 felts)
let identity_bytes = self.identity.as_bytes();
let (identity_high, identity_low) = identity_bytes.split_at(identity_bytes.len() / 2);

// Convert to Felts and add to result
let identity_high_felt = Felt::from_bytes_be_slice(identity_high);
let identity_low_felt = Felt::from_bytes_be_slice(identity_low);
result.extend_from_slice(&identity_high_felt.to_bytes_be());
result.extend_from_slice(&identity_low_felt.to_bytes_be());

Ok(result)
}

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!("Empty response"));
}

// Response should be a single felt (32 bytes) representing 0 or 1
if response.len() != 32 {
return Err(eyre::eyre!("Invalid response length"));
}

// Check the last byte for 0 or 1
Ok(response[31] == 1)
}
}
Loading
Loading