diff --git a/crates/context/config/src/client/env/config/mutate.rs b/crates/context/config/src/client/env/config/mutate.rs index 51e68c6a9..9d6efc823 100644 --- a/crates/context/config/src/client/env/config/mutate.rs +++ b/crates/context/config/src/client/env/config/mutate.rs @@ -159,12 +159,10 @@ impl<'a> Method for Mutate<'a> { } impl<'a, T: Transport> ContextConfigMutateRequest<'a, T> { - pub async fn send(self, signing_key: [u8; 32]) -> Result<(), ClientError> { + pub async fn send(self, signing_key: [u8; 32], nonce: u64) -> Result<(), ClientError> { let request = Mutate { signing_key, - // todo! when nonces are implemented in context - // todo! config contract, we fetch it here first - nonce: 0, + nonce, kind: self.kind, }; diff --git a/crates/context/config/src/client/env/config/query.rs b/crates/context/config/src/client/env/config/query.rs index dc9d39f28..1f66824fc 100644 --- a/crates/context/config/src/client/env/config/query.rs +++ b/crates/context/config/src/client/env/config/query.rs @@ -13,6 +13,7 @@ pub mod members; pub mod members_revision; pub mod privileges; pub mod proxy_contract; +pub mod fetch_nonce; #[derive(Debug)] pub struct ContextConfigQuery<'a, T> { @@ -101,4 +102,17 @@ impl<'a, T: Transport> ContextConfigQuery<'a, T> { utils::send(&self.client, Operation::Read(params)).await } + + pub async fn fetch_nonce( + &self, + context_id: ContextId, + member_id: ContextIdentity, + ) -> Result> { + let params = fetch_nonce::FetchNonceRequest { + context_id: Repr::new(context_id), + member: Repr::new(member_id), + }; + + utils::send(&self.client, Operation::Read(params)).await + } } diff --git a/crates/context/config/src/client/env/config/query/fetch_nonce.rs b/crates/context/config/src/client/env/config/query/fetch_nonce.rs new file mode 100644 index 000000000..184253a4a --- /dev/null +++ b/crates/context/config/src/client/env/config/query/fetch_nonce.rs @@ -0,0 +1,104 @@ +use candid::{Decode, Encode}; +use serde::Serialize; +use starknet::core::codec::Encode as StarknetEncode; + +use crate::client::env::config::types::starknet::{ + CallData, ContextId as StarknetContextId, ContextIdentity as StarknetContextIdentity, +}; +use crate::client::env::Method; +use crate::client::protocol::icp::Icp; +use crate::client::protocol::near::Near; +use crate::client::protocol::starknet::Starknet; +use crate::icp::repr::ICRepr; +use crate::repr::Repr; +use crate::types::{ContextId, ContextIdentity}; + +#[derive(Copy, Clone, Debug, Serialize)] +pub(super) struct FetchNonceRequest { + pub(super) context_id: Repr, + pub(super) member: Repr, +} + +impl FetchNonceRequest { + pub const fn new(context_id: ContextId, member: ContextIdentity) -> Self { + + Self { + context_id: Repr::new(context_id), + member: Repr::new(member), + } + } +} + +impl Method for FetchNonceRequest { + const METHOD: &'static str = "fetch_nonce"; + + type Returns = u64; + + fn encode(self) -> eyre::Result> { + serde_json::to_vec(&self).map_err(Into::into) + } + + fn decode(response: Vec) -> eyre::Result { + let nonce: u64 = + serde_json::from_slice(&response)?; + + Ok(nonce) + } +} + +impl Method for FetchNonceRequest { + type Returns = u64; + + const METHOD: &'static str = "fetch_nonce"; + + fn encode(self) -> eyre::Result> { + let mut call_data = CallData::default(); + + // Dereference Repr and encode context_id + let context_id: StarknetContextId = (*self.context_id).into(); + context_id.encode(&mut call_data)?; + + let member: StarknetContextIdentity = (*self.member).into(); + member.encode(&mut call_data)?; + + Ok(call_data.0) + } + + fn decode(response: Vec) -> eyre::Result { + if response.len() != 8 { + return Err(eyre::eyre!( + "Invalid response length: expected 8 bytes, got {}", + response.len() + )); + } + + let nonce = u64::from_be_bytes( + response + .try_into() + .map_err(|_| eyre::eyre!("Failed to convert response to u64"))?, + ); + + Ok(nonce) + } +} + +impl Method for FetchNonceRequest { + type Returns = u64; + + const METHOD: &'static str = "fetch_nonce"; + + fn encode(self) -> eyre::Result> { + let context_id = ICRepr::new(*self.context_id); + let member = ICRepr::new(*self.member); + + let payload = (context_id, member); + + Encode!(&payload).map_err(Into::into) + } + + fn decode(response: Vec) -> eyre::Result { + let decoded = Decode!(&response, u64)?; + + Ok(decoded) + } +} diff --git a/crates/context/src/lib.rs b/crates/context/src/lib.rs index e706b1ee7..7798fc561 100644 --- a/crates/context/src/lib.rs +++ b/crates/context/src/lib.rs @@ -13,7 +13,8 @@ use calimero_context_config::client::{AnyTransport, Client as ExternalClient}; use calimero_context_config::repr::{Repr, ReprBytes, ReprTransmute}; use calimero_context_config::types::{ Application as ApplicationConfig, ApplicationMetadata as ApplicationMetadataConfig, - ApplicationSource as ApplicationSourceConfig, ContextIdentity, ProposalId, + ApplicationSource as ApplicationSourceConfig, ContextId as IdentityContextId, ContextIdentity, + ProposalId, }; use calimero_context_config::{Proposal, ProposalAction, ProposalWithApprovals}; use calimero_network::client::NetworkClient; @@ -217,6 +218,23 @@ impl ContextManager { ) } + let context_id: IdentityContextId = context.id.rt().expect("infallible conversion"); + let member_id = identity_secret + .public_key() + .rt() + .expect("infallible conversion"); + + let nonce: u64 = this + .config_client + .query::( + this.client_config.new.protocol.as_str().into(), + this.client_config.new.network.as_str().into(), + this.client_config.new.contract_id.as_str().into(), + ) + .fetch_nonce(context_id, member_id) + .await?; + let nonce = nonce + 1; + this.config_client .mutate::( this.client_config.new.protocol.as_str().into(), @@ -237,7 +255,7 @@ impl ContextManager { ApplicationMetadataConfig(Repr::new(application.metadata.into())), ), ) - .send(*context_secret) + .send(*context_secret, nonce) .await?; let proxy_contract = this @@ -411,6 +429,19 @@ impl ContextManager { return Ok(None); }; + let member_id = inviter_id.rt().expect("infallible conversion"); + + let nonce: u64 = self + .config_client + .query::( + context_config.protocol.as_ref().into(), + context_config.network.as_ref().into(), + context_config.contract.as_ref().into(), + ) + .fetch_nonce(context_id.rt().expect("infallible conversion"), member_id) + .await?; + let nonce = nonce + 1; + self.config_client .mutate::( context_config.protocol.as_ref().into(), @@ -421,7 +452,7 @@ impl ContextManager { context_id.rt().expect("infallible conversion"), &[invitee_id.rt().expect("infallible conversion")], ) - .send(requester_secret) + .send(requester_secret, nonce) .await?; let invitation_payload = ContextInvitationPayload::new( @@ -928,6 +959,20 @@ impl ContextManager { context_id ); }; + + let member_id = signer_id.rt().expect("infallible conversion"); + + let nonce: u64 = self + .config_client + .query::( + context_config.protocol.as_ref().into(), + context_config.network.as_ref().into(), + context_config.contract.as_ref().into(), + ) + .fetch_nonce(context_id.rt().expect("infallible conversion"), member_id) + .await?; + let nonce = nonce + 1; + let _ = self .config_client .mutate::( @@ -945,7 +990,7 @@ impl ContextManager { ApplicationMetadataConfig(Repr::new(application.metadata.into())), ), ) - .send(requester_secret) + .send(requester_secret, nonce) .await?; context_meta.application = ApplicationMetaKey::new(application_id);