Skip to content

Commit

Permalink
feat: transfer_from and register_contract api
Browse files Browse the repository at this point in the history
  • Loading branch information
veeso committed Nov 8, 2023
1 parent c73fd3c commit f30733b
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 13 deletions.
10 changes: 10 additions & 0 deletions src/did/src/fly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Types associated to the "Fly" canister
use candid::{CandidType, Deserialize};
use thiserror::Error;

#[derive(Clone, Debug, Error, CandidType, PartialEq, Eq, Deserialize)]
pub enum FlyError {
#[error("storage error")]
StorageError,
}
1 change: 1 addition & 0 deletions src/did/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
mod common;

pub mod fly;
pub mod sell_contract;
pub use common::{StorableNat, StorablePrincipal, ID};
9 changes: 9 additions & 0 deletions src/did/src/sell_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ic_stable_structures::storable::Bound;
use ic_stable_structures::Storable;
use thiserror::Error;

use crate::fly::FlyError;
use crate::ID;

pub type SellContractResult<T> = Result<T, SellContractError>;
Expand All @@ -20,6 +21,10 @@ pub struct SellContractInitData {

#[derive(Clone, Debug, Error, CandidType, PartialEq, Eq, Deserialize)]
pub enum SellContractError {
#[error("unauthorized caller")]
Unauthorized,
#[error("fly error: {0}")]
Fly(#[from] FlyError),
#[error("token error: {0}")]
Token(TokenError),
#[error("configuration error: {0}")]
Expand All @@ -46,6 +51,10 @@ pub enum TokenError {
TokenNotFound(TokenIdentifier),
#[error("the provided token ID ({0}) is burned, so it cannot be touched by any operation")]
TokenIsBurned(TokenIdentifier),
#[error("the provided contract value is not a multiple of the number of installments")]
ContractValueIsNotMultipleOfInstallments,
#[error("the provided expiration date is invalid. It must have syntax YYYY-MM-DD")]
InvalidExpirationDate,
}

#[derive(Clone, Debug, Error, CandidType, PartialEq, Eq, Deserialize)]
Expand Down
92 changes: 84 additions & 8 deletions src/sell-contract/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use candid::{Nat, Principal};
use chrono::NaiveDate;
use configuration::Configuration;
use did::sell_contract::{
BuildingData, SellContractError, SellContractInitData, SellContractResult, Token, TokenError,
BuildingData, Contract, SellContractError, SellContractInitData, SellContractResult, Token,
TokenError,
};
use did::ID;
use dip721::{
Expand Down Expand Up @@ -94,8 +95,39 @@ impl SellContract {
Ok(())
}

pub fn get_contract(id: ID) -> Option<did::sell_contract::Contract> {
Storage::get_contract(&id)
/// Inspect register contract parameters
pub fn inspect_register_contract(
id: &ID,
value: u64,
installments: u64,
expiration: &str,
) -> SellContractResult<()> {
if !Self::inspect_is_custodian() {
return Err(SellContractError::Unauthorized);
}
// check if contract already exists
if Self::get_contract(id).is_some() {
return Err(SellContractError::Token(TokenError::ContractAlreadyExists(
id.clone(),
)));
}

// verify value must be multiple of installments
if value % installments != 0 {
return Err(SellContractError::Token(
TokenError::ContractValueIsNotMultipleOfInstallments,
));
}

// check if expiration is YYYY-MM-DD
NaiveDate::parse_from_str(expiration, "%Y-%m-%d")
.map_err(|_| SellContractError::Token(TokenError::InvalidExpirationDate))?;

Ok(())
}

pub fn get_contract(id: &ID) -> Option<Contract> {
Storage::get_contract(id)
}

/// Register contract inside of the canister.
Expand All @@ -104,17 +136,61 @@ impl SellContract {
id: ID,
seller: Principal,
buyers: Vec<Principal>,
expiration_date: NaiveDate,
expiration: String,
value: u64,
installments: u64,
building: BuildingData,
) -> SellContractResult<()> {
if !Self::inspect_is_custodian() {
ic_cdk::trap("Unauthorized");
Self::inspect_register_contract(&id, value, installments, &expiration)?;

// get reward for contract
let mfly_reward = FlyClient::from(Configuration::get_fly_canister())
.get_contract_reward(id.clone(), installments)
.await?;

// make tokens
let next_token_id = Storage::total_supply();
let mut tokens = Vec::with_capacity(installments as usize);
let mut tokens_ids = Vec::with_capacity(installments as usize);
let token_value: u64 = value / installments;
let marketplace_canister = Configuration::get_marketplace_canister();

for token_id in next_token_id..next_token_id + installments {
tokens.push(Token {
approved_at: Some(crate::utils::time()),
approved_by: Some(caller()),
burned_at: None,
burned_by: None,
contract_id: id.clone(),
id: token_id.into(),
is_burned: false,
minted_at: crate::utils::time(),
minted_by: caller(),
operator: Some(marketplace_canister), // * the operator must be the marketplace canister
owner: Some(seller),
transferred_at: None,
transferred_by: None,
value: token_value,
});
tokens_ids.push(token_id.into());
}

// TODO: set operator to marketplace
todo!();
// make contract
let contract = Contract {
building,
buyers,
expiration,
id: id.clone(),
mfly_reward,
seller,
tokens: tokens_ids,
value,
};

// register contract
Storage::insert_contract(contract, tokens)?;

Ok(())
}

/// Update marketplace canister id and update the operator for all the tokens
Expand Down
7 changes: 4 additions & 3 deletions src/sell-contract/src/app/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ pub use tx_history::TxHistory;
pub struct Storage;

thread_local! {

/// Contracts storage (1 contract has many tokens)
static CONTRACTS: RefCell<BTreeMap<ID, Contract, VirtualMemory<DefaultMemoryImpl>>> =
RefCell::new(BTreeMap::new(MEMORY_MANAGER.with(|mm| mm.get(CONTRACTS_MEMORY_ID))));
Expand Down Expand Up @@ -257,8 +256,10 @@ mod test {
Principal::from_text("zrrb4-gyxmq-nx67d-wmbky-k6xyt-byhmw-tr5ct-vsxu4-nuv2g-6rr65-aae")
.unwrap();
let contract_id = ID::random();
let next_token_id = Storage::total_supply();
assert_eq!(next_token_id, Nat::from(0));
let token_1 = Token {
id: TokenIdentifier::from(1),
id: next_token_id.into(),
contract_id: contract_id.clone(),
owner: Some(seller),
value: 100,
Expand All @@ -274,7 +275,7 @@ mod test {
operator: Some(seller),
};
let token_2 = Token {
id: TokenIdentifier::from(2),
id: (next_token_id + 1).into(),
contract_id: contract_id.clone(),
owner: Some(seller),
value: 100,
Expand Down
6 changes: 5 additions & 1 deletion src/sell-contract/src/client/fly_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ impl From<Principal> for FlyClient {

impl FlyClient {
/// Get contract reward. Returns $mFly
pub async fn get_contract_reward(&self, contract_id: ID) -> SellContractResult<u64> {
pub async fn get_contract_reward(
&self,
contract_id: ID,
installments: u64,
) -> SellContractResult<u64> {
todo!()
}

Expand Down
16 changes: 15 additions & 1 deletion src/sell-contract/src/inspect.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use candid::{Nat, Principal};
use did::sell_contract::BuildingData;
use did::ID;
use ic_cdk::api;
#[cfg(target_family = "wasm")]
use ic_cdk_macros::inspect_message;
Expand All @@ -20,9 +22,21 @@ fn inspect_message_impl() {

let check_result = match method.as_str() {
method if method.starts_with("admin_") => SellContract::inspect_is_custodian(),
"set_logo" | "set_name" | "set_symbol" | "set_custodians" | "register_contract" => {
"set_logo" | "set_name" | "set_symbol" | "set_custodians" => {
SellContract::inspect_is_custodian()
}
"register_contract" => {
let (id, _, _, expiration, value, installments, _) = api::call::arg_data::<(
ID,
Principal,
Vec<Principal>,
String,
u64,
u64,
BuildingData,
)>();
SellContract::inspect_register_contract(&id, value, installments, &expiration).is_ok()
}
"burn" => {
let token_identifier = api::call::arg_data::<(Nat,)>().0;
SellContract::inspect_burn(&token_identifier).is_ok()
Expand Down

0 comments on commit f30733b

Please sign in to comment.