use crate::configuration::Configuration;
use crate::rules::validation::voting::{
AfterFormalVoting, FinishedVotingCanBeCancelled, VoteInTime, VotingNotCompleted,
};
use crate::rules::RulesBuilder;
use crate::voting::ballot::Choice;
use crate::voting::types::VotingId;
use odra::types::{Address, Balance, BlockTime};
use odra::OdraType;
#[derive(OdraType, Debug)]
pub struct VotingStateMachine {
voting_id: VotingId,
state: VotingState,
voting_type: VotingType,
informal_stats: Stats,
formal_stats: Stats,
created_at: u64,
creator: Address,
}
impl VotingStateMachine {
pub fn new(voting_id: VotingId, created_at: u64, creator: Address) -> Self {
VotingStateMachine {
voting_id,
state: VotingState::Created,
voting_type: VotingType::Informal,
informal_stats: Default::default(),
formal_stats: Default::default(),
created_at,
creator,
}
}
pub fn complete_informal_voting(&mut self, configuration: &Configuration) -> bool {
let mut result_close = false;
if self.is_result_close(configuration) {
result_close = true;
}
self.state = VotingState::Formal;
self.voting_type = VotingType::Formal;
result_close
}
pub fn finish(&mut self) {
self.state = VotingState::Finished;
}
pub fn cancel(&mut self) {
self.state = VotingState::Canceled;
}
pub fn voting_type(&self) -> VotingType {
self.voting_type
}
pub fn is_informal_without_stake(&self, configuration: &Configuration) -> bool {
!configuration.informal_stake_reputation() && self.voting_type() == VotingType::Informal
}
pub fn is_in_time(&self, block_time: u64, configuration: &Configuration) -> bool {
match self.voting_type() {
VotingType::Informal => {
let start_time = self.informal_voting_start_time(configuration);
let voting_time = configuration.informal_voting_time();
start_time + voting_time <= block_time
}
VotingType::Formal => self.formal_voting_end_time(configuration) <= block_time,
}
}
pub fn informal_voting_end_time(&self, configuration: &Configuration) -> BlockTime {
self.informal_voting_start_time(configuration) + configuration.informal_voting_time()
}
pub fn time_between_votings_end_time(&self, configuration: &Configuration) -> BlockTime {
self.informal_voting_end_time(configuration)
+ configuration.time_between_informal_and_formal_voting()
}
pub fn formal_voting_end_time(&self, configuration: &Configuration) -> BlockTime {
self.time_between_votings_end_time(configuration) + configuration.formal_voting_time()
}
pub fn is_in_favor(&self) -> bool {
match self.voting_type() {
VotingType::Informal => {
self.informal_stats.stake_in_favor >= self.informal_stats.stake_against
}
VotingType::Formal => {
self.formal_stats.stake_in_favor >= self.formal_stats.stake_against
}
}
}
pub fn get_winning_stake(&self) -> Balance {
match (self.voting_type(), self.is_in_favor()) {
(VotingType::Informal, true) => self.informal_stats.stake_in_favor,
(VotingType::Informal, false) => self.informal_stats.stake_against,
(VotingType::Formal, true) => self.formal_stats.stake_in_favor,
(VotingType::Formal, false) => self.formal_stats.stake_against,
}
}
pub fn get_result(&self, voters_number: u32, configuration: &Configuration) -> VotingResult {
if self.get_quorum(configuration) > voters_number {
VotingResult::QuorumNotReached
} else if self.is_in_favor() {
VotingResult::InFavor
} else {
VotingResult::Against
}
}
pub fn add_stake(&mut self, stake: Balance, choice: Choice) {
match (self.voting_type(), choice) {
(VotingType::Informal, Choice::InFavor) => self.informal_stats.stake_in_favor += stake,
(VotingType::Informal, Choice::Against) => self.informal_stats.stake_against += stake,
(VotingType::Formal, Choice::InFavor) => self.formal_stats.stake_in_favor += stake,
(VotingType::Formal, Choice::Against) => self.formal_stats.stake_against += stake,
}
}
pub fn add_unbound_stake(&mut self, stake: Balance, choice: Choice) {
match (self.voting_type(), choice) {
(VotingType::Informal, Choice::InFavor) => {
self.informal_stats.unbound_stake_in_favor += stake
}
(VotingType::Informal, Choice::Against) => {
self.informal_stats.unbound_stake_against += stake
}
(VotingType::Formal, Choice::InFavor) => {
self.formal_stats.unbound_stake_in_favor += stake
}
(VotingType::Formal, Choice::Against) => {
self.formal_stats.unbound_stake_against += stake
}
}
}
pub fn remove_stake(&mut self, stake: Balance, choice: Choice) {
match (self.voting_type(), choice) {
(VotingType::Informal, Choice::InFavor) => self.informal_stats.stake_in_favor -= stake,
(VotingType::Informal, Choice::Against) => self.informal_stats.stake_against -= stake,
(VotingType::Formal, Choice::InFavor) => self.formal_stats.stake_in_favor -= stake,
(VotingType::Formal, Choice::Against) => self.formal_stats.stake_against -= stake,
}
}
pub fn remove_unbound_stake(&mut self, stake: Balance, choice: Choice) {
match (self.voting_type(), choice) {
(VotingType::Informal, Choice::InFavor) => {
self.informal_stats.unbound_stake_in_favor -= stake
}
(VotingType::Informal, Choice::Against) => {
self.informal_stats.unbound_stake_against -= stake
}
(VotingType::Formal, Choice::InFavor) => {
self.formal_stats.unbound_stake_in_favor -= stake
}
(VotingType::Formal, Choice::Against) => {
self.formal_stats.unbound_stake_against -= stake
}
}
}
pub fn bind_stake(&mut self, stake: Balance, choice: Choice) {
self.remove_unbound_stake(stake, choice);
self.add_stake(stake, choice);
}
pub fn total_stake(&self) -> Balance {
self.total_bound_stake() + self.total_unbound_stake()
}
pub fn total_bound_stake(&self) -> Balance {
match self.voting_type() {
VotingType::Informal => {
self.informal_stats.stake_in_favor + self.informal_stats.stake_against
}
VotingType::Formal => {
self.formal_stats.stake_in_favor + self.formal_stats.stake_against
}
}
}
pub fn total_unbound_stake(&self) -> Balance {
match self.voting_type() {
VotingType::Informal => {
self.informal_stats.unbound_stake_in_favor
+ self.informal_stats.unbound_stake_against
}
VotingType::Formal => {
self.formal_stats.unbound_stake_in_favor + self.formal_stats.unbound_stake_against
}
}
}
pub fn voting_id(&self) -> VotingId {
self.voting_id
}
pub fn stake_in_favor(&self) -> Balance {
match self.voting_type() {
VotingType::Informal => self.informal_stats.stake_in_favor,
VotingType::Formal => self.formal_stats.stake_in_favor,
}
}
pub fn stake_against(&self) -> Balance {
match self.voting_type() {
VotingType::Informal => self.informal_stats.stake_against,
VotingType::Formal => self.formal_stats.stake_against,
}
}
pub fn creator(&self) -> &Address {
&self.creator
}
pub fn state(&self) -> &VotingState {
&self.state
}
pub fn completed(&self) -> bool {
self.state() == &VotingState::Finished || self.state() == &VotingState::Canceled
}
pub fn state_in_time(
&self,
block_time: BlockTime,
configuration: &Configuration,
) -> VotingState {
let informal_voting_start = self.informal_voting_start_time(configuration);
let informal_voting_end = self.informal_voting_end_time(configuration);
let between_voting_end = self.time_between_votings_end_time(configuration);
let voting_end = self.formal_voting_end_time(configuration);
if block_time < informal_voting_start {
VotingState::Created
} else if block_time >= informal_voting_start && block_time <= informal_voting_end {
VotingState::Informal
} else if block_time > informal_voting_end && block_time <= between_voting_end {
VotingState::BetweenVotings
} else if block_time > between_voting_end && block_time <= voting_end {
VotingState::Formal
} else {
VotingState::Finished
}
}
pub fn informal_stats(&self) -> &Stats {
&self.informal_stats
}
pub fn formal_stats(&self) -> &Stats {
&self.formal_stats
}
fn informal_voting_start_time(&self, configuration: &Configuration) -> u64 {
self.created_at() + configuration.voting_delay()
}
pub fn created_at(&self) -> u64 {
self.created_at
}
fn is_result_close(&self, configuration: &Configuration) -> bool {
let stake_in_favor = self.stake_in_favor() + self.unbound_stake_in_favor();
let stake_against = self.stake_against() + self.unbound_stake_against();
let stake_diff = stake_in_favor.abs_diff(stake_against);
let stake_diff_percent = stake_diff.saturating_mul(Balance::from(100)) / self.total_stake();
stake_diff_percent <= configuration.voting_clearness_delta()
}
fn get_quorum(&self, configuration: &Configuration) -> u32 {
match self.voting_type() {
VotingType::Informal => configuration.informal_voting_quorum(),
VotingType::Formal => configuration.formal_voting_quorum(),
}
}
fn unbound_stake_in_favor(&self) -> Balance {
match self.voting_type() {
VotingType::Informal => self.informal_stats.unbound_stake_in_favor,
VotingType::Formal => self.formal_stats.unbound_stake_in_favor,
}
}
fn unbound_stake_against(&self) -> Balance {
match self.voting_type() {
VotingType::Informal => self.informal_stats.unbound_stake_against,
VotingType::Formal => self.formal_stats.unbound_stake_against,
}
}
pub fn guard_vote(&self, block_time: BlockTime, configuration: &Configuration) {
RulesBuilder::new()
.add_voting_validation(VoteInTime::create(block_time))
.build()
.validate(self, configuration);
}
pub fn guard_finish_formal_voting(&self, block_time: BlockTime, configuration: &Configuration) {
RulesBuilder::new()
.add_voting_validation(AfterFormalVoting::create(block_time))
.add_voting_validation(VotingNotCompleted::create())
.build()
.validate(self, configuration);
}
pub fn guard_cancel_finished_voting(
&self,
block_time: BlockTime,
configuration: &Configuration,
) {
RulesBuilder::new()
.add_voting_validation(VotingNotCompleted::create())
.add_voting_validation(FinishedVotingCanBeCancelled::create(block_time))
.build()
.validate(self, configuration);
}
}
#[derive(OdraType, Default, Debug)]
pub struct Stats {
pub stake_in_favor: Balance,
pub stake_against: Balance,
pub unbound_stake_in_favor: Balance,
pub unbound_stake_against: Balance,
pub votes_in_favor: u32,
pub votes_against: u32,
}
#[derive(OdraType, PartialEq, Eq, Debug)]
pub enum VotingState {
Created,
Informal,
BetweenVotings,
Formal,
Finished,
Canceled,
}
#[derive(OdraType, Copy, Hash, PartialEq, Eq, Debug)]
pub enum VotingType {
Informal,
Formal,
}
#[derive(OdraType)]
pub enum VotingStateInTime {
BeforeInformal,
Informal,
BetweenVotings,
Formal,
AfterFormal,
}
#[allow(dead_code)]
#[derive(OdraType)]
pub struct VotingSummary {
result: VotingResult,
ty: VotingType,
voting_id: VotingId,
}
impl VotingSummary {
pub fn new(result: VotingResult, ty: VotingType, voting_id: VotingId) -> Self {
Self {
result,
ty,
voting_id,
}
}
pub fn is_voting_process_finished(&self) -> bool {
match self.ty {
VotingType::Informal => self.is_rejected(),
VotingType::Formal => true,
}
}
pub fn is_formal(&self) -> bool {
self.voting_type() == VotingType::Formal
}
pub fn result(&self) -> VotingResult {
self.result.clone()
}
pub fn voting_type(&self) -> VotingType {
self.ty
}
fn is_rejected(&self) -> bool {
[VotingResult::Against, VotingResult::QuorumNotReached].contains(&self.result)
}
}
#[derive(OdraType, PartialEq, Eq, Debug)]
pub enum VotingResult {
InFavor,
Against,
QuorumNotReached,
Canceled,
}