use atp_caip::curve::Curve;
use candid::{CandidType, Decode, Encode, Principal};
use ic_nosql::traits::Model;
use ic_stable_structures::{storable::Bound, Storable};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use crate::domain::models::signer::SignatureAlgorithm;
use crate::generate_getters;
use crate::utils::ic::api::get_ic_api;
#[derive(CandidType, Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum AccountState {
#[serde(rename = "locked")]
Locked,
#[serde(rename = "unlocked")]
Unlocked,
#[serde(rename = "active")]
Active,
}
impl Storable for AccountState {
fn to_bytes(&self) -> Cow<[u8]> {
Cow::Owned(Encode!(self).unwrap())
}
fn from_bytes(bytes: Cow<[u8]>) -> Self {
Decode!(bytes.as_ref(), AccountState).unwrap()
}
const BOUND: Bound = Bound::Unbounded;
}
#[derive(CandidType, Clone, Serialize, Deserialize, Debug)]
pub struct Account {
id: String,
owner: Principal,
public_key: Vec<u8>,
algorithm: SignatureAlgorithm,
curve: Curve,
account_state: AccountState,
approved_address: Option<Principal>,
}
impl Storable for Account {
fn to_bytes(&self) -> Cow<[u8]> {
Cow::Owned(Encode!(self).unwrap())
}
fn from_bytes(bytes: Cow<[u8]>) -> Self {
Decode!(bytes.as_ref(), Account).unwrap()
}
const BOUND: Bound = Bound::Unbounded;
}
#[derive(CandidType, Clone, Serialize, Deserialize)]
pub struct AccountReply {
pub id: String,
pub owner: String,
pub public_key_hex: String,
pub algorithm: SignatureAlgorithm,
pub curve: Curve,
pub account_state: AccountState,
pub approved_address: String,
}
impl Account {
pub fn new(
id: String,
owner: Principal,
public_key: Vec<u8>,
algorithm: SignatureAlgorithm,
curve: Curve,
approved_address: Principal,
) -> Self {
Account {
id,
owner,
public_key,
algorithm,
curve,
account_state: AccountState::Locked,
approved_address: Some(approved_address),
}
}
generate_getters!(
id: String,
owner: Principal,
public_key: Vec<u8>,
algorithm: SignatureAlgorithm,
curve: Curve,
account_state: AccountState,
approved_address: Option<Principal>
);
pub fn to_account_reply(&self) -> AccountReply {
AccountReply {
id: self.id.clone(),
owner: self.owner.to_string(),
public_key_hex: hex::encode(&self.public_key),
algorithm: self.algorithm.clone(),
curve: self.curve.clone(),
account_state: self.account_state.clone(),
approved_address: match &self.approved_address {
Some(address) => address.to_string(),
None => "".to_string(),
},
}
}
pub fn is_approved(&self, caller: Principal) -> bool {
match self.approved_address {
Some(address) => address == caller,
None => false,
}
}
pub fn is_owner(&self, caller: Principal) -> bool {
self.owner == caller
}
pub fn transfer_account(&mut self, to: Principal) -> Result<Account, String> {
let ic_api = get_ic_api();
if self.is_approved(ic_api.caller()) {
if self.account_state == AccountState::Locked {
self.owner = to;
self.approved_address = None;
self.account_state = AccountState::Unlocked;
Ok(self.clone())
} else {
Err("Account must be locked to transfer".to_string())
}
} else {
Err("Caller is not approved to transfer the account".to_string())
}
}
pub fn approve_address(&mut self, address: Principal) -> Result<Account, String> {
let ic_api = get_ic_api();
if self.is_owner(ic_api.caller()) {
match &self.approved_address {
Some(approved_address) => {
if approved_address == &address {
Err("This account is already approved".to_string())
} else {
self.approved_address = Some(address);
Ok(self.clone())
}
}
None => {
self.approved_address = Some(address);
Ok(self.clone())
}
}
} else {
Err("Caller is not the owner of the account".to_string())
}
}
pub fn revoke_address(&mut self, address: Principal) -> Result<Account, String> {
let ic_api = get_ic_api();
if self.is_owner(ic_api.caller()) {
match &self.approved_address {
Some(approved_address) => {
if approved_address == &address {
self.approved_address = None;
Ok(self.clone())
} else {
Err("This account is not approved".to_string())
}
}
None => Err("This account is not approved".to_string()),
}
} else {
Err("Caller is not the owner of the account".to_string())
}
}
pub fn unlock(&mut self) -> Result<Account, String> {
match self.account_state {
AccountState::Locked => {
let ic_api = get_ic_api();
if self.is_approved(ic_api.caller()) || self.is_approved(ic_api.id()) {
self.account_state = AccountState::Unlocked;
Ok(self.clone())
} else {
Err("Caller is not approved".to_string())
}
}
AccountState::Unlocked => Err("Account is already unlocked".to_string()),
AccountState::Active => Err("Account is already active".to_string()),
}
}
pub fn lock(&mut self) -> Result<Account, String> {
match self.account_state {
AccountState::Locked => Err("Account is already locked".to_string()),
AccountState::Unlocked => {
if self.is_approved(ic_cdk::api::caller()) || self.is_approved(ic_cdk::api::id()) {
self.account_state = AccountState::Locked;
Ok(self.clone())
} else {
Err("Caller is not approved".to_string())
}
}
AccountState::Active => Err("Account is already active".to_string()),
}
}
pub fn activate(&mut self) -> Result<Account, String> {
match self.account_state {
AccountState::Locked => Err("Account is locked".to_string()),
AccountState::Active => Err("Account is already activated".to_string()),
AccountState::Unlocked => {
let ic_api = get_ic_api();
if self.is_owner(ic_api.caller()) {
self.account_state = AccountState::Active;
Ok(self.clone())
} else {
Err("Caller is not the owner".to_string())
}
}
}
}
}
impl Model for Account {
type PrimaryKey = String;
type SecondaryKey = String;
fn get_primary_key(&self) -> Self::PrimaryKey {
self.id.clone()
}
fn get_secondary_key(&self) -> Option<Self::SecondaryKey> {
Some(self.owner.to_string())
}
fn model_name() -> &'static str {
"accounts"
}
}