use rhai::plugin::*; use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use std::sync::Arc; use std::mem; use chrono::Utc; use super::account::Account; use super::asset::{Asset, AssetType}; use super::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; use crate::db::hero::OurDB; use crate::db::{Collection, Db}; type RhaiAccount = Account; type RhaiAsset = Asset; type RhaiListing = Listing; type RhaiBid = Bid; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { u32::try_from(id_i64).map_err(|_| Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), Position::NONE )) ) } // Helper functions for enum conversions // AssetType conversions fn asset_type_to_string(asset_type: &AssetType) -> String { match asset_type { AssetType::Native => "Native".to_string(), AssetType::Erc20 => "Erc20".to_string(), AssetType::Erc721 => "Erc721".to_string(), AssetType::Erc1155 => "Erc1155".to_string(), } } fn string_to_asset_type(s: &str) -> Result> { match s { "Native" => Ok(AssetType::Native), "Erc20" => Ok(AssetType::Erc20), "Erc721" => Ok(AssetType::Erc721), "Erc1155" => Ok(AssetType::Erc1155), _ => Err(Box::new(EvalAltResult::ErrorRuntime( format!("Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", s).into(), Position::NONE ))), } } // ListingStatus conversions fn listing_status_to_string(status: &ListingStatus) -> String { match status { ListingStatus::Active => "Active".to_string(), ListingStatus::Sold => "Sold".to_string(), ListingStatus::Cancelled => "Cancelled".to_string(), ListingStatus::Expired => "Expired".to_string(), } } fn string_to_listing_status(s: &str) -> Result> { match s { "Active" => Ok(ListingStatus::Active), "Sold" => Ok(ListingStatus::Sold), "Cancelled" => Ok(ListingStatus::Cancelled), "Expired" => Ok(ListingStatus::Expired), _ => Err(Box::new(EvalAltResult::ErrorRuntime( format!("Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", s).into(), Position::NONE ))), } } // ListingType conversions fn listing_type_to_string(lt: &ListingType) -> String { match lt { ListingType::FixedPrice => "FixedPrice".to_string(), ListingType::Auction => "Auction".to_string(), ListingType::Exchange => "Exchange".to_string(), } } fn string_to_listing_type(s: &str) -> Result> { match s { "FixedPrice" => Ok(ListingType::FixedPrice), "Auction" => Ok(ListingType::Auction), "Exchange" => Ok(ListingType::Exchange), _ => Err(Box::new(EvalAltResult::ErrorRuntime( format!("Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", s).into(), Position::NONE ))), } } // BidStatus conversions fn bid_status_to_string(status: &BidStatus) -> String { match status { BidStatus::Active => "Active".to_string(), BidStatus::Accepted => "Accepted".to_string(), BidStatus::Rejected => "Rejected".to_string(), BidStatus::Cancelled => "Cancelled".to_string(), } } fn string_to_bid_status(s: &str) -> Result> { match s { "Active" => Ok(BidStatus::Active), "Accepted" => Ok(BidStatus::Accepted), "Rejected" => Ok(BidStatus::Rejected), "Cancelled" => Ok(BidStatus::Cancelled), _ => Err(Box::new(EvalAltResult::ErrorRuntime( format!("Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", s).into(), Position::NONE ))), } } // Account module #[export_module] mod account_module { use super::*; // Constructor #[rhai_fn(return_raw, global)] pub fn new_account() -> Result> { Ok(Account::new()) } // Getters #[rhai_fn(global, pure)] pub fn get_id(account: &mut RhaiAccount) -> INT { account.base_data.id as INT } #[rhai_fn(global, pure)] pub fn get_created_at(account: &mut RhaiAccount) -> INT { account.base_data.created_at } #[rhai_fn(global, pure)] pub fn get_name(account: &mut RhaiAccount) -> String { account.name.clone() } #[rhai_fn(global, pure)] pub fn get_user_id(account: &mut RhaiAccount) -> INT { account.user_id as INT } #[rhai_fn(global, pure)] pub fn get_description(account: &mut RhaiAccount) -> String { account.description.clone() } #[rhai_fn(global, pure)] pub fn get_ledger(account: &mut RhaiAccount) -> String { account.ledger.clone() } #[rhai_fn(global, pure)] pub fn get_address(account: &mut RhaiAccount) -> String { account.address.clone() } #[rhai_fn(global, pure)] pub fn get_pubkey(account: &mut RhaiAccount) -> String { account.pubkey.clone() } #[rhai_fn(global, pure)] pub fn get_assets(account: &mut RhaiAccount) -> Array { let mut assets_array = Array::new(); for asset_id in &account.assets { assets_array.push(Dynamic::from(*asset_id as INT)); } assets_array } // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] pub fn name(account: &mut RhaiAccount, name: String) -> Result> { let mut acc = mem::take(account); acc = acc.name(name); *account = acc; Ok(account.clone()) } #[rhai_fn(return_raw, global)] pub fn user_id(account: &mut RhaiAccount, user_id: INT) -> Result> { let user_id = id_from_i64_to_u32(user_id)?; let mut acc = mem::take(account); acc = acc.user_id(user_id); *account = acc; Ok(account.clone()) } #[rhai_fn(return_raw, global)] pub fn description(account: &mut RhaiAccount, description: String) -> Result> { let mut acc = mem::take(account); acc = acc.description(description); *account = acc; Ok(account.clone()) } #[rhai_fn(return_raw, global)] pub fn ledger(account: &mut RhaiAccount, ledger: String) -> Result> { let mut acc = mem::take(account); acc = acc.ledger(ledger); *account = acc; Ok(account.clone()) } #[rhai_fn(return_raw, global)] pub fn address(account: &mut RhaiAccount, address: String) -> Result> { let mut acc = mem::take(account); acc = acc.address(address); *account = acc; Ok(account.clone()) } #[rhai_fn(return_raw, global)] pub fn pubkey(account: &mut RhaiAccount, pubkey: String) -> Result> { let mut acc = mem::take(account); acc = acc.pubkey(pubkey); *account = acc; Ok(account.clone()) } #[rhai_fn(return_raw, global)] pub fn add_asset(account: &mut RhaiAccount, asset_id: INT) -> Result> { let asset_id = id_from_i64_to_u32(asset_id)?; let mut acc = mem::take(account); acc = acc.add_asset(asset_id); *account = acc; Ok(account.clone()) } } // Asset module #[export_module] mod asset_module { use super::*; // Constructor #[rhai_fn(return_raw, global)] pub fn new_asset() -> Result> { Ok(Asset::new()) } // Getters #[rhai_fn(global, pure)] pub fn get_id(asset: &mut RhaiAsset) -> INT { asset.base_data.id as INT } #[rhai_fn(global, pure)] pub fn get_created_at(asset: &mut RhaiAsset) -> INT { asset.base_data.created_at } #[rhai_fn(global, pure)] pub fn get_name(asset: &mut RhaiAsset) -> String { asset.name.clone() } #[rhai_fn(global, pure)] pub fn get_description(asset: &mut RhaiAsset) -> String { asset.description.clone() } #[rhai_fn(global, pure)] pub fn get_amount(asset: &mut RhaiAsset) -> f64 { asset.amount } #[rhai_fn(global, pure)] pub fn get_address(asset: &mut RhaiAsset) -> String { asset.address.clone() } #[rhai_fn(global, pure)] pub fn get_asset_type(asset: &mut RhaiAsset) -> String { asset_type_to_string(&asset.asset_type) } #[rhai_fn(global, pure)] pub fn get_decimals(asset: &mut RhaiAsset) -> INT { asset.decimals as INT } // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] pub fn name(asset: &mut RhaiAsset, name: String) -> Result> { let mut ast = mem::take(asset); ast = ast.name(name); *asset = ast; Ok(asset.clone()) } #[rhai_fn(return_raw, global)] pub fn description(asset: &mut RhaiAsset, description: String) -> Result> { let mut ast = mem::take(asset); ast = ast.description(description); *asset = ast; Ok(asset.clone()) } #[rhai_fn(return_raw, global)] pub fn amount(asset: &mut RhaiAsset, amount: f64) -> Result> { let mut ast = mem::take(asset); ast = ast.amount(amount); *asset = ast; Ok(asset.clone()) } #[rhai_fn(return_raw, global)] pub fn address(asset: &mut RhaiAsset, address: String) -> Result> { let mut ast = mem::take(asset); ast = ast.address(address); *asset = ast; Ok(asset.clone()) } #[rhai_fn(return_raw, global)] pub fn asset_type(asset: &mut RhaiAsset, asset_type_str: String) -> Result> { let asset_type_enum = string_to_asset_type(&asset_type_str)?; let mut ast = mem::take(asset); ast = ast.asset_type(asset_type_enum); *asset = ast; Ok(asset.clone()) } #[rhai_fn(return_raw, global)] pub fn decimals(asset: &mut RhaiAsset, decimals: INT) -> Result> { if decimals < 0 || decimals > 255 { return Err(Box::new(EvalAltResult::ErrorArithmetic( format!("Decimals value must be between 0 and 255, got {}", decimals).into(), Position::NONE ))); } let mut ast = mem::take(asset); ast = ast.decimals(decimals as u8); *asset = ast; Ok(asset.clone()) } } // Listing module #[export_module] mod listing_module { use super::*; // Constructor #[rhai_fn(return_raw, global)] pub fn new_listing() -> Result> { Ok(Listing::new()) } // Getters #[rhai_fn(global, pure)] pub fn get_id(listing: &mut RhaiListing) -> INT { listing.base_data.id as INT } #[rhai_fn(global, pure)] pub fn get_created_at(listing: &mut RhaiListing) -> INT { listing.base_data.created_at } #[rhai_fn(global, pure)] pub fn get_seller_id(listing: &mut RhaiListing) -> INT { // Parse the seller_id string to u32, then to INT listing.seller_id.parse::().unwrap_or(0) as INT } #[rhai_fn(global, pure)] pub fn get_asset_id(listing: &mut RhaiListing) -> INT { // Parse the asset_id string to u32, then to INT listing.asset_id.parse::().unwrap_or(0) as INT } #[rhai_fn(global, pure)] pub fn get_price(listing: &mut RhaiListing) -> f64 { listing.price } #[rhai_fn(global, pure)] pub fn get_currency(listing: &mut RhaiListing) -> String { listing.currency.clone() } #[rhai_fn(global, pure)] pub fn get_listing_type(listing: &mut RhaiListing) -> String { listing_type_to_string(&listing.listing_type) } #[rhai_fn(global, pure)] pub fn get_status(listing: &mut RhaiListing) -> String { listing_status_to_string(&listing.status) } #[rhai_fn(global, pure)] pub fn get_title(listing: &mut RhaiListing) -> String { listing.title.clone() } #[rhai_fn(global, pure)] pub fn get_description(listing: &mut RhaiListing) -> String { listing.description.clone() } #[rhai_fn(global, pure)] pub fn get_image_url(listing: &mut RhaiListing) -> String { listing.image_url.clone().unwrap_or_else(|| "".to_string()) } #[rhai_fn(global, pure)] pub fn get_end_date(listing: &mut RhaiListing) -> INT { listing.expires_at.map_or(0, |d| d.timestamp()) } #[rhai_fn(global, pure)] pub fn get_tags(listing: &mut RhaiListing) -> Array { let mut tags_array = Array::new(); for tag in &listing.tags { tags_array.push(Dynamic::from(tag.clone())); } tags_array } #[rhai_fn(global, pure)] pub fn get_bids(listing: &mut RhaiListing) -> Array { let mut bids_array = Array::new(); for bid in &listing.bids { bids_array.push(Dynamic::from(bid.clone())); } bids_array } // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] pub fn seller_id(listing: &mut RhaiListing, seller_id: INT) -> Result> { let seller_id = id_from_i64_to_u32(seller_id)?; let mut lst = mem::take(listing); lst = lst.seller_id(seller_id); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn asset_id(listing: &mut RhaiListing, asset_id: INT) -> Result> { let asset_id = id_from_i64_to_u32(asset_id)?; let mut lst = mem::take(listing); lst = lst.asset_id(asset_id); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn price(listing: &mut RhaiListing, price: f64) -> Result> { let mut lst = mem::take(listing); lst = lst.price(price); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn currency(listing: &mut RhaiListing, currency: String) -> Result> { let mut lst = mem::take(listing); lst = lst.currency(currency); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn listing_type(listing: &mut RhaiListing, listing_type_str: String) -> Result> { let listing_type_enum = string_to_listing_type(&listing_type_str)?; let mut lst = mem::take(listing); lst = lst.listing_type(listing_type_enum); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn title(listing: &mut RhaiListing, title: String) -> Result> { let mut lst = mem::take(listing); lst = lst.title(title); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn description(listing: &mut RhaiListing, description: String) -> Result> { let mut lst = mem::take(listing); lst = lst.description(description); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn image_url(listing: &mut RhaiListing, image_url: String) -> Result> { let mut lst = mem::take(listing); lst = lst.image_url(Some(image_url)); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn expires_at(listing: &mut RhaiListing, end_date_ts: INT) -> Result> { use chrono::TimeZone; let end_date = chrono::Utc.timestamp_opt(end_date_ts, 0) .single() .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( format!("Invalid timestamp: {}", end_date_ts).into(), Position::NONE )))?; let mut lst = mem::take(listing); lst = lst.expires_at(Some(end_date)); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn add_tag(listing: &mut RhaiListing, tag: String) -> Result> { let mut lst = mem::take(listing); lst = lst.add_tag(tag); *listing = lst; Ok(listing.clone()) } #[rhai_fn(return_raw, global)] pub fn add_bid(listing: &mut RhaiListing, bid: RhaiBid) -> Result> { let lst = mem::take(listing); match lst.clone().add_bid(bid) { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) }, Err(err) => { // Put back the original listing since the add_bid failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to add bid: {}", err).into(), Position::NONE ))) } } } #[rhai_fn(return_raw, global)] pub fn complete_sale(listing: &mut RhaiListing, buyer_id: INT) -> Result> { let buyer_id = id_from_i64_to_u32(buyer_id)?; let lst = mem::take(listing); // First set the buyer_id and sale_price let lst_with_buyer = lst.clone().buyer_id(buyer_id); // Now complete the sale match lst_with_buyer.complete_sale() { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) }, Err(err) => { // Put back the original listing since the complete_sale failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to complete sale: {}", err).into(), Position::NONE ))) } } } #[rhai_fn(return_raw, global)] pub fn cancel(listing: &mut RhaiListing) -> Result> { let lst = mem::take(listing); match lst.clone().cancel() { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) }, Err(err) => { // Put back the original listing since the cancel failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to cancel listing: {}", err).into(), Position::NONE ))) } } } #[rhai_fn(return_raw, global)] pub fn check_expiration(listing: &mut RhaiListing) -> Result> { let mut lst = mem::take(listing); lst = lst.check_expiration(); *listing = lst; Ok(listing.clone()) } } // Bid module #[export_module] mod bid_module { use super::*; // Constructor #[rhai_fn(return_raw, global)] pub fn new_bid() -> Result> { Ok(Bid::new()) } // Getters #[rhai_fn(global, pure)] pub fn get_listing_id(bid: &mut RhaiBid) -> String { bid.listing_id.clone() } #[rhai_fn(global, pure)] pub fn get_bidder_id(bid: &mut RhaiBid) -> INT { bid.bidder_id as INT } #[rhai_fn(global, pure)] pub fn get_amount(bid: &mut RhaiBid) -> f64 { bid.amount } #[rhai_fn(global, pure)] pub fn get_currency(bid: &mut RhaiBid) -> String { bid.currency.clone() } #[rhai_fn(global, pure)] pub fn get_status(bid: &mut RhaiBid) -> String { bid_status_to_string(&bid.status) } #[rhai_fn(global, pure)] pub fn get_created_at(bid: &mut RhaiBid) -> INT { bid.created_at.timestamp() } // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] pub fn listing_id(bid: &mut RhaiBid, listing_id: String) -> Result> { let mut b = mem::take(bid); b = b.listing_id(listing_id); *bid = b; Ok(bid.clone()) } #[rhai_fn(return_raw, global)] pub fn bidder_id(bid: &mut RhaiBid, bidder_id: INT) -> Result> { let bidder_id = id_from_i64_to_u32(bidder_id)?; let mut b = mem::take(bid); b = b.bidder_id(bidder_id); *bid = b; Ok(bid.clone()) } #[rhai_fn(return_raw, global)] pub fn amount(bid: &mut RhaiBid, amount: f64) -> Result> { let mut b = mem::take(bid); b = b.amount(amount); *bid = b; Ok(bid.clone()) } #[rhai_fn(return_raw, global)] pub fn currency(bid: &mut RhaiBid, currency: String) -> Result> { let mut b = mem::take(bid); b = b.currency(currency); *bid = b; Ok(bid.clone()) } #[rhai_fn(return_raw, global)] pub fn update_status(bid: &mut RhaiBid, status_str: String) -> Result> { let status = string_to_bid_status(&status_str)?; let mut b = mem::take(bid); b = b.status(status); *bid = b; Ok(bid.clone()) } } use rhai::ImmutableString; // Register all Rhai functions for the finance module pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { // --- Register model-specific modules with the engine --- let account_module = exported_module!(account_module); engine.register_global_module(account_module.into()); let asset_module = exported_module!(asset_module); engine.register_global_module(asset_module.into()); let listing_module = exported_module!(listing_module); engine.register_global_module(listing_module.into()); let bid_module = exported_module!(bid_module); engine.register_global_module(bid_module.into()); // --- Global Helper Functions (Enum conversions) --- engine.register_fn("str_to_asset_type", |s: ImmutableString| self::string_to_asset_type(s.as_str())); engine.register_fn("asset_type_to_str", self::asset_type_to_string); engine.register_fn("str_to_listing_status", |s: ImmutableString| self::string_to_listing_status(s.as_str())); engine.register_fn("listing_status_to_str", self::listing_status_to_string); engine.register_fn("str_to_listing_type", |s: ImmutableString| self::string_to_listing_type(s.as_str())); engine.register_fn("listing_type_to_str", self::listing_type_to_string); engine.register_fn("str_to_bid_status", |s: ImmutableString| self::string_to_bid_status(s.as_str())); engine.register_fn("bid_status_to_str", self::bid_status_to_string); // --- Database interaction functions (registered in a separate db_module) --- let mut db_module = Module::new(); // Account DB functions let db_set_account = Arc::clone(&db); db_module.set_native_fn("set_account", move |account: Account| -> Result> { let collection = db_set_account.collection::().map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; collection.set(&account) .map(|(id, _)| id as INT) .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Account: {:?}", e).into(), Position::NONE))) }); let db_get_account = Arc::clone(&db); db_module.set_native_fn("get_account_by_id", move |id_rhai: INT| -> Result> { let id_u32 = id_from_i64_to_u32(id_rhai)?; let collection = db_get_account.collection::().map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; collection.get_by_id(id_u32) .map(|opt_account| opt_account.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) }); // Asset DB functions let db_set_asset = Arc::clone(&db); db_module.set_native_fn("set_asset", move |asset: Asset| -> Result> { let collection = db_set_asset.collection::().map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; collection.set(&asset) .map(|(id, _)| id as INT) .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Asset: {:?}", e).into(), Position::NONE))) }); let db_get_asset = Arc::clone(&db); db_module.set_native_fn("get_asset_by_id", move |id_rhai: INT| -> Result> { let id_u32 = id_from_i64_to_u32(id_rhai)?; let collection = db_get_asset.collection::().map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; collection.get_by_id(id_u32) .map(|opt_asset| opt_asset.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) }); // Listing DB functions let db_set_listing = Arc::clone(&db); db_module.set_native_fn("set_listing", move |listing: Listing| -> Result> { let collection = db_set_listing.collection::().map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; collection.set(&listing) .map(|(id, _)| id as INT) .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Listing: {:?}", e).into(), Position::NONE))) }); let db_get_listing = Arc::clone(&db); db_module.set_native_fn("get_listing_by_id", move |id_rhai: INT| -> Result> { let id_u32 = id_from_i64_to_u32(id_rhai)?; let collection = db_get_listing.collection::().map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; collection.get_by_id(id_u32) .map(|opt_listing| opt_listing.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) }); engine.register_global_module(db_module.into()); // Global timestamp function for scripts to get current time engine.register_fn("timestamp", || Utc::now().timestamp()); println!("Successfully registered finance Rhai module."); }