Skip to content

Commit

Permalink
feat: add ic_message_channel
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Aug 27, 2024
1 parent 6584e17 commit 5d5b671
Show file tree
Hide file tree
Showing 23 changed files with 2,105 additions and 64 deletions.
276 changes: 217 additions & 59 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[workspace]
members = [
"src/cli_cryptogram",
"src/ic_message",
"src/ic_message_channel",
"src/ic_message_profile",
"src/ic_panda_ai",
"src/ic_panda_infra",
"src/ic_panda_luckypool",
Expand Down Expand Up @@ -30,5 +33,6 @@ num-traits = "0.2"
ic-cdk = "0.15"
ic-cdk-timers = "0.8"
ic-stable-structures = "0.6"
ic_cose_types = "0.1"
getrandom = { version = "0.2", features = ["custom"] }
rand = { version = "0.8", features = ["getrandom"] }
11 changes: 6 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ twiggy:

# cargo install ic-wasm
build-wasm:
cargo build --release --target wasm32-unknown-unknown --package ic_panda_ai
cargo build --release --target wasm32-unknown-unknown --package ic_message
cargo build --release --target wasm32-unknown-unknown --package ic_message_channel
cargo build --release --target wasm32-unknown-unknown --package ic_message_profile
cargo build --release --target wasm32-unknown-unknown --package ic_panda_luckypool

shrink-wasm:
ic-wasm -o target/wasm32-unknown-unknown/release/ic_panda_ai_optimized.wasm target/wasm32-unknown-unknown/release/ic_panda_ai.wasm shrink

# cargo install candid-extractor
build-did:
candid-extractor target/wasm32-unknown-unknown/release/ic_panda_ai.wasm > src/ic_panda_ai/ic_panda_ai.did
candid-extractor target/wasm32-unknown-unknown/release/ic_message.wasm > src/ic_message/ic_message.did
candid-extractor target/wasm32-unknown-unknown/release/ic_message_channel.wasm > src/ic_message_channel/ic_message_channel.did
candid-extractor target/wasm32-unknown-unknown/release/ic_message_profile.wasm > src/ic_message_profile/ic_message_profile.did
candid-extractor target/wasm32-unknown-unknown/release/ic_panda_luckypool.wasm > src/ic_panda_luckypool/ic_panda_luckypool.did
18 changes: 18 additions & 0 deletions src/ic_message/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "ic_message"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
candid = { workspace = true }
ciborium = { workspace = true }
serde = { workspace = true }
getrandom = { workspace = true }
ic-cdk = { workspace = true }
ic-stable-structures = { workspace = true }
ic_cose_types = { workspace = true }
4 changes: 4 additions & 0 deletions src/ic_message/ic_message.did
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type ChainArgs = variant { Upgrade : UpgradeArgs; Init : InitArgs };
type InitArgs = record { managers : vec principal; name : text };
type UpgradeArgs = record { managers : opt vec principal; name : opt text };
service : (opt ChainArgs) -> {}
70 changes: 70 additions & 0 deletions src/ic_message/src/api_init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use candid::{CandidType, Principal};
use serde::Deserialize;
use std::collections::BTreeSet;

use crate::store;

#[derive(Clone, Debug, CandidType, Deserialize)]
pub enum ChainArgs {
Init(InitArgs),
Upgrade(UpgradeArgs),
}

#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct InitArgs {
name: String,
managers: BTreeSet<Principal>,
}

#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct UpgradeArgs {
name: Option<String>,
managers: Option<BTreeSet<Principal>>,
}

#[ic_cdk::init]
fn init(args: Option<ChainArgs>) {
match args {
None => {}
Some(ChainArgs::Init(args)) => {
store::state::with_mut(|s| {
s.name = args.name;
s.managers = args.managers;
});
}
Some(ChainArgs::Upgrade(_)) => {
ic_cdk::trap(
"cannot initialize the canister with an Upgrade args. Please provide an Init args.",
);
}
}
}

#[ic_cdk::pre_upgrade]
fn pre_upgrade() {
store::state::save();
}

#[ic_cdk::post_upgrade]
fn post_upgrade(args: Option<ChainArgs>) {
store::state::load();

match args {
Some(ChainArgs::Upgrade(args)) => {
store::state::with_mut(|s| {
if let Some(name) = args.name {
s.name = name;
}
if let Some(managers) = args.managers {
s.managers = managers;
}
});
}
Some(ChainArgs::Init(_)) => {
ic_cdk::trap(
"cannot upgrade the canister with an Init args. Please provide an Upgrade args.",
);
}
_ => {}
}
}
42 changes: 42 additions & 0 deletions src/ic_message/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use ic_cose_types::ANONYMOUS;

mod api_init;
mod store;

use crate::api_init::ChainArgs;

fn is_controller() -> Result<(), String> {
let caller = ic_cdk::caller();
if ic_cdk::api::is_controller(&caller) {
Ok(())
} else {
Err("user is not a controller".to_string())
}
}

