1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
//! This module contains the logic for checking achievement eligibility and managing achievements.
use ic_cdk::{query, update};
use candid::Principal;
use crate::state::{get_principal_to_hash_value, update_principal_to_hash, update_principal_to_achievement_status};
use crate::ecdsa::{public_key, build_principals_message, sign, verify};
use crate::storable::{Signature, AchievementStatusEnum, AchievementStatus};
/// Checks if a principal is eligible for an achievement.
///
/// # Arguments
///
/// * `principal` - The principal to check.
/// * `blob` - Additional data for eligibility check.
///
/// # Returns
///
/// * `Result<bool, String>` - `true` if the principal is eligible, `false` otherwise.
///
/// # Example
///
/// ## IC-reactor usage
/// ```typescript
/// const { data: eligible, call: fetchEligigble }: { data: any, call: any } = useUpdateCall({
/// functionName: "checkAchievementEligibility",
/// args: [
/// identity?.getPrincipal(),
/// []
/// ]
/// })
/// ```
#[update(name = "checkAchievementEligibility")]
async fn check_achievement_eligibility(principal: Principal, blob: Vec<u8>) -> Result<bool, String> {
// Your conditions for achievement
// Example of calling another canister
let example_backend_canister_id = Principal::from_text("aax3a-h4aaa-aaaaa-qaahq-cai").unwrap();
let is_posted: (bool, ) = ic_cdk::call(example_backend_canister_id, "getPrincipalToIsPosted", (principal,)).await.unwrap();
Ok(is_posted.0)
}
/// Generates a hash for the caller's identity wallet.
///
/// # Arguments
///
/// * `identity_wallet` - The principal of the identity wallet.
/// * `blob` - Additional data for hash generation.
///
/// # Returns
///
/// * `Result<String, String>` - The result of the hash generation.
///
/// # Example
///
/// ```
/// dfx --identity pa_local_wallet canister call achievement generateHashToIdentityWallet "(principal \"$(dfx --identity pa_identity_wallet identity get-principal)\", vec {})"
///
/// (
/// variant {
/// Ok = "Succesfully generate hash for Identity Wallet. Signature 5ac9cae0bd534ee09eea7bf9ddd85a53ba13efe9a416fb13155b46fa2af2f3f0671b2b79c534a29ade73811098cb947ccbd606b935aa1e0610093eac3b3ddc00"
/// },
/// )
/// ```
///
/// ## IC-reactor usage
/// ```typescript
/// const { call: generateHash }: { call: any} = useUpdateCall({
/// functionName: "generateHashToIdentityWallet",
/// args: [
/// Principal.fromText(identity_wallet as string || identity!.getPrincipal()!.toText()),
/// []
/// ]
/// })
/// ```
#[update(name = "generateHashToIdentityWallet")]
async fn generate_hash_to_identity_wallet(identity_wallet: Principal, blob: Vec<u8>) -> Result<String, String> {
let caller = ic_cdk::api::caller();
let eligibility = check_achievement_eligibility(caller, blob).await.unwrap();
if eligibility {
let message = build_principals_message(caller, identity_wallet);
let signature = sign(message).await?;
update_principal_to_hash(caller, Signature(signature.clone().signature_hex))?;
Ok(String::from(format!("Succesfully generate hash for Identity Wallet. Signature {}", signature.signature_hex)))
} else {
Err(String::from("Caller principal is not eligible"))
}
}
/// Receives an achievement for the caller's identity wallet.
///
/// # Arguments
///
/// * `blob` - Additional data for receiving the achievement.
///
/// # Returns
///
/// * `Result<String, String>` - The result of the achievement reception.
///
/// # Example
///
/// ```
/// dfx --identity pa_identity_wallet canister call reputation_module issueAchievementToIdentityWallet "(principal \"$(dfx canister id achievement)\")"
///
/// (variant { Ok = "Achievement issued" })
/// ```
#[update(name = "receiveAchievementFromIdentityWallet")]
async fn receive_achievement_from_identity_wallet(blob: Vec<u8>) -> Result<String, String> {
let caller = ic_cdk::api::caller();
let eligibility = check_achievement_eligibility(caller, blob).await.unwrap();
if eligibility {
let allowed_status = AchievementStatusEnum::Allowed;
update_principal_to_achievement_status(caller, AchievementStatus(allowed_status.to_u8()))?;
Ok(String::from("Achievement status changed to allowed"))
} else {
Err(String::from("Caller principal is not eligible"))
}
}
/// Receives an achievement for the caller's identity wallet using a hash.
///
/// # Arguments
///
/// * `principal` - The principal of the identity wallet.
///
/// # Returns
///
/// * `Result<String, String>` - The result of the achievement reception.
///
/// # Example
///
/// ```
/// dfx --identity pa_identity_wallet canister call achievement receiveAchievementFromIdentityWalletWithHash "(principal \"$(dfx --identity pa_local_wallet identity get-principal)\")"
///
/// (variant { Ok = "Achievement status changed to allowed" })
/// ```
#[update(name = "receiveAchievementFromIdentityWalletWithHash")]
async fn receive_achievement_from_identity_wallet_with_hash(principal: Principal) -> Result<String, String> {
let caller = ic_cdk::api::caller();
let hash = get_principal_to_hash_value(principal)?;
let public_key = public_key().await?;
let message = build_principals_message(principal, caller);
let eligibility = verify(hash.0, message, public_key.public_key_hex).await?;
if eligibility.is_signature_valid {
let allowed_status = AchievementStatusEnum::Allowed;
update_principal_to_achievement_status(caller, AchievementStatus(allowed_status.to_u8()))?;
Ok(String::from("Achievement status changed to allowed"))
} else {
Err(String::from("Principal is not eligible or hash mismatch"))
}
}