Skip to content

Commit

Permalink
feat: topup channel
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Sep 27, 2024
1 parent 18a4547 commit af25fcb
Show file tree
Hide file tree
Showing 27 changed files with 599 additions and 142 deletions.
7 changes: 7 additions & 0 deletions src/ic_message/ic_message.did
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ type ChannelSetting = record {
last_read : nat32;
ecdh_pub : opt blob;
};
type ChannelTopupInput = record {
id : nat32;
canister : principal;
payer : principal;
amount : nat64;
};
type CreateChannelInput = record {
dek : blob;
managers : vec record { principal; ChannelECDHInput };
Expand Down Expand Up @@ -185,6 +191,7 @@ service : (opt ChainArgs) -> {
register_username : (text, opt text) -> (Result_3);
save_channel_kek : (ChannelKEKInput) -> (Result);
search_username : (text) -> (Result_7) query;
topup_channel : (ChannelTopupInput) -> (Result_2);
transfer_username : (principal) -> (Result);
update_my_ecdh : (blob, blob) -> (Result);
update_my_image : (text) -> (Result);
Expand Down
10 changes: 9 additions & 1 deletion src/ic_message/src/api_update.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use candid::Principal;
use ic_cose_types::{cose::encrypt0::try_decode_encrypt0, validate_key, MILLISECONDS};
use ic_message_types::{
channel::{ChannelInfo, ChannelKEKInput, CreateChannelInput},
channel::{ChannelInfo, ChannelKEKInput, ChannelTopupInput, CreateChannelInput},
profile::{UpdateKVInput, UserInfo},
};
use serde_bytes::{ByteArray, ByteBuf};
Expand Down Expand Up @@ -94,6 +94,14 @@ async fn create_channel(input: CreateChannelInput) -> Result<ChannelInfo, String
store::channel::create_channel(caller, now_ms, input).await
}

#[ic_cdk::update(guard = "is_authenticated")]
async fn topup_channel(input: ChannelTopupInput) -> Result<ChannelInfo, String> {
input.validate()?;

let caller = ic_cdk::caller();
store::channel::topup_channel(caller, input).await
}

#[ic_cdk::update(guard = "is_authenticated")]
async fn save_channel_kek(input: ChannelKEKInput) -> Result<(), String> {
let caller = ic_cdk::caller();
Expand Down
2 changes: 1 addition & 1 deletion src/ic_message/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use candid::{utils::ArgumentEncoder, Nat, Principal};
use ic_cdk::api::management_canister::main::CanisterStatusResponse;
use ic_cose_types::ANONYMOUS;
use ic_message_types::{
channel::{ChannelInfo, ChannelKEKInput, CreateChannelInput},
channel::{ChannelInfo, ChannelKEKInput, ChannelTopupInput, CreateChannelInput},
profile::{UpdateKVInput, UserInfo},
};
use icrc_ledger_types::icrc3::{
Expand Down
26 changes: 25 additions & 1 deletion src/ic_message/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ pub mod user {
pub mod channel {
use super::*;
use ic_message_types::channel::{
channel_kek_key, ChannelInfo, ChannelKEKInput, CreateChannelInput,
channel_kek_key, ChannelInfo, ChannelKEKInput, ChannelTopupInput, CreateChannelInput,
};

pub async fn create_channel(
Expand Down Expand Up @@ -917,6 +917,30 @@ pub mod channel {
res
}

pub async fn topup_channel(
caller: Principal,
mut input: ChannelTopupInput,
) -> Result<ChannelInfo, String> {
input.payer = caller;
state::with(|s| {
if !s.channel_canisters.contains(&input.canister)
&& !s.matured_channel_canisters.contains(&input.canister)
{
Err("channel canister not found".to_string())
} else {
Ok(())
}
})?;
let amount = input.amount.saturating_sub(types::TOKEN_FEE);
token_transfer_from(caller, amount.into(), "TC".to_string()).await?;
state::with_mut(|s| {
s.incoming_total += amount as u128;
});
let res: Result<ChannelInfo, String> =
call(input.canister, "admin_topup_channel", (input,), 0).await?;
res
}

pub async fn save_channel_kek(caller: Principal, input: ChannelKEKInput) -> Result<(), String> {
let cose_canister = USER_STORE
.with(|r| r.borrow().get(&caller).map(|u| u.cose_canister))
Expand Down
7 changes: 7 additions & 0 deletions src/ic_message_channel/ic_message_channel.did
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ type ChannelSetting = record {
last_read : nat32;
ecdh_pub : opt blob;
};
type ChannelTopupInput = record {
id : nat32;
canister : principal;
payer : principal;
amount : nat64;
};
type CreateChannelInput = record {
dek : blob;
managers : vec record { principal; ChannelECDHInput };
Expand Down Expand Up @@ -152,6 +158,7 @@ service : (opt ChainArgs) -> {
admin_add_managers : (vec principal) -> (Result_1);
admin_create_channel : (CreateChannelInput) -> (Result_2);
admin_remove_managers : (vec principal) -> (Result_1);
admin_topup_channel : (ChannelTopupInput) -> (Result_2);
batch_get_channels : (vec nat32) -> (Result_3) query;
delete_message : (DeleteMessageInput) -> (Result_1);
get_canister_status : () -> (Result_4) query;
Expand Down
8 changes: 8 additions & 0 deletions src/ic_message_channel/src/api_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ fn admin_create_channel(input: types::CreateChannelInput) -> Result<types::Chann
store::channel::create(caller, input, now_ms)
}

#[ic_cdk::update]
fn admin_topup_channel(input: types::ChannelTopupInput) -> Result<types::ChannelInfo, String> {
let caller = ic_cdk::caller();
let now_ms = ic_cdk::api::time() / MILLISECONDS;
store::state::is_manager(&caller)?;
store::channel::topup(input.payer, input.id, input.amount, now_ms)
}

#[ic_cdk::update]
fn validate_admin_add_managers(args: BTreeSet<Principal>) -> Result<(), String> {
validate_principals(&args)?;
Expand Down
33 changes: 33 additions & 0 deletions src/ic_message_channel/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,39 @@ pub mod channel {
})
}

pub fn topup(
payer: Principal,
id: u32,
amount: u64,
now_ms: u64,
) -> Result<types::ChannelInfo, String> {
CHANNEL_STORE.with(|r| {
let mut m = r.borrow_mut();
match m.get(&id) {
None => Err("channel not found".to_string()),
Some(mut c) => {
let gas = c.gas.saturating_add(amount);
c.gas = gas;
c.latest_message_id += 1;
c.latest_message_at = now_ms;
c.latest_message_by = payer;
c.paid = c.paid.saturating_add(amount);
add_sys_message(
payer,
now_ms,
MessageId(id, c.latest_message_id),
format!("{}: {}", types::SYS_MSG_CHANNEL_TOPUP, amount),
);
state::with_mut(|s| {
s.incoming_gas = s.incoming_gas.saturating_add(amount as u128);
});
m.insert(id, c.clone());
Ok(c.into_info(payer, ic_cdk::id(), id))
}
}
})
}

pub fn update_my_setting(
caller: Principal,
input: types::UpdateMySettingInput,
Expand Down
19 changes: 19 additions & 0 deletions src/ic_message_types/src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ pub const MAX_CHANNEL_MEMBERS: usize = 100;
pub const MAX_CHANNEL_MESSAGES: u32 = 10000;
pub const MAX_USER_CHANNELS: usize = 1000;
pub const MAX_MESSAGE_SIZE: usize = 1024 * 32; // 32KB
pub const MIN_TOPUP_AMOUNT: u64 = 100_000_000; // 1 token

pub static SYS_MSG_CHANNEL_CREATE: &str = "Channel.Create";
pub static SYS_MSG_CHANNEL_TOPUP: &str = "Channel.Topup";
pub static SYS_MSG_CHANNEL_UPDATE_INFO: &str = "Channel.Update.Info";
pub static SYS_MSG_CHANNEL_ADD_MANAGER: &str = "Channel.Add.Manager";
pub static SYS_MSG_CHANNEL_ADD_MEMBER: &str = "Channel.Add.Member";
Expand Down Expand Up @@ -119,6 +121,23 @@ pub struct ChannelKEKInput {
pub kek: ByteBuf, // encrypted key to decrypt channel dek
}

#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
pub struct ChannelTopupInput {
pub id: u32,
pub canister: Principal,
pub payer: Principal,
pub amount: u64,
}

impl ChannelTopupInput {
pub fn validate(&self) -> Result<(), String> {
if self.amount < MIN_TOPUP_AMOUNT {
Err("amount is too small".to_string())?;
}
Ok(())
}
}

pub fn channel_kek_key(canister: &Principal, id: u32) -> ByteBuf {
to_cbor_bytes(&(canister, id)).into()
}
Expand Down
2 changes: 1 addition & 1 deletion src/ic_panda_frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,5 @@
"test": "vitest run"
},
"type": "module",
"version": "2.2.3"
"version": "2.2.4"
}
11 changes: 10 additions & 1 deletion src/ic_panda_frontend/src/app.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ body {
}

/* https://www.skeleton.dev/docs/themes */
:root {
:root [data-theme='skeleton'] {
--theme-font-family-base: -apple-system, BlinkMacSystemFont, 'system-ui',
'Segoe UI', 'Noto Sans', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji';
}

.icpanda-message {
font-family: -apple-system, BlinkMacSystemFont, 'system-ui', 'Segoe UI',
'Noto Sans', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji';
}

.global-backgroud {
Expand Down
11 changes: 11 additions & 0 deletions src/ic_panda_frontend/src/declarations/ic_message/ic_message.did
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ type ChannelSetting = record {
last_read : nat32;
ecdh_pub : opt blob;
};
type ChannelTopupInput = record {
id : nat32;
canister : principal;
payer : principal;
amount : nat64;
};
type CreateChannelInput = record {
dek : blob;
managers : vec record { principal; ChannelECDHInput };
Expand Down Expand Up @@ -185,11 +191,16 @@ service : (opt ChainArgs) -> {
register_username : (text, opt text) -> (Result_3);
save_channel_kek : (ChannelKEKInput) -> (Result);
search_username : (text) -> (Result_7) query;
topup_channel : (ChannelTopupInput) -> (Result_2);
transfer_username : (principal) -> (Result);
update_my_ecdh : (blob, blob) -> (Result);
update_my_image : (text) -> (Result);
update_my_kv : (UpdateKVInput) -> (Result);
update_my_name : (text) -> (Result_3);
validate2_admin_add_canister : (CanisterKind, principal) -> (Result_8);
validate2_admin_add_managers : (vec principal) -> (Result_8);
validate2_admin_collect_token : (Account, nat) -> (Result_8);
validate2_admin_remove_managers : (vec principal) -> (Result_8);
validate2_admin_update_price : (UpdatePriceInput) -> (Result_8);
validate_admin_add_canister : (CanisterKind, principal) -> (Result);
validate_admin_add_managers : (vec principal) -> (Result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ export interface ChannelSetting {
'last_read' : number,
'ecdh_pub' : [] | [Uint8Array | number[]],
}
export interface ChannelTopupInput {
'id' : number,
'canister' : Principal,
'payer' : Principal,
'amount' : bigint,
}
export interface CreateChannelInput {
'dek' : Uint8Array | number[],
'managers' : Array<[Principal, ChannelECDHInput]>,
Expand Down Expand Up @@ -220,6 +226,7 @@ export interface _SERVICE {
'register_username' : ActorMethod<[string, [] | [string]], Result_3>,
'save_channel_kek' : ActorMethod<[ChannelKEKInput], Result>,
'search_username' : ActorMethod<[string], Result_7>,
'topup_channel' : ActorMethod<[ChannelTopupInput], Result_2>,
'transfer_username' : ActorMethod<[Principal], Result>,
'update_my_ecdh' : ActorMethod<
[Uint8Array | number[], Uint8Array | number[]],
Expand All @@ -228,6 +235,13 @@ export interface _SERVICE {
'update_my_image' : ActorMethod<[string], Result>,
'update_my_kv' : ActorMethod<[UpdateKVInput], Result>,
'update_my_name' : ActorMethod<[string], Result_3>,
'validate2_admin_add_canister' : ActorMethod<
[CanisterKind, Principal],
Result_8
>,
'validate2_admin_add_managers' : ActorMethod<[Array<Principal>], Result_8>,
'validate2_admin_collect_token' : ActorMethod<[Account, bigint], Result_8>,
'validate2_admin_remove_managers' : ActorMethod<[Array<Principal>], Result_8>,
'validate2_admin_update_price' : ActorMethod<[UpdatePriceInput], Result_8>,
'validate_admin_add_canister' : ActorMethod<
[CanisterKind, Principal],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ export const idlFactory = ({ IDL }) => {
'canister' : IDL.Principal,
});
const Result_7 = IDL.Variant({ 'Ok' : IDL.Vec(IDL.Text), 'Err' : IDL.Text });
const ChannelTopupInput = IDL.Record({
'id' : IDL.Nat32,
'canister' : IDL.Principal,
'payer' : IDL.Principal,
'amount' : IDL.Nat64,
});
const UpdateKVInput = IDL.Record({
'upsert_kv' : IDL.Vec(IDL.Tuple(IDL.Text, IDL.Vec(IDL.Nat8))),
'remove_kv' : IDL.Vec(IDL.Text),
Expand Down Expand Up @@ -259,6 +265,7 @@ export const idlFactory = ({ IDL }) => {
),
'save_channel_kek' : IDL.Func([ChannelKEKInput], [Result], []),
'search_username' : IDL.Func([IDL.Text], [Result_7], ['query']),
'topup_channel' : IDL.Func([ChannelTopupInput], [Result_2], []),
'transfer_username' : IDL.Func([IDL.Principal], [Result], []),
'update_my_ecdh' : IDL.Func(
[IDL.Vec(IDL.Nat8), IDL.Vec(IDL.Nat8)],
Expand All @@ -268,6 +275,26 @@ export const idlFactory = ({ IDL }) => {
'update_my_image' : IDL.Func([IDL.Text], [Result], []),
'update_my_kv' : IDL.Func([UpdateKVInput], [Result], []),
'update_my_name' : IDL.Func([IDL.Text], [Result_3], []),
'validate2_admin_add_canister' : IDL.Func(
[CanisterKind, IDL.Principal],
[Result_8],
[],
),
'validate2_admin_add_managers' : IDL.Func(
[IDL.Vec(IDL.Principal)],
[Result_8],
[],
),
'validate2_admin_collect_token' : IDL.Func(
[Account, IDL.Nat],
[Result_8],
[],
),
'validate2_admin_remove_managers' : IDL.Func(
[IDL.Vec(IDL.Principal)],
[Result_8],
[],
),
'validate2_admin_update_price' : IDL.Func(
[UpdatePriceInput],
[Result_8],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ type ChannelSetting = record {
last_read : nat32;
ecdh_pub : opt blob;
};
type ChannelTopupInput = record {
id : nat32;
canister : principal;
payer : principal;
amount : nat64;
};
type CreateChannelInput = record {
dek : blob;
managers : vec record { principal; ChannelECDHInput };
Expand Down Expand Up @@ -110,6 +116,7 @@ type Result = variant { Ok : AddMessageOutput; Err : text };
type Result_1 = variant { Ok; Err : text };
type Result_10 = variant { Ok : record { nat64; opt Message }; Err : text };
type Result_11 = variant { Ok : ChannelSetting; Err : text };
type Result_12 = variant { Ok : text; Err : text };
type Result_2 = variant { Ok : ChannelInfo; Err : text };
type Result_3 = variant { Ok : vec ChannelBasicInfo; Err : text };
type Result_4 = variant { Ok : CanisterStatusResponse; Err : text };
Expand Down Expand Up @@ -151,6 +158,7 @@ service : (opt ChainArgs) -> {
admin_add_managers : (vec principal) -> (Result_1);
admin_create_channel : (CreateChannelInput) -> (Result_2);
admin_remove_managers : (vec principal) -> (Result_1);
admin_topup_channel : (ChannelTopupInput) -> (Result_2);
batch_get_channels : (vec nat32) -> (Result_3) query;
delete_message : (DeleteMessageInput) -> (Result_1);
get_canister_status : () -> (Result_4) query;
Expand All @@ -167,6 +175,8 @@ service : (opt ChainArgs) -> {
update_manager : (UpdateChannelMemberInput) -> (Result_10);
update_member : (UpdateChannelMemberInput) -> (Result_10);
update_my_setting : (UpdateMySettingInput) -> (Result_11);
validate2_admin_add_managers : (vec principal) -> (Result_12);
validate2_admin_remove_managers : (vec principal) -> (Result_12);
validate_admin_add_managers : (vec principal) -> (Result_1);
validate_admin_remove_managers : (vec principal) -> (Result_1);
}
Loading

0 comments on commit af25fcb

Please sign in to comment.