fn is_authenticated() -> Result<(), String> {
if ic_cdk::caller() == ANONYMOUS {
Err("anonymous user is not allowed".to_string())
} else {
Ok(())
}
}

#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
/// A getrandom implementation that always fails
pub fn always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> {
Err(getrandom::Error::UNSUPPORTED)
}

#[cfg(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))]
getrandom::register_custom_getrandom!(always_fail);

ic_cdk::export_candid!();
129 changes: 129 additions & 0 deletions src/ic_message/src/store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use candid::Principal;
use ciborium::{from_reader, from_reader_with_buffer, into_writer};
use ic_stable_structures::{
memory_manager::{MemoryId, MemoryManager, VirtualMemory},
storable::Bound,
DefaultMemoryImpl, StableBTreeMap, StableCell, Storable,
};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, cell::RefCell, collections::BTreeSet};

type Memory = VirtualMemory<DefaultMemoryImpl>;

#[derive(Clone, Default, Deserialize, Serialize)]
pub struct State {
pub name: String,
pub managers: BTreeSet<Principal>,
}

impl Storable for State {
const BOUND: Bound = Bound::Unbounded;

fn to_bytes(&self) -> Cow<[u8]> {
let mut buf = vec![];
into_writer(self, &mut buf).expect("failed to encode State data");
Cow::Owned(buf)
}

fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
from_reader(&bytes[..]).expect("failed to decode State data")
}
}

#[derive(Clone, Deserialize, Serialize)]
pub struct User {
#[serde(rename = "n")]
pub name: String,
#[serde(rename = "i")]
pub image: String,
#[serde(rename = "p")]
pub profile_canister: Principal, // profile canister
#[serde(rename = "cn")]
pub cose_namespace: Option<(Principal, String)>, // namespace in shared COSE service
}

impl Storable for User {
const BOUND: Bound = Bound::Unbounded;

fn to_bytes(&self) -> Cow<[u8]> {
let mut buf = vec![];
into_writer(self, &mut buf).expect("failed to encode User data");
Cow::Owned(buf)
}

fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
from_reader(&bytes[..]).expect("failed to decode User data")
}
}

const STATE_MEMORY_ID: MemoryId = MemoryId::new(0);
const NAME_MEMORY_ID: MemoryId = MemoryId::new(1);
const USER_MEMORY_ID: MemoryId = MemoryId::new(2);

thread_local! {
static STATE: RefCell<State> = RefCell::new(State::default());

static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

static STATE_STORE: RefCell<StableCell<Vec<u8>, Memory>> = RefCell::new(
StableCell::init(
MEMORY_MANAGER.with_borrow(|m| m.get(STATE_MEMORY_ID)),
Vec::new()
).expect("failed to init STATE store")
);

static NAME_STORE: RefCell<StableBTreeMap<String, Principal, Memory>> = RefCell::new(
StableBTreeMap::init(
MEMORY_MANAGER.with_borrow(|m| m.get(NAME_MEMORY_ID)),
)
);

static USER_STORE: RefCell<StableBTreeMap<Principal, User, Memory>> = RefCell::new(
StableBTreeMap::init(
MEMORY_MANAGER.with_borrow(|m| m.get(USER_MEMORY_ID)),
)
);
}

pub mod state {
use super::*;

pub fn with<R>(f: impl FnOnce(&State) -> R) -> R {
STATE.with(|r| f(&r.borrow()))
}

pub fn with_mut<R>(f: impl FnOnce(&mut State) -> R) -> R {
STATE.with(|r| f(&mut r.borrow_mut()))
}

pub fn is_manager(caller: &Principal) -> Result<(), String> {
STATE.with(|r| match r.borrow().managers.contains(caller) {
true => Ok(()),
false => Err("caller is not a manager".to_string()),
})
}

pub fn load() {
let mut scratch = [0; 4096];
STATE_STORE.with(|r| {
STATE.with(|h| {
let v: State = from_reader_with_buffer(&r.borrow().get()[..], &mut scratch)
.expect("failed to decode STATE_STORE data");
*h.borrow_mut() = v;
});
});
}

pub fn save() {
STATE.with(|h| {
STATE_STORE.with(|r| {
let mut buf = vec![];
into_writer(&(*h.borrow()), &mut buf).expect("failed to encode STATE_STORE data");
r.borrow_mut()
.set(buf)
.expect("failed to set STATE_STORE data");
});
});
}
}
19 changes: 19 additions & 0 deletions src/ic_message_channel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "ic_message_channel"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
candid = { workspace = true }
ciborium = { workspace = true }
serde = { workspace = true }
serde_bytes = { workspace = true }
getrandom = { workspace = true }
ic-cdk = { workspace = true }
ic-stable-structures = { workspace = true }
ic_cose_types = { workspace = true }
Loading

0 comments on commit 5d5b671

Please sign in to comment.