diff --git a/.github/workflows/on_pull_request_build_contracts.yml b/.github/workflows/on_pull_request_build_contracts.yml new file mode 100644 index 000000000..0fc2e4f99 --- /dev/null +++ b/.github/workflows/on_pull_request_build_contracts.yml @@ -0,0 +1,14 @@ +name: On pull request, build contracts + +on: + pull_request: + +permissions: + contents: write + +jobs: + build: + uses: multiversx/mx-sc-actions/.github/workflows/reproducible-build.yml@v2.3.4 + with: + image_tag: v4.2.1 + package_whole_project_src: true diff --git a/dex/pair-mock/wasm/src/lib.rs b/dex/pair-mock/wasm/src/lib.rs index e3462d773..54b9b427e 100644 --- a/dex/pair-mock/wasm/src/lib.rs +++ b/dex/pair-mock/wasm/src/lib.rs @@ -1,4 +1,4 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. //////////////////////////////////////////////////// ////////////////// AUTO-GENERATED ////////////////// @@ -10,8 +10,7 @@ // Total number of exported functions: 4 #![no_std] -#![allow(internal_features)] -#![feature(lang_items)] +#![feature(alloc_error_handler, lang_items)] multiversx_sc_wasm_adapter::allocator!(); multiversx_sc_wasm_adapter::panic_handler!(); @@ -19,10 +18,9 @@ multiversx_sc_wasm_adapter::panic_handler!(); multiversx_sc_wasm_adapter::endpoints! { pair_mock ( - init => init - addInitialLiquidity => add_initial_liquidity - getTokensForGivenPositionWithSafePrice => get_tokens_for_given_position_with_safe_price + addInitialLiquidity + getTokensForGivenPositionWithSafePrice ) } -multiversx_sc_wasm_adapter::async_callback_empty! {} +multiversx_sc_wasm_adapter::empty_callback! {} diff --git a/energy-integration/governance-v2/src/configurable.rs b/energy-integration/governance-v2/src/configurable.rs index 12751ce7f..b8736c0ca 100644 --- a/energy-integration/governance-v2/src/configurable.rs +++ b/energy-integration/governance-v2/src/configurable.rs @@ -9,10 +9,9 @@ multiversx_sc::imports!(); /// - voting/downvoting a certain proposal /// - after a voting period, either putting the action in a queue (if it reached quorum), or canceling /// -/// Voting is done through energy. +/// Voting is done through tokens ownership. /// -/// The module provides the following configurable parameters: -/// - `minEnergyForPropose` - the minimum energy required for submitting a proposal +/// The module provides the following configurable parameters: /// - `quorum` - the minimum number of (`votes` minus `downvotes`) at the end of voting period /// - `maxActionsPerProposal` - Maximum number of actions (transfers and/or smart contract calls) that a proposal may have /// - `votingDelayInBlocks` - Number of blocks to wait after a block is proposed before being able to vote/downvote that proposal @@ -36,25 +35,17 @@ const MIN_VOTING_PERIOD: u64 = 14_400; // 24 Hours const MAX_VOTING_PERIOD: u64 = 201_600; // 2 Weeks const MIN_QUORUM: u64 = 1_000; // 10% const MAX_QUORUM: u64 = 6_000; // 60% -const MIN_MIN_FEE_FOR_PROPOSE: u64 = 2_000_000; +const MIN_MIN_FEE_FOR_PROPOSE: u64 = 1; const MAX_MIN_FEE_FOR_PROPOSE: u64 = 200_000_000_000; const DECIMALS_CONST: u64 = 1_000_000_000_000_000_000; pub const MAX_GAS_LIMIT_PER_BLOCK: u64 = 600_000_000; pub const FULL_PERCENTAGE: u64 = 10_000; #[multiversx_sc::module] -pub trait ConfigurablePropertiesModule: - energy_query::EnergyQueryModule + permissions_module::PermissionsModule -{ +pub trait ConfigurablePropertiesModule { // endpoints - these can only be called by the SC itself. // i.e. only by proposing and executing an action with the SC as dest and the respective func name - #[only_owner] - #[endpoint(changeMinEnergyForProposal)] - fn change_min_energy_for_propose(&self, new_value: BigUint) { - self.try_change_min_energy_for_propose(new_value); - } - #[only_owner] #[endpoint(changeMinFeeForProposal)] fn change_min_fee_for_propose(&self, new_value: BigUint) { @@ -63,16 +54,10 @@ pub trait ConfigurablePropertiesModule: #[only_owner] #[endpoint(changeQuorumPercentage)] - fn change_quorum_percentage(&self, new_value: u64) { + fn change_quorum_percentage(&self, new_value: BigUint) { self.try_change_quorum_percentage(new_value); } - #[only_owner] - #[endpoint(changeWithdrawPercentage)] - fn change_withdraw_percentage(&self, new_value: u64) { - self.try_change_withdraw_percentage_defeated(new_value); - } - #[only_owner] #[endpoint(changeVotingDelayInBlocks)] fn change_voting_delay_in_blocks(&self, new_value: u64) { @@ -85,10 +70,6 @@ pub trait ConfigurablePropertiesModule: self.try_change_voting_period_in_blocks(new_value); } - fn try_change_min_energy_for_propose(&self, new_value: BigUint) { - self.min_energy_for_propose().set(&new_value); - } - fn try_change_min_fee_for_propose(&self, new_value: BigUint) { let minimum_min_fee = BigUint::from(MIN_MIN_FEE_FOR_PROPOSE) * BigUint::from(DECIMALS_CONST); @@ -102,41 +83,40 @@ pub trait ConfigurablePropertiesModule: self.min_fee_for_propose().set(&new_value); } - fn try_change_quorum_percentage(&self, new_quorum_percentage: u64) { + fn try_change_quorum_percentage(&self, new_value: BigUint) { require!( - (MIN_QUORUM..MAX_QUORUM).contains(&new_quorum_percentage), + new_value > MIN_QUORUM && new_value < MAX_QUORUM, "Not valid value for Quorum!" ); - self.quorum_percentage().set(new_quorum_percentage); + self.quorum_percentage().set(&new_value); } - fn try_change_voting_delay_in_blocks(&self, new_voting_delay: u64) { + fn try_change_voting_delay_in_blocks(&self, new_value: u64) { require!( - (MIN_VOTING_DELAY..MAX_VOTING_DELAY).contains(&new_voting_delay), + new_value > MIN_VOTING_DELAY && new_value < MAX_VOTING_DELAY, "Not valid value for voting delay!" ); - self.voting_delay_in_blocks().set(new_voting_delay); + self.voting_delay_in_blocks().set(new_value); } - fn try_change_voting_period_in_blocks(&self, new_voting_period: u64) { + fn try_change_voting_period_in_blocks(&self, new_value: u64) { require!( - (MIN_VOTING_PERIOD..MAX_VOTING_PERIOD).contains(&new_voting_period), + new_value > MIN_VOTING_PERIOD && new_value < MAX_VOTING_PERIOD, "Not valid value for voting period!" ); - self.voting_period_in_blocks().set(new_voting_period); + self.voting_period_in_blocks().set(new_value); } - fn try_change_withdraw_percentage_defeated(&self, new_withdraw_percentage: u64) { + fn try_change_withdraw_percentage_defeated(&self, new_value: u64) { require!( - new_withdraw_percentage <= FULL_PERCENTAGE, + new_value > 0 && new_value < FULL_PERCENTAGE, "Not valid value for withdraw percentage if defeated!" ); - self.withdraw_percentage_defeated() - .set(new_withdraw_percentage); + self.withdraw_percentage_defeated().set(new_value); } fn try_change_fee_token_id(&self, fee_token_id: TokenIdentifier) { @@ -144,21 +124,13 @@ pub trait ConfigurablePropertiesModule: self.fee_token_id().set_if_empty(&fee_token_id); } - fn smoothing_function(&self, input: &BigUint) -> BigUint { - input.sqrt() - } - - #[view(getMinEnergyForPropose)] - #[storage_mapper("minEnergyForPropose")] - fn min_energy_for_propose(&self) -> SingleValueMapper; - #[view(getMinFeeForPropose)] #[storage_mapper("minFeeForPropose")] fn min_fee_for_propose(&self) -> SingleValueMapper; #[view(getQuorum)] #[storage_mapper("quorumPercentage")] - fn quorum_percentage(&self) -> SingleValueMapper; + fn quorum_percentage(&self) -> SingleValueMapper; #[view(getVotingDelayInBlocks)] #[storage_mapper("votingDelayInBlocks")] diff --git a/energy-integration/governance-v2/src/errors.rs b/energy-integration/governance-v2/src/errors.rs index f1100a5be..2a8f3bf1f 100644 --- a/energy-integration/governance-v2/src/errors.rs +++ b/energy-integration/governance-v2/src/errors.rs @@ -1,14 +1,14 @@ pub const WRONG_TOKEN_ID: &[u8] = b"Wrong payment token id for fee"; pub const NOT_ENOUGH_FEE: &[u8] = b"Minimum fee required not reached"; -pub const NOT_ENOUGH_ENERGY: &[u8] = b"Not enough energy for propose"; pub const TOO_MUCH_GAS: &[u8] = b"Actions require too much gas to be executed"; pub const PROPOSAL_NOT_ACTIVE: &[u8] = b"Proposal is not active"; pub const ERROR_NOT_AN_ESDT: &[u8] = b"Not a valid esdt id"; pub const ALREADY_VOTED_ERR_MSG: &[u8] = b"Already voted for this proposal"; +pub const INVALID_ROOT_HASH: &[u8] = b"Invalid root hash provided"; +pub const PROPOSAL_NO_ACTION: &[u8] = b"Proposal has no actions"; pub const EXEEDED_MAX_ACTIONS: &[u8] = b"Exceeded max actions per proposal"; pub const ONLY_PROPOSER_CANCEL: &[u8] = b"Only original proposer may cancel a pending proposal"; pub const ONLY_PROPOSER_WITHDRAW: &[u8] = b"Only original proposer may withdraw a pending proposal"; -pub const FEE_ALREADY_WITHDRAWN: &[u8] = b"Fee already withdrawn!"; pub const NO_PROPOSAL: &[u8] = b"Proposal does not exist"; pub const WITHDRAW_NOT_ALLOWED: &[u8] = b"You may not withdraw funds from this proposal!"; -pub const PROPOSAL_NOT_ALLOWED_FOR_SC: &[u8] = b"Smart Contracts are not allowed to propose!"; +pub const INVALID_MERKLE_PROOF: &[u8] = b"Invalid merkle proof provided"; diff --git a/energy-integration/governance-v2/src/lib.rs b/energy-integration/governance-v2/src/lib.rs index 0fcb53dca..f3800f915 100644 --- a/energy-integration/governance-v2/src/lib.rs +++ b/energy-integration/governance-v2/src/lib.rs @@ -11,13 +11,14 @@ pub mod views; use proposal::*; use proposal_storage::VoteType; -use weekly_rewards_splitting::events::Week; -use weekly_rewards_splitting::global_info::ProxyTrait as _; use crate::configurable::{FULL_PERCENTAGE, MAX_GAS_LIMIT_PER_BLOCK}; use crate::errors::*; use crate::proposal_storage::ProposalVotes; +const MAX_GAS_LIMIT_PER_BLOCK: u64 = 600_000_000; +const FULL_PERCENTAGE: u64 = 10_000; + /// An empty contract. To be used as a template when starting a new contract from scratch. #[multiversx_sc::contract] pub trait GovernanceV2: @@ -25,73 +26,74 @@ pub trait GovernanceV2: + events::EventsModule + proposal_storage::ProposalStorageModule + views::ViewsModule - + energy_query::EnergyQueryModule - + permissions_module::PermissionsModule { - /// - `min_energy_for_propose` - the minimum energy required for submitting a proposal - /// - `min_fee_for_propose` - the minimum fee required for submitting a proposal - /// - `quorum_percentage` - the minimum number of (`votes` minus `downvotes`) at the end of voting period - /// - `votingDelayInBlocks` - Number of blocks to wait after a block is proposed before being able to vote/downvote that proposal - /// - `votingPeriodInBlocks` - Number of blocks the voting period lasts (voting delay does not count towards this) - /// - `withdraw_percentage_defeated` - Percetange of the fee to be returned if proposal defetead - /// - `energy_factory_address` - /// - `fees_collector_address` - /// - `fee_token` - The token used to pay the fee + /// - `min_fee_for_propose` - the minimum fee required for submitting a proposal; + /// - `quorum` - the minimum number of (`votes` minus `downvotes`) at the end of voting period; + /// - `votingDelayInBlocks` - Number of blocks to wait after a block is proposed before being able to vote/downvote that proposal; + /// - `votingPeriodInBlocks` - Number of blocks the voting period lasts (voting delay does not count towards this); + /// - `withdraw_percentage_defeated` - The percentage used to return in case of DownVetoVote; + /// - `fee_token` - The token used to pay the fee for governance proposal; #[init] fn init( &self, - min_energy_for_propose: BigUint, min_fee_for_propose: BigUint, quorum_percentage: u64, voting_delay_in_blocks: u64, voting_period_in_blocks: u64, withdraw_percentage_defeated: u64, - energy_factory_address: ManagedAddress, - fees_collector_address: ManagedAddress, fee_token: TokenIdentifier, ) { - self.try_change_min_energy_for_propose(min_energy_for_propose); self.try_change_min_fee_for_propose(min_fee_for_propose); self.try_change_quorum_percentage(quorum_percentage); self.try_change_voting_delay_in_blocks(voting_delay_in_blocks); self.try_change_voting_period_in_blocks(voting_period_in_blocks); self.try_change_withdraw_percentage_defeated(withdraw_percentage_defeated); - self.set_energy_factory_address(energy_factory_address); - self.fees_collector_address().set(&fees_collector_address); self.try_change_fee_token_id(fee_token); } #[endpoint] - fn upgrade(&self) {} + fn upgrade( + &self, + proposal_id: ProposalId, + total_percentage: BigUint, + ) { + let mut proposal = self.proposals().get(proposal_id); + proposal.total_quorum = total_percentage; + self.proposals().set(proposal_id, &proposal); + } /// Propose a list of actions. /// A maximum of MAX_GOVERNANCE_PROPOSAL_ACTIONS can be proposed at a time. /// - /// The proposer's energy is NOT automatically used for voting. A separate vote is needed. + /// An action has the following format: + /// - gas limit for action execution + /// - destination address + /// - a fee payment for proposal (if smaller than min_fee_for_propose, state: WaitForFee) + /// - endpoint to be called on the destination + /// - a vector of arguments for the endpoint, in the form of ManagedVec + /// + /// The proposer's quorum is NOT automatically used for voting. A separate vote is needed. /// /// Returns the ID of the newly created proposal. + #[only_owner] #[payable("*")] #[endpoint] fn propose( &self, + root_hash: ManagedByteArray, + total_quorum: BigUint, description: ManagedBuffer, actions: MultiValueEncoded>, ) -> ProposalId { - let proposer = self.blockchain().get_caller(); - require!( - !self.blockchain().is_smart_contract(&proposer), - PROPOSAL_NOT_ALLOWED_FOR_SC - ); - + self.require_caller_not_self(); + require!(!root_hash.is_empty(), INVALID_ROOT_HASH); + require!(!actions.is_empty(), PROPOSAL_NO_ACTION); require!( actions.len() <= MAX_GOVERNANCE_PROPOSAL_ACTIONS, EXEEDED_MAX_ACTIONS ); - let user_energy = self.get_energy_amount(&proposer); - let min_energy_for_propose = self.min_energy_for_propose().get(); - require!(user_energy >= min_energy_for_propose, NOT_ENOUGH_ENERGY); - + let proposer = self.blockchain().get_caller(); let user_fee = self.call_value().single_esdt(); require!( self.fee_token_id().get() == user_fee.token_identifier, @@ -130,15 +132,15 @@ pub trait GovernanceV2: proposal_id: self.proposals().len() + 1, proposer: proposer.clone(), description, + root_hash, actions: gov_actions, fee_payment: user_fee, minimum_quorum, voting_delay_in_blocks, voting_period_in_blocks, withdraw_percentage_defeated, - total_quorum: BigUint::zero(), + total_quorum, proposal_start_block: current_block, - fee_withdrawn: false, }; let proposal_id = self.proposals().push(&proposal); @@ -149,9 +151,16 @@ pub trait GovernanceV2: proposal_id } - /// Vote on a proposal. The voting power depends on the user's energy. + /// Vote on a proposal. The voting power depends on the user's quorum (number of tokens). #[endpoint] - fn vote(&self, proposal_id: ProposalId, vote: VoteType) { + fn vote( + &self, + proposal_id: ProposalId, + vote: VoteType, + user_quorum: BigUint, + proof: ArrayVec, PROOF_LENGTH>, + ) { + self.require_caller_not_self(); self.require_valid_proposal_id(proposal_id); require!( self.get_proposal_status(proposal_id) == GovernanceProposalStatus::Active, @@ -162,57 +171,48 @@ pub trait GovernanceV2: let new_user = self.user_voted_proposals(&voter).insert(proposal_id); require!(new_user, ALREADY_VOTED_ERR_MSG); - let current_quorum = self.proposal_votes(proposal_id).get().quorum; - - // First voter -> update total_energy - if current_quorum == BigUint::zero() { - let fees_collector_addr = self.fees_collector_address().get(); - let last_global_update_week: Week = self - .fees_collector_proxy(fees_collector_addr.clone()) - .last_global_update_week() - .execute_on_dest_context(); + let voting_power = &user_quorum; - let total_quorum: BigUint = self - .fees_collector_proxy(fees_collector_addr) - .total_energy_for_week(last_global_update_week) - .execute_on_dest_context(); - - let mut proposal = self.proposals().get(proposal_id); - proposal.total_quorum = total_quorum; - self.proposals().set(proposal_id, &proposal); + match self.get_root_hash(proposal_id) { + OptionalValue::None => { + sc_panic!(NO_PROPOSAL); + } + OptionalValue::Some(root_hash) => { + require!( + self.verify_merkle_proof(user_quorum.clone(), proof, root_hash), + INVALID_MERKLE_PROOF + ); + } } - let user_quorum = self.get_energy_amount_non_zero(&voter); - let voting_power = self.smoothing_function(&user_quorum); - match vote { VoteType::UpVote => { self.proposal_votes(proposal_id).update(|proposal_votes| { - proposal_votes.up_votes += &voting_power.clone(); + proposal_votes.up_votes += voting_power.clone(); proposal_votes.quorum += &user_quorum.clone(); }); - self.up_vote_cast_event(&voter, proposal_id, &voting_power, &user_quorum); + self.up_vote_cast_event(&voter, proposal_id, voting_power, &user_quorum); } VoteType::DownVote => { self.proposal_votes(proposal_id).update(|proposal_votes| { - proposal_votes.down_votes += &voting_power.clone(); + proposal_votes.down_votes += voting_power.clone(); proposal_votes.quorum += &user_quorum.clone(); }); - self.down_vote_cast_event(&voter, proposal_id, &voting_power, &user_quorum); + self.down_vote_cast_event(&voter, proposal_id, voting_power, &user_quorum); } VoteType::DownVetoVote => { self.proposal_votes(proposal_id).update(|proposal_votes| { - proposal_votes.down_veto_votes += &voting_power.clone(); + proposal_votes.down_veto_votes += voting_power.clone(); proposal_votes.quorum += &user_quorum.clone(); }); - self.down_veto_vote_cast_event(&voter, proposal_id, &voting_power, &user_quorum); + self.down_veto_vote_cast_event(&voter, proposal_id, voting_power, &user_quorum); } VoteType::AbstainVote => { self.proposal_votes(proposal_id).update(|proposal_votes| { - proposal_votes.abstain_votes += &voting_power.clone(); + proposal_votes.abstain_votes += voting_power.clone(); proposal_votes.quorum += &user_quorum.clone(); }); - self.abstain_vote_cast_event(&voter, proposal_id, &voting_power, &user_quorum); + self.abstain_vote_cast_event(&voter, proposal_id, voting_power, &user_quorum); } } } @@ -229,7 +229,7 @@ pub trait GovernanceV2: let caller = self.blockchain().get_caller(); require!(caller == proposal.proposer, ONLY_PROPOSER_CANCEL); - self.refund_proposal_fee(&proposal, &proposal.fee_payment.amount); + self.refund_proposal_fee(proposal_id, &proposal.fee_payment.amount); self.clear_proposal(proposal_id); self.proposal_canceled_event(proposal_id); } @@ -250,36 +250,30 @@ pub trait GovernanceV2: sc_panic!(NO_PROPOSAL); } GovernanceProposalStatus::Succeeded | GovernanceProposalStatus::Defeated => { - let mut proposal = self.proposals().get(proposal_id); + let proposal = self.proposals().get(proposal_id); require!(caller == proposal.proposer, ONLY_PROPOSER_WITHDRAW); - require!(!proposal.fee_withdrawn, FEE_ALREADY_WITHDRAWN); - self.refund_proposal_fee(&proposal, &proposal.fee_payment.amount); - proposal.fee_withdrawn = true; - self.proposals().set(proposal_id, &proposal); + self.refund_proposal_fee(proposal_id, &proposal.fee_payment.amount); } GovernanceProposalStatus::DefeatedWithVeto => { - let mut proposal = self.proposals().get(proposal_id); - - require!(!proposal.fee_withdrawn, FEE_ALREADY_WITHDRAWN); - + let proposal = self.proposals().get(proposal_id); let refund_percentage = BigUint::from(proposal.withdraw_percentage_defeated); let refund_amount = refund_percentage * proposal.fee_payment.amount.clone() / FULL_PERCENTAGE; - // Burn remaining fees - let remaining_fee = proposal.fee_payment.amount.clone() - refund_amount.clone(); - self.send().esdt_non_zero_local_burn( - &proposal.fee_payment.token_identifier, - proposal.fee_payment.token_nonce, - &remaining_fee, - ); + require!(caller == proposal.proposer, ONLY_PROPOSER_WITHDRAW); + + self.refund_proposal_fee(proposal_id, &refund_amount); + let remaining_fee = proposal.fee_payment.amount - refund_amount; - // Mark this proposal that fee is withdrawn - self.refund_proposal_fee(&proposal, &refund_amount); - proposal.fee_withdrawn = true; - self.proposals().set(proposal_id, &proposal); + self.proposal_remaining_fees().update(|fees| { + fees.push(EsdtTokenPayment::new( + proposal.fee_payment.token_identifier, + proposal.fee_payment.token_nonce, + remaining_fee, + )); + }); } _ => { sc_panic!(WITHDRAW_NOT_ALLOWED); @@ -300,18 +294,51 @@ pub trait GovernanceV2: total } - fn refund_proposal_fee( - &self, - proposal: &GovernanceProposal, - refund_amount: &BigUint, - ) { - self.send().direct_non_zero_esdt_payment( + fn refund_proposal_fee(&self, proposal_id: ProposalId, refund_amount: &BigUint) { + let proposal: GovernanceProposal<::Api> = + self.proposals().get(proposal_id); + + self.send().direct_esdt( &proposal.proposer, - &EsdtTokenPayment::new( - proposal.fee_payment.token_identifier.clone(), - proposal.fee_payment.token_nonce, - refund_amount.clone(), - ), + &proposal.fee_payment.token_identifier, + proposal.fee_payment.token_nonce, + refund_amount, ); } + + fn verify_merkle_proof( + &self, + power: BigUint, + proof: ArrayVec, PROOF_LENGTH>, + root_hash: ManagedByteArray, + ) -> bool { + let caller = self.blockchain().get_caller(); + let mut leaf_bytes = caller.as_managed_buffer().clone(); + + let p = power.to_bytes_be_buffer(); + leaf_bytes.append(&p); + + let mut hash = self.crypto().sha256(&leaf_bytes); + for proof_item in proof { + if BigUint::from(hash.as_managed_buffer()) + < BigUint::from(proof_item.as_managed_buffer()) + { + let mut tst = hash.as_managed_buffer().clone(); + tst.append(proof_item.as_managed_buffer()); + + hash = self.crypto().sha256(tst); + } else { + let mut tst = proof_item.as_managed_buffer().clone(); + tst.append(hash.as_managed_buffer()); + + hash = self.crypto().sha256(tst); + } + } + + hash == root_hash + } + + #[storage_mapper("proposalRemainingFees")] + fn proposal_remaining_fees(&self) + -> SingleValueMapper>>; } diff --git a/energy-integration/governance-v2/src/proposal.rs b/energy-integration/governance-v2/src/proposal.rs index e26591817..a1e916e69 100644 --- a/energy-integration/governance-v2/src/proposal.rs +++ b/energy-integration/governance-v2/src/proposal.rs @@ -3,6 +3,8 @@ use multiversx_sc::codec::{DecodeDefault, EncodeDefault}; multiversx_sc::imports!(); multiversx_sc::derive_imports!(); +pub const HASH_LENGTH: usize = 32; +pub const PROOF_LENGTH: usize = 18; pub const MAX_GOVERNANCE_PROPOSAL_ACTIONS: usize = 4; pub type ProposalId = usize; @@ -60,6 +62,7 @@ pub struct GovernanceProposal { pub proposer: ManagedAddress, pub actions: ArrayVec, MAX_GOVERNANCE_PROPOSAL_ACTIONS>, pub description: ManagedBuffer, + pub root_hash: ManagedByteArray, pub fee_payment: EsdtTokenPayment, pub minimum_quorum: u64, pub voting_delay_in_blocks: u64, diff --git a/energy-integration/governance-v2/src/views.rs b/energy-integration/governance-v2/src/views.rs index ec748edd9..700d97ffc 100644 --- a/energy-integration/governance-v2/src/views.rs +++ b/energy-integration/governance-v2/src/views.rs @@ -1,7 +1,7 @@ multiversx_sc::imports!(); use crate::{ - proposal::{GovernanceProposalStatus, ProposalId}, + proposal::{GovernanceProposalStatus, ProposalId, HASH_LENGTH}, FULL_PERCENTAGE, }; @@ -9,8 +9,7 @@ use crate::{ pub trait ViewsModule: crate::proposal_storage::ProposalStorageModule + crate::configurable::ConfigurablePropertiesModule - + permissions_module::PermissionsModule - + energy_query::EnergyQueryModule + + crate::caller_check::CallerCheckModule { #[view(getProposalStatus)] fn get_proposal_status(&self, proposal_id: ProposalId) -> GovernanceProposalStatus { @@ -44,11 +43,26 @@ pub trait ViewsModule: } } - // private + #[view(getProposalRootHash)] + fn get_root_hash( + &self, + proposal_id: ProposalId, + ) -> OptionalValue> { + if !self.proposal_exists(proposal_id) { + return OptionalValue::None; + } + + OptionalValue::Some(self.proposals().get(proposal_id).root_hash) + } fn vote_reached(&self, proposal_id: ProposalId) -> bool { let proposal_votes = self.proposal_votes(proposal_id).get(); let total_votes = proposal_votes.get_total_votes(); + + if total_votes == 0u64 { + return false; + } + let total_up_votes = proposal_votes.up_votes; let total_down_veto_votes = proposal_votes.down_veto_votes; let third_total_votes = &total_votes / 3u64; @@ -64,6 +78,11 @@ pub trait ViewsModule: fn vote_down_with_veto(&self, proposal_id: ProposalId) -> bool { let proposal_votes = self.proposal_votes(proposal_id).get(); let total_votes = proposal_votes.get_total_votes(); + + if total_votes == 0u64 { + return false; + } + let total_down_veto_votes = proposal_votes.down_veto_votes; let third_total_votes = &total_votes / 3u64; @@ -72,11 +91,18 @@ pub trait ViewsModule: fn quorum_reached(&self, proposal_id: ProposalId) -> bool { let proposal = self.proposals().get(proposal_id); - let total_quorum_for_proposal = proposal.total_quorum; - let required_minimum_percentage = BigUint::from(proposal.minimum_quorum); + let total_quorum = proposal.total_quorum; + + if total_quorum == 0u64 { + return false; + } + + let required_minimum_percentage = proposal.minimum_quorum; + let current_quorum = self.proposal_votes(proposal_id).get().quorum; + let current_quorum_percentage = current_quorum * FULL_PERCENTAGE / total_quorum; - current_quorum * FULL_PERCENTAGE >= required_minimum_percentage * total_quorum_for_proposal + current_quorum_percentage >= required_minimum_percentage } fn require_valid_proposal_id(&self, proposal_id: ProposalId) { @@ -93,17 +119,4 @@ pub trait ViewsModule: fn proposal_exists(&self, proposal_id: ProposalId) -> bool { self.is_valid_proposal_id(proposal_id) && !self.proposals().item_is_empty(proposal_id) } - - #[only_owner] - #[endpoint(changeFeesCollectorAddress)] - fn change_fees_collector_address(&self, new_value: ManagedAddress) { - self.fees_collector_address().set(new_value); - } - - #[proxy] - fn fees_collector_proxy(&self, sc_address: ManagedAddress) -> fees_collector::Proxy; - - #[view(getFeesCollectorAddress)] - #[storage_mapper("feesCollectorAddress")] - fn fees_collector_address(&self) -> SingleValueMapper; } diff --git a/energy-integration/governance-v2/wasm/src/lib.rs b/energy-integration/governance-v2/wasm/src/lib.rs index bb977bc97..98ea5cb27 100644 --- a/energy-integration/governance-v2/wasm/src/lib.rs +++ b/energy-integration/governance-v2/wasm/src/lib.rs @@ -1,17 +1,16 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. //////////////////////////////////////////////////// ////////////////// AUTO-GENERATED ////////////////// //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 30 +// Endpoints: 19 // Async Callback (empty): 1 -// Total number of exported functions: 32 +// Total number of exported functions: 21 #![no_std] -#![allow(internal_features)] -#![feature(lang_items)] +#![feature(alloc_error_handler, lang_items)] multiversx_sc_wasm_adapter::allocator!(); multiversx_sc_wasm_adapter::panic_handler!(); @@ -19,38 +18,26 @@ multiversx_sc_wasm_adapter::panic_handler!(); multiversx_sc_wasm_adapter::endpoints! { governance_v2 ( - init => init - upgrade => upgrade - propose => propose - vote => vote - cancel => cancel - withdrawDeposit => withdraw_deposit - changeMinEnergyForProposal => change_min_energy_for_propose - changeMinFeeForProposal => change_min_fee_for_propose - changeQuorumPercentage => change_quorum_percentage - changeWithdrawPercentage => change_withdraw_percentage - changeVotingDelayInBlocks => change_voting_delay_in_blocks - changeVotingPeriodInBlocks => change_voting_period_in_blocks - getMinEnergyForPropose => min_energy_for_propose - getMinFeeForPropose => min_fee_for_propose - getQuorum => quorum_percentage - getVotingDelayInBlocks => voting_delay_in_blocks - getVotingPeriodInBlocks => voting_period_in_blocks - getFeeTokenId => fee_token_id - getWithdrawPercentageDefeated => withdraw_percentage_defeated - getProposals => proposals - getUserVotedProposals => user_voted_proposals - getProposalVotes => proposal_votes - getProposalStatus => get_proposal_status - changeFeesCollectorAddress => change_fees_collector_address - getFeesCollectorAddress => fees_collector_address - setEnergyFactoryAddress => set_energy_factory_address - getEnergyFactoryAddress => energy_factory_address - addAdmin => add_admin_endpoint - removeAdmin => remove_admin_endpoint - updateOwnerOrAdmin => update_owner_or_admin_endpoint - getPermissions => permissions + propose + vote + cancel + withdrawDeposit + changeMinFeeForProposal + changeQuorumPercentage + changeVotingDelayInBlocks + changeVotingPeriodInBlocks + getMinFeeForPropose + getQuorum + getVotingDelayInBlocks + getVotingPeriodInBlocks + getFeeTokenId + getWithdrawPercentageDefeated + getProposals + getUserVotedProposals + getProposalVotes + getProposalStatus + getProposalRootHash ) } -multiversx_sc_wasm_adapter::async_callback_empty! {} +multiversx_sc_wasm_adapter::empty_callback! {}