367 lines
14 KiB
Rust
367 lines
14 KiB
Rust
use heromodels::db::hero::OurDB;
|
|
use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot};
|
|
use rhai::Engine;
|
|
use rhai_wrapper::wrap_vec_return;
|
|
use std::sync::Arc;
|
|
use chrono::{Utc, Duration};
|
|
use rhai_client_macros::rhai;
|
|
|
|
// Define the functions we want to expose to Rhai
|
|
// We'll only use the #[rhai] attribute on functions with simple types
|
|
|
|
// Create a proposal (returns a complex type, but takes simple parameters)
|
|
fn create_proposal(id: i64, creator_id: String, title: String, description: String) -> Proposal {
|
|
let start_date = Utc::now();
|
|
let end_date = start_date + Duration::days(14);
|
|
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
|
|
}
|
|
|
|
// Getter functions for Proposal properties
|
|
fn get_title(proposal: &Proposal) -> String {
|
|
proposal.title.clone()
|
|
}
|
|
|
|
fn get_description(proposal: &Proposal) -> String {
|
|
proposal.description.clone()
|
|
}
|
|
|
|
fn get_creator_id(proposal: &Proposal) -> String {
|
|
proposal.creator_id.clone()
|
|
}
|
|
|
|
fn get_id(proposal: &Proposal) -> i64 {
|
|
proposal.base_data.id as i64
|
|
}
|
|
|
|
fn get_status(proposal: &Proposal) -> String {
|
|
format!("{:?}", proposal.status)
|
|
}
|
|
|
|
fn get_vote_status(proposal: &Proposal) -> String {
|
|
format!("{:?}", proposal.vote_status)
|
|
}
|
|
|
|
// Functions that operate on Proposal objects
|
|
fn add_option_to_proposal(proposal: Proposal, option_id: i64, option_text: String) -> Proposal {
|
|
proposal.add_option(option_id as u8, option_text)
|
|
}
|
|
|
|
fn cast_vote_on_proposal(proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64) -> Proposal {
|
|
proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares)
|
|
}
|
|
|
|
fn change_proposal_status(proposal: Proposal, status_str: String) -> Proposal {
|
|
let new_status = match status_str.as_str() {
|
|
"Draft" => ProposalStatus::Draft,
|
|
"Active" => ProposalStatus::Active,
|
|
"Approved" => ProposalStatus::Approved,
|
|
"Rejected" => ProposalStatus::Rejected,
|
|
"Cancelled" => ProposalStatus::Cancelled,
|
|
_ => ProposalStatus::Draft,
|
|
};
|
|
proposal.change_proposal_status(new_status)
|
|
}
|
|
|
|
fn change_vote_event_status(proposal: Proposal, status_str: String) -> Proposal {
|
|
let new_status = match status_str.as_str() {
|
|
"Open" => VoteEventStatus::Open,
|
|
"Closed" => VoteEventStatus::Closed,
|
|
"Cancelled" => VoteEventStatus::Cancelled,
|
|
_ => VoteEventStatus::Open,
|
|
};
|
|
proposal.change_vote_event_status(new_status)
|
|
}
|
|
|
|
// Functions for accessing proposal options and ballots
|
|
fn get_option_count(proposal: &Proposal) -> i64 {
|
|
proposal.options.len() as i64
|
|
}
|
|
|
|
fn get_option_at(proposal: &Proposal, index: i64) -> VoteOption {
|
|
if index >= 0 && index < proposal.options.len() as i64 {
|
|
proposal.options[index as usize].clone()
|
|
} else {
|
|
VoteOption::new(0, "Invalid Option")
|
|
}
|
|
}
|
|
|
|
fn get_option_text(option: &VoteOption) -> String {
|
|
option.text.clone()
|
|
}
|
|
|
|
fn get_option_votes(option: &VoteOption) -> i64 {
|
|
option.count
|
|
}
|
|
|
|
fn get_ballot_count(proposal: &Proposal) -> i64 {
|
|
proposal.ballots.len() as i64
|
|
}
|
|
|
|
fn get_ballot_at(proposal: &Proposal, index: i64) -> Ballot {
|
|
if index >= 0 && index < proposal.ballots.len() as i64 {
|
|
proposal.ballots[index as usize].clone()
|
|
} else {
|
|
Ballot::new(0, 0, 0, 0)
|
|
}
|
|
}
|
|
|
|
fn get_ballot_user_id(ballot: &Ballot) -> i64 {
|
|
ballot.user_id as i64
|
|
}
|
|
|
|
fn get_ballot_option_id(ballot: &Ballot) -> i64 {
|
|
ballot.vote_option_id as i64
|
|
}
|
|
|
|
fn get_ballot_shares(ballot: &Ballot) -> i64 {
|
|
ballot.shares_count
|
|
}
|
|
|
|
// Simple functions that we can use with the #[rhai] attribute
|
|
#[rhai]
|
|
fn create_proposal_wrapper(id: i64, creator_id: String, title: String, description: String) -> String {
|
|
let proposal = create_proposal(id, creator_id, title, description);
|
|
format!("Created proposal with ID: {}", proposal.base_data.id)
|
|
}
|
|
|
|
#[rhai]
|
|
fn add_option_wrapper(id: i64, option_id: i64, option_text: String) -> String {
|
|
let proposal = create_proposal(id, "user".to_string(), "title".to_string(), "description".to_string());
|
|
let updated = add_option_to_proposal(proposal, option_id, option_text.clone());
|
|
format!("Added option '{}' to proposal {}", option_text, id)
|
|
}
|
|
|
|
// Database operations
|
|
fn save_proposal(_db: Arc<OurDB>, proposal: Proposal) {
|
|
println!("Proposal saved: {}", proposal.title);
|
|
}
|
|
|
|
fn get_all_proposals(_db: Arc<OurDB>) -> Vec<Proposal> {
|
|
// In a real implementation, this would retrieve all proposals from the database
|
|
let start_date = Utc::now();
|
|
let end_date = start_date + Duration::days(14);
|
|
vec![
|
|
Proposal::new(1, "Creator 1", "Proposal 1", "Description 1", start_date, end_date),
|
|
Proposal::new(2, "Creator 2", "Proposal 2", "Description 2", start_date, end_date)
|
|
]
|
|
}
|
|
|
|
fn delete_proposal_by_id(_db: Arc<OurDB>, id: i64) {
|
|
// In a real implementation, this would delete the proposal from the database
|
|
println!("Proposal deleted with ID: {}", id);
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Initialize Rhai engine
|
|
let mut engine = Engine::new();
|
|
|
|
// Initialize database
|
|
let db = Arc::new(OurDB::new("temp_governance_db", true).expect("Failed to create database"));
|
|
|
|
// Register the Proposal type with Rhai
|
|
// This function is generated by the #[rhai_model_export] attribute
|
|
Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone());
|
|
|
|
// Register the Ballot type with Rhai
|
|
Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone());
|
|
|
|
// Create a clone of db for use in the get_db function
|
|
let db_for_get_db = db.clone();
|
|
|
|
// Register a function to get the database instance
|
|
engine.register_fn("get_db", move || db_for_get_db.clone());
|
|
|
|
// Register builder functions for Proposal and related types
|
|
engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| {
|
|
let start_date = Utc::now();
|
|
let end_date = start_date + Duration::days(14);
|
|
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
|
|
});
|
|
|
|
engine.register_fn("create_vote_option", |id: i64, text: String| {
|
|
VoteOption::new(id as u8, text)
|
|
});
|
|
|
|
engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| {
|
|
Ballot::new(id as u32, user_id as u32, vote_option_id as u8, shares_count)
|
|
});
|
|
|
|
// Register getter and setter methods for Proposal properties
|
|
engine.register_fn("get_title", get_title);
|
|
engine.register_fn("get_description", get_description);
|
|
engine.register_fn("get_creator_id", get_creator_id);
|
|
engine.register_fn("get_id", get_id);
|
|
engine.register_fn("get_status", get_status);
|
|
engine.register_fn("get_vote_status", get_vote_status);
|
|
|
|
// Register methods for proposal operations
|
|
engine.register_fn("add_option_to_proposal", add_option_to_proposal);
|
|
engine.register_fn("cast_vote_on_proposal", cast_vote_on_proposal);
|
|
engine.register_fn("change_proposal_status", change_proposal_status);
|
|
engine.register_fn("change_vote_event_status", change_vote_event_status);
|
|
|
|
// Register functions for database operations
|
|
engine.register_fn("save_proposal", save_proposal);
|
|
|
|
engine.register_fn("get_proposal_by_id", |_db: Arc<OurDB>, id: i64| -> Proposal {
|
|
// In a real implementation, this would retrieve the proposal from the database
|
|
let start_date = Utc::now();
|
|
let end_date = start_date + Duration::days(14);
|
|
Proposal::new(id as u32, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date)
|
|
});
|
|
|
|
// Register a function to check if a proposal exists
|
|
engine.register_fn("proposal_exists", |_db: Arc<OurDB>, id: i64| -> bool {
|
|
// In a real implementation, this would check if the proposal exists in the database
|
|
id == 1 || id == 2
|
|
});
|
|
|
|
// Register the function with the wrap_vec_return macro
|
|
engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc<OurDB> => Proposal));
|
|
|
|
engine.register_fn("delete_proposal_by_id", delete_proposal_by_id);
|
|
|
|
// Register helper functions for accessing proposal options and ballots
|
|
engine.register_fn("get_option_count", get_option_count);
|
|
engine.register_fn("get_option_at", get_option_at);
|
|
engine.register_fn("get_option_text", get_option_text);
|
|
engine.register_fn("get_option_votes", get_option_votes);
|
|
engine.register_fn("get_ballot_count", get_ballot_count);
|
|
engine.register_fn("get_ballot_at", get_ballot_at);
|
|
engine.register_fn("get_ballot_user_id", get_ballot_user_id);
|
|
engine.register_fn("get_ballot_option_id", get_ballot_option_id);
|
|
engine.register_fn("get_ballot_shares", get_ballot_shares);
|
|
|
|
// Register our wrapper functions that use the #[rhai] attribute
|
|
engine.register_fn("create_proposal_wrapper", create_proposal_wrapper);
|
|
engine.register_fn("add_option_wrapper", add_option_wrapper);
|
|
|
|
// Now instead of loading and evaluating a Rhai script, we'll use direct function calls
|
|
// to implement the same functionality
|
|
|
|
// Use the database instance
|
|
|
|
// Create a new proposal
|
|
let proposal = create_proposal(1,
|
|
"user_creator_123".to_string(),
|
|
"Community Fund Allocation for Q3".to_string(),
|
|
"Proposal to allocate funds for community projects in the third quarter.".to_string());
|
|
|
|
println!("Created Proposal: '{}' (ID: {})",
|
|
get_title(&proposal),
|
|
get_id(&proposal));
|
|
println!("Status: {}, Vote Status: {}",
|
|
get_status(&proposal),
|
|
get_vote_status(&proposal));
|
|
|
|
// Add vote options
|
|
let mut proposal_with_options = add_option_to_proposal(
|
|
proposal, 1, "Approve Allocation".to_string());
|
|
proposal_with_options = add_option_to_proposal(
|
|
proposal_with_options, 2, "Reject Allocation".to_string());
|
|
proposal_with_options = add_option_to_proposal(
|
|
proposal_with_options, 3, "Abstain".to_string());
|
|
|
|
println!("\nAdded Vote Options:");
|
|
let option_count = get_option_count(&proposal_with_options);
|
|
for i in 0..option_count {
|
|
let option = get_option_at(&proposal_with_options, i);
|
|
println!("- Option ID: {}, Text: '{}', Votes: {}",
|
|
i, get_option_text(&option),
|
|
get_option_votes(&option));
|
|
}
|
|
|
|
// Save the proposal to the database
|
|
save_proposal(db.clone(), proposal_with_options.clone());
|
|
println!("\nProposal saved to database");
|
|
|
|
// Simulate casting votes
|
|
println!("\nSimulating Votes...");
|
|
// User 1 votes for 'Approve Allocation' with 100 shares
|
|
let mut proposal_with_votes = cast_vote_on_proposal(
|
|
proposal_with_options, 101, 1, 1, 100);
|
|
// User 2 votes for 'Reject Allocation' with 50 shares
|
|
proposal_with_votes = cast_vote_on_proposal(
|
|
proposal_with_votes, 102, 2, 2, 50);
|
|
// User 3 votes for 'Approve Allocation' with 75 shares
|
|
proposal_with_votes = cast_vote_on_proposal(
|
|
proposal_with_votes, 103, 3, 1, 75);
|
|
// User 4 abstains with 20 shares
|
|
proposal_with_votes = cast_vote_on_proposal(
|
|
proposal_with_votes, 104, 4, 3, 20);
|
|
|
|
println!("\nVote Counts After Simulation:");
|
|
let option_count = get_option_count(&proposal_with_votes);
|
|
for i in 0..option_count {
|
|
let option = get_option_at(&proposal_with_votes, i);
|
|
println!("- Option ID: {}, Text: '{}', Votes: {}",
|
|
i, get_option_text(&option),
|
|
get_option_votes(&option));
|
|
}
|
|
|
|
println!("\nBallots Cast:");
|
|
let ballot_count = get_ballot_count(&proposal_with_votes);
|
|
for i in 0..ballot_count {
|
|
let ballot = get_ballot_at(&proposal_with_votes, i);
|
|
println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}",
|
|
i, get_ballot_user_id(&ballot),
|
|
get_ballot_option_id(&ballot),
|
|
get_ballot_shares(&ballot));
|
|
}
|
|
|
|
// Change proposal status
|
|
let active_proposal = change_proposal_status(
|
|
proposal_with_votes, "Active".to_string());
|
|
println!("\nChanged Proposal Status to: {}",
|
|
get_status(&active_proposal));
|
|
|
|
// Simulate closing the vote
|
|
let closed_proposal = change_vote_event_status(
|
|
active_proposal, "Closed".to_string());
|
|
println!("Changed Vote Event Status to: {}",
|
|
get_vote_status(&closed_proposal));
|
|
|
|
// Final proposal state
|
|
println!("\nFinal Proposal State:");
|
|
println!("Title: '{}'", get_title(&closed_proposal));
|
|
println!("Status: {}", get_status(&closed_proposal));
|
|
println!("Vote Status: {}", get_vote_status(&closed_proposal));
|
|
println!("Options:");
|
|
let option_count = get_option_count(&closed_proposal);
|
|
for i in 0..option_count {
|
|
let option = get_option_at(&closed_proposal, i);
|
|
println!(" - {}: {} (Votes: {})",
|
|
i, get_option_text(&option),
|
|
get_option_votes(&option));
|
|
}
|
|
println!("Total Ballots: {}", get_ballot_count(&closed_proposal));
|
|
|
|
// Get all proposals from the database
|
|
let all_proposals = get_all_proposals(db.clone());
|
|
println!("\nTotal Proposals in Database: {}", all_proposals.len());
|
|
for proposal in all_proposals {
|
|
println!("Proposal ID: {}, Title: '{}'",
|
|
get_id(&proposal),
|
|
get_title(&proposal));
|
|
}
|
|
|
|
// Delete a proposal
|
|
delete_proposal_by_id(db.clone(), 1);
|
|
println!("\nDeleted proposal with ID 1");
|
|
|
|
// Demonstrate the use of Rhai client functions for simple types
|
|
println!("\nUsing Rhai client functions for simple types:");
|
|
let create_result = create_proposal_wrapper_rhai_client(&engine, 2,
|
|
"rhai_user".to_string(),
|
|
"Rhai Proposal".to_string(),
|
|
"This proposal was created using a Rhai client function".to_string());
|
|
println!("{}", create_result);
|
|
|
|
let add_option_result = add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string());
|
|
println!("{}", add_option_result);
|
|
|
|
println!("\nGovernance Proposal Example Finished.");
|
|
|
|
Ok(())
|
|
}
|