Skip to content

Commit

Permalink
Feat icp proxy (#993)
Browse files Browse the repository at this point in the history
  • Loading branch information
alenmestrov authored Dec 4, 2024
1 parent f1213c0 commit 78bfaae
Show file tree
Hide file tree
Showing 33 changed files with 2,858 additions and 355 deletions.
334 changes: 207 additions & 127 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 @@ -39,6 +39,9 @@ members = [
"./contracts/proxy-lib",
"./contracts/test-counter",
"./contracts/icp/context-config",
"./contracts/icp/proxy-contract",
"./contracts/icp/proxy-contract/mock/ledger",
"./contracts/icp/proxy-contract/mock/external",

"./e2e-tests",
]
Expand Down
3 changes: 2 additions & 1 deletion contracts/icp/context-config/context_contract.did
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ service : () -> {
privileges : (blob, vec blob) -> (
vec record { blob; vec ICCapability },
) query;
proxy_contract : (blob) -> (text) query;
proxy_contract : (blob) -> (principal) query;
set_proxy_code : (blob) -> (Result);
}
14 changes: 10 additions & 4 deletions contracts/icp/context-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};

use candid::CandidType;
use candid::{CandidType, Principal};
use guard::Guard;
use serde::{Deserialize, Serialize};

Expand All @@ -12,25 +12,31 @@ use crate::types::{
pub mod guard;
pub mod mutate;
pub mod query;
pub mod sys;
pub mod types;

#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
pub struct Context {
pub application: Guard<ICApplication>,
pub members: Guard<Vec<ICContextIdentity>>,
pub proxy: Guard<String>,
pub proxy: Guard<Principal>,
}

#[derive(CandidType, Deserialize, Clone, Debug)]
pub struct ContextConfigs {
pub contexts: HashMap<ICContextId, Context>,
pub next_proxy_id: u64,
pub proxy_code: Option<Vec<u8>>,
pub owner: Principal,
pub ledger_id: Principal,
}

impl Default for ContextConfigs {
fn default() -> Self {
Self {
contexts: HashMap::new(),
next_proxy_id: 0,
proxy_code: None,
owner: ic_cdk::api::caller(),
ledger_id: Principal::anonymous(),
}
}
}
Expand Down
108 changes: 99 additions & 9 deletions contracts/icp/context-config/src/mutate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::ops::Deref;

use calimero_context_config::repr::{ReprBytes, ReprTransmute};
use candid::Principal;
use ic_cdk::api::management_canister::main::{
create_canister, install_code, CanisterSettings, CreateCanisterArgument, InstallCodeArgument,
};

use crate::guard::Guard;
use crate::types::{
Expand All @@ -10,7 +14,7 @@ use crate::types::{
use crate::{Context, CONTEXT_CONFIGS};

#[ic_cdk::update]
pub fn mutate(signed_request: ICPSigned<Request>) -> Result<(), String> {
pub async fn mutate(signed_request: ICPSigned<Request>) -> Result<(), String> {
let request = signed_request
.parse(|r| r.signer_id)
.map_err(|e| format!("Failed to verify signature: {}", e))?;
Expand All @@ -27,7 +31,7 @@ pub fn mutate(signed_request: ICPSigned<Request>) -> Result<(), String> {
ContextRequestKind::Add {
author_id,
application,
} => add_context(&request.signer_id, context_id, author_id, application),
} => add_context(&request.signer_id, context_id, author_id, application).await,
ContextRequestKind::UpdateApplication { application } => {
update_application(&request.signer_id, &context_id, application)
}
Expand All @@ -44,24 +48,26 @@ pub fn mutate(signed_request: ICPSigned<Request>) -> Result<(), String> {
revoke(&request.signer_id, &context_id, capabilities)
}
ContextRequestKind::UpdateProxyContract => {
// TODO: Implement update_proxy_contract
Ok(())
update_proxy_contract(&request.signer_id, context_id).await
}
},
}
}

fn add_context(
async fn add_context(
signer_id: &ICSignerId,
context_id: ICContextId,
author_id: ICContextIdentity,
application: ICApplication,
) -> Result<(), String> {
// 1. Verify signer is the context itself - direct array comparison
if signer_id.as_bytes() != context_id.as_bytes() {
return Err("context addition must be signed by the context itself".into());
}

let proxy_canister_id = deploy_proxy_contract(&context_id)
.await
.unwrap_or_else(|e| panic!("Failed to deploy proxy contract: {}", e));

CONTEXT_CONFIGS.with(|configs| {
let mut configs = configs.borrow_mut();

Expand All @@ -74,7 +80,7 @@ fn add_context(
),
proxy: Guard::new(
author_id.rt().expect("infallible conversion"),
format!("{}.{}", configs.next_proxy_id, ic_cdk::api::id()),
proxy_canister_id,
),
};

Expand All @@ -83,12 +89,55 @@ fn add_context(
return Err("context already exists".into());
}

configs.next_proxy_id += 1;

Ok(())
})
}

async fn deploy_proxy_contract(context_id: &ICContextId) -> Result<Principal, String> {
// Get the proxy code
let proxy_code = CONTEXT_CONFIGS
.with(|configs| configs.borrow().proxy_code.clone())
.ok_or("proxy code not set")?;

// Get the ledger ID
let ledger_id = CONTEXT_CONFIGS.with(|configs| configs.borrow().ledger_id.clone());
// Create canister with cycles
let create_args = CreateCanisterArgument {
settings: Some(CanisterSettings {
controllers: Some(vec![ic_cdk::api::id()]),
compute_allocation: None,
memory_allocation: None,
freezing_threshold: None,
reserved_cycles_limit: None,
log_visibility: None,
wasm_memory_limit: None,
}),
};

let (canister_record,) = create_canister(create_args, 500_000_000_000_000u128)
.await
.map_err(|e| format!("Failed to create canister: {:?}", e))?;

let canister_id = canister_record.canister_id;

// Encode init args matching the proxy's init(context_id: ICContextId, ledger_id: Principal)
let init_args = candid::encode_args((context_id.clone(), ledger_id))
.map_err(|e| format!("Failed to encode init args: {}", e))?;

let install_args = InstallCodeArgument {
mode: ic_cdk::api::management_canister::main::CanisterInstallMode::Install,
canister_id,
wasm_module: proxy_code,
arg: init_args,
};

install_code(install_args)
.await
.map_err(|e| format!("Failed to install code: {:?}", e))?;

Ok(canister_id)
}

fn update_application(
signer_id: &ICSignerId,
context_id: &ICContextId,
Expand Down Expand Up @@ -282,3 +331,44 @@ fn revoke(
Ok(())
})
}

async fn update_proxy_contract(
signer_id: &ICSignerId,
context_id: ICContextId,
) -> Result<(), String> {
let mut context = CONTEXT_CONFIGS.with(|configs| {
let configs = configs.borrow();
configs
.contexts
.get(&context_id)
.ok_or_else(|| "context does not exist".to_string())
.cloned()
})?;

// Get proxy canister ID
let proxy_canister_id = context
.proxy
.get(signer_id)
.map_err(|_| "unauthorized: Proxy capability required".to_string())?
.get_mut()
.clone();

// Get the proxy code
let proxy_code = CONTEXT_CONFIGS
.with(|configs| configs.borrow().proxy_code.clone())
.ok_or("proxy code not set")?;

// Update the proxy contract code
let install_args = InstallCodeArgument {
mode: ic_cdk::api::management_canister::main::CanisterInstallMode::Upgrade(None),
canister_id: proxy_canister_id,
wasm_module: proxy_code,
arg: candid::encode_one(&context_id).map_err(|e| format!("Encoding error: {}", e))?,
};

install_code(install_args)
.await
.map_err(|e| format!("Failed to update proxy contract: {:?}", e))?;

Ok(())
}
3 changes: 2 additions & 1 deletion contracts/icp/context-config/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::BTreeMap;

use calimero_context_config::repr::ReprTransmute;
use candid::Principal;
use ic_cdk_macros::query;

use crate::types::*;
Expand Down Expand Up @@ -33,7 +34,7 @@ fn application_revision(context_id: ICContextId) -> u64 {
}

#[query]
fn proxy_contract(context_id: ICContextId) -> String {
fn proxy_contract(context_id: ICContextId) -> Principal {
CONTEXT_CONFIGS.with(|configs| {
let configs = configs.borrow();
let context = configs
Expand Down
60 changes: 60 additions & 0 deletions contracts/icp/context-config/src/sys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use candid::{CandidType, Deserialize, Principal};
use ic_cdk;

use crate::CONTEXT_CONFIGS;

#[derive(CandidType, Deserialize)]
struct StableStorage {
configs: crate::ContextConfigs,
}

#[ic_cdk::pre_upgrade]
fn pre_upgrade() {
// Verify caller is the owner
CONTEXT_CONFIGS.with(|configs| {
let configs = configs.borrow();
if ic_cdk::api::caller() != configs.owner {
ic_cdk::trap("unauthorized: only owner can upgrade context contract");
}
});

// Store the contract state
let state = CONTEXT_CONFIGS.with(|configs| StableStorage {
configs: configs.borrow().clone(),
});

// Write state to stable storage
match ic_cdk::storage::stable_save((state,)) {
Ok(_) => (),
Err(err) => ic_cdk::trap(&format!("Failed to save stable storage: {}", err)),
}
}

#[ic_cdk::post_upgrade]
fn post_upgrade() {
// Restore the contract state
match ic_cdk::storage::stable_restore::<(StableStorage,)>() {
Ok((state,)) => {
CONTEXT_CONFIGS.with(|configs| {
*configs.borrow_mut() = state.configs;
});
}
Err(err) => ic_cdk::trap(&format!("Failed to restore stable storage: {}", err)),
}
}

#[ic_cdk::update]
pub fn set_proxy_code(proxy_code: Vec<u8>, ledger_id: Principal) -> Result<(), String> {
CONTEXT_CONFIGS.with(|configs| {
let mut configs = configs.borrow_mut();

// Check if caller is the owner
if ic_cdk::api::caller() != configs.owner {
return Err("Unauthorized: only owner can set proxy code".to_string());
}

configs.ledger_id = ledger_id;
configs.proxy_code = Some(proxy_code);
Ok(())
})
}
Loading

0 comments on commit 78bfaae

Please sign in to comment.