diff --git a/Cargo.lock b/Cargo.lock index 0f89d27..6592962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,9 +325,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecdsa" -version = "0.16.9" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" dependencies = [ "der", "digest", @@ -829,9 +829,9 @@ checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "sec1" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ "base16ct", "der", @@ -849,9 +849,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "sep-41-token" -version = "0.3.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8832c57dd6d2ad1d31f9ac8434bd84677ab4f7ca0e2369fd57df49f42f2567ff" +checksum = "a0512f9829db34a9a9076e95c8137bbf88276915ec568d91e37350046fc3168c" dependencies = [ "soroban-sdk", ] @@ -939,9 +939,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.2.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest", "rand_core", @@ -1037,16 +1037,16 @@ dependencies = [ [[package]] name = "soroban-fixed-point-math" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230e5902daf9de6e7591aa7864dcf763ff96914a4460a0294a5dfd62b3181e0f" +checksum = "7db9baa088cdf971a359b6a392ec3601290d8f3a6d6312e375e0310a4c1a3b7d" dependencies = [ "soroban-sdk", ] [[package]] name = "soroban-governor" -version = "1.0.0" +version = "1.1.0" dependencies = [ "soroban-sdk", ] @@ -1135,7 +1135,7 @@ dependencies = [ [[package]] name = "soroban-votes" -version = "1.0.0" +version = "1.1.0" dependencies = [ "sep-41-token", "soroban-fixed-point-math", diff --git a/Cargo.toml b/Cargo.toml index b5232e0..bdc43f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ lto = true version = "20.5.0" [workspace.dependencies.sep-41-token] -version = "0.3.0" +version = "1.0.0" [workspace.dependencies.soroban-fixed-point-math] version = "1.0.0" diff --git a/contracts/governor/Cargo.toml b/contracts/governor/Cargo.toml index 6ad5d9c..5986724 100644 --- a/contracts/governor/Cargo.toml +++ b/contracts/governor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soroban-governor" -version = "1.0.0" +version = "1.1.0" authors = ["Script3 Ltd. "] license = "MIT" edition = "2021" diff --git a/contracts/governor/src/constants.rs b/contracts/governor/src/constants.rs index 15f7673..d835a03 100644 --- a/contracts/governor/src/constants.rs +++ b/contracts/governor/src/constants.rs @@ -1,4 +1,19 @@ -pub(crate) const ONE_DAY_LEDGERS: u32 = 17280; // assumes 5s a ledger -pub(crate) const MAX_PROPOSAL_LIFETIME: u32 = 31 * ONE_DAY_LEDGERS; // 31 days -pub(crate) const MAX_VOTE_PERIOD: u32 = 7 * ONE_DAY_LEDGERS; // 7 days -pub(crate) const BPS_SCALAR: i128 = 10_000; +/// One day assuming 5s a ledger +pub(crate) const ONE_DAY_LEDGERS: u32 = 17280; +/// One hour assuming 5s a ledger +pub(crate) const ONE_HOUR_LEDGERS: u32 = 720; +/// 1 in basis points +pub(crate) const BPS_SCALAR: u32 = 10_000; + +/// The maximum number of ledgers a proposal can exist for (31 days) +pub(crate) const MAX_PROPOSAL_LIFETIME: u32 = 31 * ONE_DAY_LEDGERS; +/// The maximum number of ledgers a proposal can be voted on for (7 days) +pub(crate) const MAX_VOTE_PERIOD: u32 = 7 * ONE_DAY_LEDGERS; +/// The minimum number of ledgers a proposal can be voted on for +pub(crate) const MIN_VOTE_PERIOD: u32 = ONE_HOUR_LEDGERS; +/// The maximum number of ledgers a proposal has between state changes before expiration +pub(crate) const MAX_GRACE_PERIOD: u32 = 7 * ONE_DAY_LEDGERS; +/// The minimum number of ledgers a proposal has between state changes before expiration +pub(crate) const MIN_GRACE_PERIOD: u32 = ONE_DAY_LEDGERS; +/// The minimum number of tokens required to create a proposal +pub(crate) const MIN_VOTE_THRESHOLD: i128 = 1; diff --git a/contracts/governor/src/contract.rs b/contracts/governor/src/contract.rs index 5d3eb3f..5a46aec 100644 --- a/contracts/governor/src/contract.rs +++ b/contracts/governor/src/contract.rs @@ -20,12 +20,13 @@ pub struct GovernorContract; #[contractimpl] impl Governor for GovernorContract { - fn initialize(e: Env, votes: Address, settings: GovernorSettings) { + fn initialize(e: Env, votes: Address, council: Address, settings: GovernorSettings) { if storage::get_is_init(&e) { panic_with_error!(&e, GovernorError::AlreadyInitializedError); } require_valid_settings(&e, &settings); storage::set_settings(&e, &settings); + storage::set_council_address(&e, &council); storage::set_voter_token_address(&e, &votes); storage::set_is_init(&e); storage::extend_instance(&e); @@ -35,6 +36,14 @@ impl Governor for GovernorContract { storage::get_settings(&e) } + fn council(e: Env) -> Address { + storage::get_council_address(&e) + } + + fn vote_token(e: Env) -> Address { + storage::get_voter_token_address(&e) + } + fn propose( e: Env, creator: Address, @@ -49,15 +58,16 @@ impl Governor for GovernorContract { panic_with_error!(&e, GovernorError::ProposalAlreadyOpenError); } - let settings = storage::get_settings(&e); match action { ProposalAction::Upgrade(_) => { - if creator != settings.council { + let council = storage::get_council_address(&e); + if creator != council { panic_with_error!(&e, GovernorError::UnauthorizedError); } } _ => {} }; + let settings = storage::get_settings(&e); let votes_client = VotesClient::new(&e, &storage::get_voter_token_address(&e)); let creater_votes = votes_client.get_votes(&creator); if creater_votes < settings.proposal_threshold { @@ -136,6 +146,7 @@ impl Governor for GovernorContract { if e.ledger().sequence() > proposal_data.vote_end + settings.grace_period { // proposal took too long to be closed. Mark expired and close. proposal_data.status = ProposalStatus::Expired; + GovernorEvents::proposal_expired(&e, proposal_id); } else { // proposal closed in time. Check if it passed or failed. let votes_client = VotesClient::new(&e, &storage::get_voter_token_address(&e)); @@ -204,11 +215,22 @@ impl Governor for GovernorContract { let mut proposal_data = storage::get_proposal_data(&e, proposal_id) .unwrap_or_else(|| panic_with_error!(&e, GovernorError::NonExistentProposalError)); + // require from to be the creator or the council if from != proposal_data.creator { - let settings = storage::get_settings(&e); - if from != settings.council { + let council = storage::get_council_address(&e); + if from != council { panic_with_error!(&e, GovernorError::UnauthorizedError); + } else { + // block the security council from canceling council proposals + let proposal_config = + storage::get_proposal_config(&e, proposal_id).unwrap_optimized(); + match proposal_config.action { + ProposalAction::Council(_) => { + panic_with_error!(&e, GovernorError::UnauthorizedError); + } + _ => {} + } } } diff --git a/contracts/governor/src/governor.rs b/contracts/governor/src/governor.rs index 9016fee..f0f4493 100644 --- a/contracts/governor/src/governor.rs +++ b/contracts/governor/src/governor.rs @@ -8,12 +8,19 @@ pub trait Governor { /// /// ### Arguments /// * `votes` - The address of the contract used to track votes + /// * `council` - The address of the security council for the DAO /// * `settings` - The settings for the governor - fn initialize(e: Env, votes: Address, settings: GovernorSettings); + fn initialize(e: Env, votes: Address, council: Address, settings: GovernorSettings); /// Get the current settings of the governor fn settings(e: Env) -> GovernorSettings; + /// Get the address of the security council for the DAO + fn council(e: Env) -> Address; + + /// Get the address of the votes token contract + fn vote_token(e: Env) -> Address; + /// Create a new proposal /// /// Returns the id of the new proposal diff --git a/contracts/governor/src/proposal_config.rs b/contracts/governor/src/proposal_config.rs index 136d029..7e768bc 100644 --- a/contracts/governor/src/proposal_config.rs +++ b/contracts/governor/src/proposal_config.rs @@ -26,6 +26,7 @@ impl ProposalConfig { } ProposalAction::Settings(ref settings) => require_valid_settings(e, settings), ProposalAction::Upgrade(_) => (), + ProposalAction::Council(_) => (), ProposalAction::Snapshot => (), } @@ -54,6 +55,9 @@ impl ProposalConfig { ProposalAction::Upgrade(ref wasm_hash) => { e.deployer().update_current_contract_wasm(wasm_hash.clone()); } + ProposalAction::Council(ref council) => { + storage::set_council_address(e, council); + } ProposalAction::Snapshot => { panic_with_error!(e, GovernorError::InvalidProposalType) } @@ -63,10 +67,8 @@ impl ProposalConfig { /// Check if the proposal is executable pub fn is_executable(&self) -> bool { match self.action { - ProposalAction::Calldata(_) => true, - ProposalAction::Settings(_) => true, - ProposalAction::Upgrade(_) => true, ProposalAction::Snapshot => false, + _ => true, } } } diff --git a/contracts/governor/src/settings.rs b/contracts/governor/src/settings.rs index 72e92e4..fe6496e 100644 --- a/contracts/governor/src/settings.rs +++ b/contracts/governor/src/settings.rs @@ -1,7 +1,10 @@ use soroban_sdk::{panic_with_error, Env}; use crate::{ - constants::{MAX_PROPOSAL_LIFETIME, MAX_VOTE_PERIOD}, + constants::{ + BPS_SCALAR, MAX_GRACE_PERIOD, MAX_PROPOSAL_LIFETIME, MAX_VOTE_PERIOD, MIN_GRACE_PERIOD, + MIN_VOTE_PERIOD, MIN_VOTE_THRESHOLD, + }, errors::GovernorError, types::GovernorSettings, }; @@ -12,24 +15,28 @@ use crate::{ /// * `settings` - The settings for the governor /// /// ### Panics -/// * If the vote_period is greater than the maximum vote period +/// * If the vote_period is greater than the maximum vote period or less than the minimum vote period /// * If the vote_delay + vote_period + timelock + grace_period is greater than the maximum proposal lifetime +/// * If the grace_period is less than the minimum grace period /// * If the proposal_threshold is less than 1 /// * If the counting_type is greater than 0b111 -/// * If the quorum or vote threshold is greater than 99% +/// * If the quorum or vote threshold is greater than 99% or less than 0.1% pub fn require_valid_settings(e: &Env, settings: &GovernorSettings) { - if settings.vote_period > MAX_VOTE_PERIOD { - panic_with_error!(&e, GovernorError::InvalidSettingsError) - } - if settings.vote_delay + settings.vote_period + settings.timelock + settings.grace_period * 2 - > MAX_PROPOSAL_LIFETIME - { - panic_with_error!(&e, GovernorError::InvalidSettingsError) - } - if settings.proposal_threshold < 1 + if settings.vote_period > MAX_VOTE_PERIOD + || settings.vote_period < MIN_VOTE_PERIOD + || settings.grace_period < MIN_GRACE_PERIOD + || settings.grace_period > MAX_GRACE_PERIOD + || settings.vote_delay + + settings.vote_period + + settings.timelock + + settings.grace_period * 2 + > MAX_PROPOSAL_LIFETIME || settings.counting_type > 0b111 - || settings.quorum > 9999 - || settings.vote_threshold > 9999 + || settings.proposal_threshold < MIN_VOTE_THRESHOLD + || settings.quorum > BPS_SCALAR - 100 + || settings.quorum < 10 + || settings.vote_threshold > BPS_SCALAR - 100 + || settings.vote_threshold < 10 { panic_with_error!(&e, GovernorError::InvalidSettingsError) } @@ -37,9 +44,7 @@ pub fn require_valid_settings(e: &Env, settings: &GovernorSettings) { #[cfg(test)] mod tests { - use soroban_sdk::{testutils::Address as _, Address}; - - use crate::constants::ONE_DAY_LEDGERS; + use crate::constants::{ONE_DAY_LEDGERS, ONE_HOUR_LEDGERS}; use super::*; @@ -47,7 +52,6 @@ mod tests { fn test_require_valid_settings_is_valid() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS, vote_period: ONE_DAY_LEDGERS * 5, @@ -66,7 +70,6 @@ mod tests { fn test_require_valid_settings_is_valid_at_max() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS * 3, vote_period: ONE_DAY_LEDGERS * 7, @@ -83,10 +86,9 @@ mod tests { #[test] #[should_panic(expected = "Error(Contract, #200)")] - fn test_require_valid_settings_invalid_vote_period() { + fn test_require_valid_settings_invalid_vote_period_max() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS, vote_period: ONE_DAY_LEDGERS * 7 + 1, @@ -100,12 +102,67 @@ mod tests { require_valid_settings(&e, &settings); } + #[test] + #[should_panic(expected = "Error(Contract, #200)")] + fn test_require_valid_settings_invalid_vote_period_min() { + let e = Env::default(); + let settings = GovernorSettings { + proposal_threshold: 1_0000000, + vote_delay: ONE_DAY_LEDGERS, + vote_period: ONE_HOUR_LEDGERS - 1, + timelock: ONE_DAY_LEDGERS, + grace_period: ONE_DAY_LEDGERS * 7, + quorum: 100, + counting_type: 2, + vote_threshold: 5100, + }; + + require_valid_settings(&e, &settings); + } + + #[test] + #[should_panic(expected = "Error(Contract, #200)")] + fn test_require_valid_settings_invalid_grace_period_max() { + let e = Env::default(); + let settings = GovernorSettings { + proposal_threshold: 1_0000000, + vote_delay: ONE_DAY_LEDGERS, + vote_period: ONE_DAY_LEDGERS * 5, + timelock: ONE_DAY_LEDGERS, + grace_period: 7 * ONE_DAY_LEDGERS + 1, + quorum: 100, + counting_type: 2, + vote_threshold: 5100, + }; + + require_valid_settings(&e, &settings); + assert!(true); + } + + #[test] + #[should_panic(expected = "Error(Contract, #200)")] + fn test_require_valid_settings_invalid_grace_period_min() { + let e = Env::default(); + let settings = GovernorSettings { + proposal_threshold: 1_0000000, + vote_delay: ONE_DAY_LEDGERS, + vote_period: ONE_DAY_LEDGERS * 5, + timelock: ONE_DAY_LEDGERS, + grace_period: ONE_DAY_LEDGERS - 1, + quorum: 100, + counting_type: 2, + vote_threshold: 5100, + }; + + require_valid_settings(&e, &settings); + assert!(true); + } + #[test] #[should_panic(expected = "Error(Contract, #200)")] fn test_require_valid_settings_invalid_proposal_lifetime() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS * 3 + 1, vote_period: ONE_DAY_LEDGERS * 7, @@ -124,7 +181,6 @@ mod tests { fn test_require_valid_settings_invalid_threshold() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 0, vote_delay: ONE_DAY_LEDGERS, vote_period: ONE_DAY_LEDGERS * 5, @@ -143,7 +199,6 @@ mod tests { fn test_require_valid_settings_invalid_counting_type() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS, vote_period: ONE_DAY_LEDGERS * 5, @@ -159,16 +214,33 @@ mod tests { #[test] #[should_panic(expected = "Error(Contract, #200)")] - fn test_require_valid_settings_invalid_quorum() { + fn test_require_valid_settings_invalid_quorum_max() { + let e = Env::default(); + let settings = GovernorSettings { + proposal_threshold: 1_0000000, + vote_delay: ONE_DAY_LEDGERS, + vote_period: ONE_DAY_LEDGERS * 5, + timelock: ONE_DAY_LEDGERS, + grace_period: ONE_DAY_LEDGERS * 7, + quorum: BPS_SCALAR - 99, + counting_type: 2, + vote_threshold: 5100, + }; + + require_valid_settings(&e, &settings); + } + + #[test] + #[should_panic(expected = "Error(Contract, #200)")] + fn test_require_valid_settings_invalid_quorum_min() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS, vote_period: ONE_DAY_LEDGERS * 5, timelock: ONE_DAY_LEDGERS, grace_period: ONE_DAY_LEDGERS * 7, - quorum: 10000, + quorum: 9, counting_type: 2, vote_threshold: 5100, }; @@ -178,10 +250,27 @@ mod tests { #[test] #[should_panic(expected = "Error(Contract, #200)")] - fn test_require_valid_settings_invalid_vote_threshold() { + fn test_require_valid_settings_invalid_vote_threshold_max() { + let e = Env::default(); + let settings = GovernorSettings { + proposal_threshold: 1_0000000, + vote_delay: ONE_DAY_LEDGERS, + vote_period: ONE_DAY_LEDGERS * 5, + timelock: ONE_DAY_LEDGERS, + grace_period: ONE_DAY_LEDGERS * 7, + quorum: 100, + counting_type: 2, + vote_threshold: BPS_SCALAR - 99, + }; + + require_valid_settings(&e, &settings); + } + + #[test] + #[should_panic(expected = "Error(Contract, #200)")] + fn test_require_valid_settings_invalid_vote_threshold_min() { let e = Env::default(); let settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS, vote_period: ONE_DAY_LEDGERS * 5, @@ -189,7 +278,7 @@ mod tests { grace_period: ONE_DAY_LEDGERS * 7, quorum: 100, counting_type: 2, - vote_threshold: 10000, + vote_threshold: 9, }; require_valid_settings(&e, &settings); diff --git a/contracts/governor/src/storage.rs b/contracts/governor/src/storage.rs index 6582843..28bd45b 100644 --- a/contracts/governor/src/storage.rs +++ b/contracts/governor/src/storage.rs @@ -10,7 +10,8 @@ use crate::{ const VOTER_TOKEN_ADDRESS_KEY: &str = "Votes"; const SETTINGS_KEY: &str = "Settings"; const IS_INIT_KEY: &str = "IsInit"; -const PROPOSAL_ID_KEY: &str = "ProposalId"; +const PROPOSAL_ID_KEY: &str = "PropId"; +const COUNCIL_KEY: &str = "Council"; // All stored data is used on a per proposal basis outside of the instance. Extend past the max possible // proposal lifetime to ensure all data is available after the proposal is concluced. @@ -119,6 +120,24 @@ pub fn get_settings(e: &Env) -> GovernorSettings { .unwrap_optimized() } +/// Set the council address +/// +/// ### Arguments +/// * `council` - The address of the council +pub fn set_council_address(e: &Env, council: &Address) { + e.storage() + .instance() + .set::(&Symbol::new(&e, COUNCIL_KEY), &council); +} + +/// Get the council address +pub fn get_council_address(e: &Env) -> Address { + e.storage() + .instance() + .get::(&Symbol::new(&e, COUNCIL_KEY)) + .unwrap_optimized() +} + /********** Persistent **********/ /// Set the next proposal id and bump if necessary diff --git a/contracts/governor/src/types.rs b/contracts/governor/src/types.rs index f915005..78090b3 100644 --- a/contracts/governor/src/types.rs +++ b/contracts/governor/src/types.rs @@ -4,9 +4,6 @@ use soroban_sdk::{contracttype, Address, BytesN, String, Symbol, Val, Vec}; #[derive(Clone)] #[contracttype] pub struct GovernorSettings { - /// The address of the security council that can cancel proposals during the vote delay period. If the DAO does not - /// have a council, this should be set to the zero address. - pub council: Address, /// The votes required to create a proposal. pub proposal_threshold: i128, /// The delay (in ledgers) from the proposal creation to when the voting period begins. The voting @@ -69,6 +66,9 @@ pub struct ProposalConfig { /// ### Settings /// The proposal will update the governor settings on execute. /// +/// ### Council +/// The proposal will update the council address on execute. +/// /// ### Snapshot /// There is no action to be taken by the proposal. #[derive(Clone)] @@ -77,6 +77,7 @@ pub enum ProposalAction { Calldata(Calldata), Upgrade(BytesN<32>), Settings(GovernorSettings), + Council(Address), Snapshot, } @@ -115,8 +116,7 @@ pub struct VoteCount { pub abstain: i128, } -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[contracttype] pub enum ProposalStatus { /// The proposal exists and voting has not been closed diff --git a/contracts/governor/src/vote_count.rs b/contracts/governor/src/vote_count.rs index 4b49b9b..338806d 100644 --- a/contracts/governor/src/vote_count.rs +++ b/contracts/governor/src/vote_count.rs @@ -50,7 +50,7 @@ impl VoteCount { /// * False if the vote has not reached quorum pub fn is_over_quorum(&self, quorum: u32, counting_type: u32, total_votes: i128) -> bool { let quorum_votes = self.count_quorum(counting_type); - let quorum_requirement_floor = (total_votes * quorum as i128) / BPS_SCALAR; + let quorum_requirement_floor = (total_votes * quorum as i128) / (BPS_SCALAR as i128); quorum_votes > quorum_requirement_floor } @@ -67,7 +67,7 @@ impl VoteCount { if against_and_for_votes == 0 { return false; } - let for_votes = (self._for * BPS_SCALAR) / against_and_for_votes; + let for_votes = (self._for * BPS_SCALAR as i128) / against_and_for_votes; for_votes > vote_threshold as i128 } diff --git a/contracts/tests/src/env.rs b/contracts/tests/src/env.rs index c4623df..53fbfd2 100644 --- a/contracts/tests/src/env.rs +++ b/contracts/tests/src/env.rs @@ -37,7 +37,7 @@ impl EnvTestUtils for Env { sequence_number: 100, network_id: Default::default(), base_reserve: 10, - min_temp_entry_ttl: 10 * ONE_DAY_LEDGERS, + min_temp_entry_ttl: ONE_DAY_LEDGERS, min_persistent_entry_ttl: 10 * ONE_DAY_LEDGERS, max_entry_ttl: 365 * ONE_DAY_LEDGERS, }); diff --git a/contracts/tests/src/governor.rs b/contracts/tests/src/governor.rs index 8676721..1855988 100644 --- a/contracts/tests/src/governor.rs +++ b/contracts/tests/src/governor.rs @@ -18,10 +18,12 @@ mod governor_contract_wasm { /// /// ### Arguments /// * `admin` - The address of the admin +/// * `council` - The address of the governor security council /// * `settings` - The settings for the governor pub fn create_governor<'a>( e: &Env, admin: &Address, + council: &Address, settings: &GovernorSettings, ) -> (Address, Address, Address) { let governor_address = e.register_contract(None, GovernorContract {}); @@ -30,7 +32,7 @@ pub fn create_governor<'a>( votes::create_bonding_token_votes(e, &underlying_token, &governor_address); let govenor_client: GovernorContractClient<'a> = GovernorContractClient::new(&e, &governor_address); - govenor_client.initialize(&vote_address, settings); + govenor_client.initialize(&vote_address, council, settings); return (governor_address, underlying_token, vote_address); } @@ -40,10 +42,12 @@ pub fn create_governor<'a>( /// /// ### Arguments /// * `admin` - The address of the admin +/// * `council` - The address of the governor security council /// * `settings` - The settings for the governor pub fn create_governor_wasm<'a>( e: &Env, admin: &Address, + council: &Address, settings: &GovernorSettings, ) -> (Address, Address, Address) { let governor_address = e.register_contract_wasm(None, governor_contract_wasm::WASM); @@ -52,7 +56,7 @@ pub fn create_governor_wasm<'a>( votes::create_bonding_token_votes_wasm(e, &underlying_token, &governor_address); let govenor_client: GovernorContractClient<'a> = GovernorContractClient::new(&e, &governor_address); - govenor_client.initialize(&vote_address, settings); + govenor_client.initialize(&vote_address, council, settings); return (governor_address, underlying_token, vote_address); } @@ -72,7 +76,7 @@ pub fn create_soroban_governor_wasm<'a>( let (vote_address, _) = votes::create_soroban_token_votes_wasm(e, &admin, &governor_address); let govenor_client: GovernorContractClient<'a> = GovernorContractClient::new(&e, &governor_address); - govenor_client.initialize(&vote_address, settings); + govenor_client.initialize(&vote_address, &admin, settings); return (governor_address, vote_address); } @@ -92,14 +96,13 @@ pub fn create_soroban_admin_governor_wasm<'a>( let (vote_address, _) = votes::create_soroban_admin_votes_wasm(e, &admin, &governor_address); let govenor_client: GovernorContractClient<'a> = GovernorContractClient::new(&e, &governor_address); - govenor_client.initialize(&vote_address, settings); + govenor_client.initialize(&vote_address, admin, settings); return (governor_address, vote_address); } /// Default governor settings -pub fn default_governor_settings(e: &Env) -> GovernorSettings { +pub fn default_governor_settings() -> GovernorSettings { GovernorSettings { - council: Address::generate(e), proposal_threshold: 1_0000000, vote_delay: ONE_DAY_LEDGERS, vote_period: ONE_DAY_LEDGERS * 7, diff --git a/contracts/tests/tests/governor/test_cancel.rs b/contracts/tests/tests/governor/test_cancel.rs index ae9f86b..b792b45 100644 --- a/contracts/tests/tests/governor/test_cancel.rs +++ b/contracts/tests/tests/governor/test_cancel.rs @@ -1,6 +1,9 @@ #[cfg(test)] use sep_41_token::testutils::MockTokenClient; -use soroban_governor::{types::ProposalStatus, GovernorContractClient}; +use soroban_governor::{ + types::{ProposalAction, ProposalStatus}, + GovernorContractClient, +}; use soroban_sdk::{ testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, vec, Address, Env, Error, IntoVal, Symbol, TryIntoVal, @@ -19,9 +22,9 @@ fn test_cancel() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -86,9 +89,9 @@ fn test_cancel_council() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -103,22 +106,18 @@ fn test_cancel_council() { let proposal_id = governor_client.propose(&samwise, &title, &description, &action); e.jump(settings.vote_delay / 2); - governor_client.cancel(&settings.council, &proposal_id); + governor_client.cancel(&bombadil, &proposal_id); // verify auths assert_eq!( e.auths()[0], ( - settings.council.clone(), + bombadil.clone(), AuthorizedInvocation { function: AuthorizedFunction::Contract(( governor_address.clone(), Symbol::new(&e, "cancel"), - vec![ - &e, - settings.council.to_val(), - proposal_id.try_into_val(&e).unwrap() - ] + vec![&e, bombadil.to_val(), proposal_id.try_into_val(&e).unwrap()] )), sub_invocations: std::vec![] } @@ -149,6 +148,47 @@ fn test_cancel_council() { assert_eq!(proposal_id_new, proposal_id + 1); } +#[test] +fn test_cancel_council_proposal() { + let e = Env::default(); + e.mock_all_auths(); + e.set_default_info(); + + let bombadil = Address::generate(&e); + let new_council = Address::generate(&e); + let samwise = Address::generate(&e); + let settings = default_governor_settings(); + let (governor_address, token_address, votes_address) = + create_governor(&e, &bombadil, &bombadil, &settings); + let token_client = MockTokenClient::new(&e, &token_address); + let votes_client = BondingVotesClient::new(&e, &votes_address); + let governor_client = GovernorContractClient::new(&e, &governor_address); + + let samwise_votes: i128 = 1 * 10i128.pow(7); + token_client.mint(&samwise, &samwise_votes); + votes_client.deposit(&samwise, &samwise_votes); + + // setup a council proposal + let (title, description, _) = default_proposal_data(&e); + let action = ProposalAction::Council(new_council.clone()); + + let proposal_id = governor_client.propose(&samwise, &title, &description, &action); + e.jump(settings.vote_delay / 2); + + // try to cancel with council + let result = governor_client.try_cancel(&bombadil, &proposal_id); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(4)))); + + let proposal = governor_client.get_proposal(&proposal_id).unwrap(); + assert_eq!(proposal.data.status, ProposalStatus::Open); + + // cancel with creator + governor_client.cancel(&samwise, &proposal_id); + + let proposal = governor_client.get_proposal(&proposal_id).unwrap(); + assert_eq!(proposal.data.status, ProposalStatus::Canceled); +} + #[test] #[should_panic(expected = "Error(Contract, #201)")] fn test_cancel_nonexistent_proposal() { @@ -158,8 +198,8 @@ fn test_cancel_nonexistent_proposal() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); - let (governor_address, _, _) = create_governor(&e, &bombadil, &settings); + let settings = default_governor_settings(); + let (governor_address, _, _) = create_governor(&e, &bombadil, &bombadil, &settings); let governor_client = GovernorContractClient::new(&e, &governor_address); governor_client.cancel(&samwise, &1); @@ -173,9 +213,9 @@ fn test_cancel_proposal_active() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -204,9 +244,10 @@ fn test_cancel_unauthorized_address() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let pippin = Address::generate(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -221,7 +262,7 @@ fn test_cancel_unauthorized_address() { let proposal_id = governor_client.propose(&samwise, &title, &description, &action); e.jump(settings.vote_delay / 2); - governor_client.cancel(&bombadil, &proposal_id); + governor_client.cancel(&pippin, &proposal_id); } #[test] @@ -235,9 +276,9 @@ fn test_cancel_already_closed() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); diff --git a/contracts/tests/tests/governor/test_close.rs b/contracts/tests/tests/governor/test_close.rs index 756a875..2d3a807 100644 --- a/contracts/tests/tests/governor/test_close.rs +++ b/contracts/tests/tests/governor/test_close.rs @@ -23,9 +23,9 @@ fn test_close_successful() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -99,9 +99,9 @@ fn test_close_defeated_quorum_not_met() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -171,9 +171,9 @@ fn test_close_defeated_threshold_not_met() { let frodo = Address::generate(&e); let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -244,9 +244,9 @@ fn test_close_expired() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -284,11 +284,16 @@ fn test_close_expired() { // verify events let proposal_votes = governor_client.get_proposal_votes(&proposal_id); let events = e.events().all(); - let tx_events = vec![&e, events.last().unwrap()]; + let tx_events = events.slice((events.len() - 2)..events.len()); assert_eq!( tx_events, vec![ &e, + ( + governor_address.clone(), + (Symbol::new(&e, "proposal_expired"), proposal_id,).into_val(&e), + ().into_val(&e) + ), ( governor_address.clone(), ( @@ -320,10 +325,10 @@ fn test_close_tracks_quorum_with_counting_type() { let pippin = Address::generate(&e); let merry = Address::generate(&e); - let mut settings = default_governor_settings(&e); + let mut settings = default_governor_settings(); settings.counting_type = 0b011; // include against and abstain in quorum let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -373,9 +378,9 @@ fn test_close_successful_non_executable() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -419,9 +424,9 @@ fn test_close_nonexistent_proposal() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -445,9 +450,9 @@ fn test_close_vote_period_unfinished() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -477,9 +482,9 @@ fn test_close_already_closed() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); diff --git a/contracts/tests/tests/governor/test_execute.rs b/contracts/tests/tests/governor/test_execute.rs index 060cade..cab088d 100644 --- a/contracts/tests/tests/governor/test_execute.rs +++ b/contracts/tests/tests/governor/test_execute.rs @@ -25,9 +25,9 @@ fn test_execute_calldata_no_auths() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -119,9 +119,9 @@ fn test_execute_calldata_auth_chain() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -200,9 +200,9 @@ fn test_execute_calldata_single_auth() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -274,9 +274,9 @@ fn test_execute_settings() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -288,7 +288,6 @@ fn test_execute_settings() { // create a proposal let new_settings = GovernorSettings { - council: Address::generate(&e), proposal_threshold: 829421, vote_delay: 1231, vote_period: 7456, @@ -345,10 +344,9 @@ fn test_execute_upgrade() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); - let mut settings = default_governor_settings(&e); - settings.council = frodo.clone(); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &frodo, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -394,6 +392,53 @@ fn test_execute_upgrade() { assert_eq!(new_client.balance(&frodo), 123); } +#[test] +fn test_execute_council() { + let e = Env::default(); + e.set_default_info(); + e.budget().reset_unlimited(); + + let bombadil = Address::generate(&e); + let frodo = Address::generate(&e); + let new_council = Address::generate(&e); + + let settings = default_governor_settings(); + let (governor_address, token_address, votes_address) = + create_governor(&e, &bombadil, &bombadil, &settings); + let token_client = MockTokenClient::new(&e, &token_address); + let votes_client = TokenVotesClient::new(&e, &votes_address); + let governor_client = GovernorContractClient::new(&e, &governor_address); + + // set intial votes + let frodo_votes: i128 = 10_000 * 10i128.pow(7); + token_client.mock_all_auths().mint(&frodo, &frodo_votes); + votes_client.mock_all_auths().deposit(&frodo, &frodo_votes); + + // create a proposal + let (title, description, _) = default_proposal_data(&e); + let action = ProposalAction::Council(new_council.clone()); + + let proposal_id = + governor_client + .mock_all_auths() + .propose(&frodo, &title, &description, &action); + e.jump(settings.vote_delay + 1); + governor_client + .mock_all_auths() + .vote(&frodo, &proposal_id, &1); + e.jump(settings.vote_period); + governor_client.mock_all_auths().close(&proposal_id); + e.jump(settings.timelock); + + // remove any potential auth mocking + e.set_auths(&[]); + governor_client.set_auths(&[]); + governor_client.execute(&proposal_id); + + let council = governor_client.council(); + assert_eq!(council, new_council); +} + #[test] fn test_execute_expired() { let e = Env::default(); @@ -413,9 +458,9 @@ fn test_execute_expired() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -510,8 +555,8 @@ fn test_execute_nonexistent_proposal() { e.mock_all_auths(); let bombadil = Address::generate(&e); - let settings = default_governor_settings(&e); - let (governor_address, _, _) = create_governor(&e, &bombadil, &settings); + let settings = default_governor_settings(); + let (governor_address, _, _) = create_governor(&e, &bombadil, &bombadil, &settings); let governor_client = GovernorContractClient::new(&e, &governor_address); governor_client.execute(&0); @@ -528,9 +573,9 @@ fn test_execute_proposal_not_queued() { let frodo = Address::generate(&e); let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -588,9 +633,9 @@ fn test_execute_timelock_not_met() { let frodo = Address::generate(&e); let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -648,9 +693,9 @@ fn test_execute_defeated_errors() { let frodo = Address::generate(&e); let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -711,9 +756,9 @@ fn test_execute_snapshot_errors() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = TokenVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); diff --git a/contracts/tests/tests/governor/test_initialize.rs b/contracts/tests/tests/governor/test_initialize.rs index 5d80371..f087117 100644 --- a/contracts/tests/tests/governor/test_initialize.rs +++ b/contracts/tests/tests/governor/test_initialize.rs @@ -4,6 +4,7 @@ use soroban_sdk::{testutils::Address as _, Address, Env}; use tests::{ env::EnvTestUtils, governor::{create_governor, default_governor_settings}, + votes::create_soroban_token_votes_wasm, }; #[test] @@ -13,9 +14,12 @@ fn test_initialize_sets_storage() { e.mock_all_auths(); let bombadil = Address::generate(&e); - let settings = default_governor_settings(&e); - let (governor_address, _, _) = create_governor(&e, &bombadil, &settings); + let settings = default_governor_settings(); + let governor_address = e.register_contract(None, GovernorContract {}); + let (vote_address, _) = create_soroban_token_votes_wasm(&e, &bombadil, &governor_address); + let governor_client = GovernorContractClient::new(&e, &governor_address); + governor_client.initialize(&vote_address, &bombadil, &settings); let result = governor_client.settings(); assert_eq!(result.counting_type, settings.counting_type); @@ -25,6 +29,12 @@ fn test_initialize_sets_storage() { assert_eq!(result.vote_delay, settings.vote_delay); assert_eq!(result.vote_period, settings.vote_period); assert_eq!(result.vote_threshold, settings.vote_threshold); + + let council = governor_client.council(); + assert_eq!(council, bombadil); + + let vote_token = governor_client.vote_token(); + assert_eq!(vote_token, vote_address); } #[test] @@ -35,11 +45,11 @@ fn test_initalize_already_initalized() { e.mock_all_auths(); let bombadil = Address::generate(&e); - let settings = default_governor_settings(&e); - let (governor_address, _, votes_address) = create_governor(&e, &bombadil, &settings); + let settings = default_governor_settings(); + let (governor_address, _, votes_address) = create_governor(&e, &bombadil, &bombadil, &settings); let governor_client = GovernorContractClient::new(&e, &governor_address); - governor_client.initialize(&votes_address, &settings); + governor_client.initialize(&votes_address, &bombadil, &settings); } #[test] @@ -49,10 +59,10 @@ fn test_initialize_vote_period_exceeds_max() { let address = e.register_contract(None, GovernorContract {}); let govenor: GovernorContractClient<'_> = GovernorContractClient::new(&e, &address); let votes = Address::generate(&e); - let mut settings = default_governor_settings(&e); + let mut settings = default_governor_settings(); settings.vote_period = 7 * 17280 + 1; - govenor.initialize(&votes, &settings); + govenor.initialize(&votes, &Address::generate(&e), &settings); } #[test] @@ -62,11 +72,11 @@ fn test_initialize_proposal_exceeds_max_lifetime() { let address = e.register_contract(None, GovernorContract {}); let govenor: GovernorContractClient<'_> = GovernorContractClient::new(&e, &address); let votes = Address::generate(&e); - let mut settings = default_governor_settings(&e); + let mut settings = default_governor_settings(); settings.vote_delay = 5 * 17280; settings.vote_period = 5 * 17280; settings.timelock = 7 * 17280; settings.grace_period = 7 * 17280 + 1; - govenor.initialize(&votes, &settings); + govenor.initialize(&votes, &Address::generate(&e), &settings); } diff --git a/contracts/tests/tests/governor/test_propose.rs b/contracts/tests/tests/governor/test_propose.rs index 7cf9047..ec0bea2 100644 --- a/contracts/tests/tests/governor/test_propose.rs +++ b/contracts/tests/tests/governor/test_propose.rs @@ -19,9 +19,9 @@ fn test_propose_calldata() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -139,9 +139,9 @@ fn test_propose_calldata_validates() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -169,9 +169,9 @@ fn test_propose_with_active_proposal() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -205,9 +205,9 @@ fn test_propose_snapshot() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -241,10 +241,9 @@ fn test_propose_upgrade() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let mut settings = default_governor_settings(&e); - settings.council = samwise.clone(); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &samwise, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -284,10 +283,9 @@ fn test_propose_upgrade_requires_council() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let mut settings = default_governor_settings(&e); - settings.council = bombadil.clone(); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -310,9 +308,9 @@ fn test_propose_settings() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -353,9 +351,9 @@ fn test_propose_settings_validates() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -384,9 +382,9 @@ fn test_propose_below_proposal_threshold() { let bombadil = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); diff --git a/contracts/tests/tests/governor/test_vote.rs b/contracts/tests/tests/governor/test_vote.rs index 507dab0..74cc700 100644 --- a/contracts/tests/tests/governor/test_vote.rs +++ b/contracts/tests/tests/governor/test_vote.rs @@ -22,9 +22,9 @@ fn test_vote() { let frodo = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -105,9 +105,9 @@ fn test_vote_user_changes_support() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -155,9 +155,9 @@ fn test_vote_multiple_users() { let merry = Address::generate(&e); let bilbo = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -224,9 +224,9 @@ fn test_vote_nonexistent_proposal() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -253,9 +253,9 @@ fn test_vote_delay_not_ended() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -287,9 +287,9 @@ fn test_vote_period_ended() { let bombadil = Address::generate(&e); let frodo = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -323,9 +323,9 @@ fn test_vote_invalid_support_option() { let frodo = Address::generate(&e); let samwise = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor(&e, &bombadil, &settings); + create_governor(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); diff --git a/contracts/tests/tests/test_checkpoint_integration.rs b/contracts/tests/tests/test_checkpoint_integration.rs new file mode 100644 index 0000000..e1cbaf6 --- /dev/null +++ b/contracts/tests/tests/test_checkpoint_integration.rs @@ -0,0 +1,245 @@ +#[cfg(test)] +use sep_41_token::testutils::MockTokenClient; +use soroban_governor::types::{ProposalAction, ProposalStatus}; +use soroban_governor::GovernorContractClient; +use soroban_sdk::testutils::{Ledger as _, LedgerInfo}; +use soroban_sdk::{testutils::Address as _, Address, Env}; + +use tests::governor::create_governor_wasm; +use tests::{ + env::EnvTestUtils, + governor::{default_governor_settings, default_proposal_data}, +}; +use tests::{votes::BondingVotesClient, ONE_DAY_LEDGERS}; + +#[test] +fn test_checkpoint_safely_tracked_for_proposals() { + let e = Env::default(); + e.set_default_info(); + e.mock_all_auths(); + e.budget().reset_unlimited(); + + let bombadil = Address::generate(&e); + let prop_user_1 = Address::generate(&e); + let prop_user_2 = Address::generate(&e); + let prop_user_3 = Address::generate(&e); + let frodo = Address::generate(&e); + let samwise = Address::generate(&e); + let pippin = Address::generate(&e); + + let settings = default_governor_settings(); + let (governor_address, underlying_address, votes_address) = + create_governor_wasm(&e, &bombadil, &bombadil, &settings); + let votes_client = BondingVotesClient::new(&e, &votes_address); + let governor_client = GovernorContractClient::new(&e, &governor_address); + let token_client = MockTokenClient::new(&e, &underlying_address); + + let gov_balance: i128 = 123 * 10i128.pow(7); + token_client.mint(&governor_address, &gov_balance); + + // set intial votes + let frodo_votes: i128 = 10_000 * 10i128.pow(7); + token_client.mint(&frodo, &(frodo_votes + 10i128.pow(7))); + votes_client.deposit(&frodo, &frodo_votes); + + let samwise_votes = 5_000 * 10i128.pow(7); + token_client.mint(&samwise, &samwise_votes); + votes_client.deposit(&samwise, &samwise_votes); + + let pippin_votes = 2_000 * 10i128.pow(7); + token_client.mint(&pippin, &(pippin_votes * 2)); + votes_client.deposit(&pippin, &pippin_votes); + + token_client.mint(&prop_user_1, &10i128.pow(7)); + votes_client.deposit(&prop_user_1, &10i128.pow(7)); + token_client.mint(&prop_user_2, &10i128.pow(7)); + votes_client.deposit(&prop_user_2, &10i128.pow(7)); + token_client.mint(&prop_user_3, &10i128.pow(7)); + votes_client.deposit(&prop_user_3, &10i128.pow(7)); + + // pippin delegate to frodo + votes_client.delegate(&pippin, &frodo); + + // create a proposal w/ a vote delay (1 day ledgers) + let (title, description, action) = default_proposal_data(&e); + let real_proposal_id = governor_client.propose(&prop_user_1, &title, &description, &action); + + e.jump(ONE_DAY_LEDGERS / 2); + + // create a snapshot proposal (no vote delay) to cause out of order vote ledger + let action = ProposalAction::Snapshot; + let snapshot_proposal_id = governor_client.propose(&prop_user_2, &title, &description, &action); + + e.jump(5); + + // pippin forces update to Frodo's votes + votes_client.withdraw(&pippin, &10i128.pow(7)); + + e.jump(1); + + // vote on snapshot proposal + governor_client.vote(&frodo, &snapshot_proposal_id, &0); + governor_client.vote(&samwise, &snapshot_proposal_id, &1); + + let votes_0 = governor_client + .get_proposal_votes(&snapshot_proposal_id) + .unwrap(); + + assert_eq!(votes_0.against, frodo_votes + pippin_votes); + assert_eq!(votes_0._for, samwise_votes); + assert_eq!(votes_0.abstain, 0); + + e.jump(ONE_DAY_LEDGERS / 2); + + // Frodo and Samwise update their votes + votes_client.deposit(&frodo, &10i128.pow(7)); + votes_client.withdraw(&samwise, &10i128.pow(7)); + + e.jump(1); + + governor_client.vote(&frodo, &real_proposal_id, &0); + governor_client.vote(&samwise, &real_proposal_id, &1); + + let votes_1 = governor_client + .get_proposal_votes(&real_proposal_id) + .unwrap(); + + assert_eq!(votes_1.against, frodo_votes + pippin_votes - 10i128.pow(7)); + assert_eq!(votes_1._for, samwise_votes); + assert_eq!(votes_1.abstain, 0); +} + +#[test] +fn test_checkpoints_retained_long_enough() { + let e = Env::default(); + e.set_default_info(); + e.ledger().set(LedgerInfo { + timestamp: 1441065600, // Sept 1st, 2015 12:00:00 AM UTC + protocol_version: 20, + sequence_number: 100, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: ONE_DAY_LEDGERS, + // required to ensure token balances don't expire + min_persistent_entry_ttl: 100 * ONE_DAY_LEDGERS, + max_entry_ttl: 365 * ONE_DAY_LEDGERS, + }); + e.mock_all_auths(); + e.budget().reset_unlimited(); + + let bombadil = Address::generate(&e); + let prop_user_1 = Address::generate(&e); + let prop_user_2 = Address::generate(&e); + let prop_user_3 = Address::generate(&e); + let frodo = Address::generate(&e); + let samwise = Address::generate(&e); + + let mut settings = default_governor_settings(); + // set settings to max grace period and vote period while ensuring + // maximum proposal lifetime is reached + settings.vote_period = 7 * ONE_DAY_LEDGERS; + settings.grace_period = 7 * ONE_DAY_LEDGERS; + settings.vote_delay = 7 * ONE_DAY_LEDGERS; + settings.timelock = 0; + settings.quorum = 8000; // force close to fail if supply checkpoint is lost + let (governor_address, underlying_address, votes_address) = + create_governor_wasm(&e, &bombadil, &bombadil, &settings); + let votes_client = BondingVotesClient::new(&e, &votes_address); + let governor_client = GovernorContractClient::new(&e, &governor_address); + let token_client = MockTokenClient::new(&e, &underlying_address); + + let gov_balance: i128 = 123 * 10i128.pow(7); + token_client.mint(&governor_address, &gov_balance); + + // set intial votes + let frodo_votes: i128 = 10_000 * 10i128.pow(7); + token_client.mint(&frodo, &(frodo_votes * 3)); + votes_client.deposit(&frodo, &frodo_votes); + + let samwise_votes = 2_000 * 10i128.pow(7); + token_client.mint(&samwise, &(frodo_votes * 2)); + votes_client.deposit(&samwise, &samwise_votes); + + token_client.mint(&prop_user_1, &10i128.pow(7)); + votes_client.deposit(&prop_user_1, &10i128.pow(7)); + token_client.mint(&prop_user_2, &10i128.pow(7)); + votes_client.deposit(&prop_user_2, &10i128.pow(7)); + token_client.mint(&prop_user_3, &10i128.pow(7)); + votes_client.deposit(&prop_user_3, &10i128.pow(7)); + + // allow time to pass before any proposals are created + e.jump(31 * ONE_DAY_LEDGERS); + + // create an executable proposal + let (title, description, _) = default_proposal_data(&e); + let action = ProposalAction::Council(frodo.clone()); + let proposal_id = governor_client.propose(&prop_user_1, &title, &description, &action); + let executable_vote_ledger = e.ledger().sequence() + 7 * ONE_DAY_LEDGERS; + + e.jump(1); + + // create a snapshot proposal (no vote delay) to create checkpoints + let snapshot = ProposalAction::Snapshot; + let snapshot_proposal_id_1 = + governor_client.propose(&prop_user_2, &title, &description, &snapshot); + + e.jump(1); + + // force checkpoint for supply and frodo + votes_client.deposit(&frodo, &10i128.pow(7)); + + // Jump to the end of the snapshot proposal voting period + e.jump(7 * ONE_DAY_LEDGERS - 2); + + governor_client.vote(&frodo, &snapshot_proposal_id_1, &1); + governor_client.vote(&samwise, &snapshot_proposal_id_1, &0); + + let votes_0 = governor_client + .get_proposal_votes(&snapshot_proposal_id_1) + .unwrap(); + assert_eq!(votes_0.against, samwise_votes); + assert_eq!(votes_0._for, frodo_votes); + + // Jump one ledger past the vote start of the executable proposal + e.jump(1); + + // force checkpoint for supply and frodo + votes_client.deposit(&frodo, &frodo_votes); + + // Jump to the end of the voting period for the executable proposal + e.jump(7 * ONE_DAY_LEDGERS - 1); + + governor_client.vote(&frodo, &proposal_id, &1); + governor_client.vote(&samwise, &proposal_id, &0); + + let votes_1 = governor_client.get_proposal_votes(&proposal_id).unwrap(); + assert_eq!(votes_1.against, samwise_votes); + assert_eq!(votes_1._for, frodo_votes + 10i128.pow(7)); + + // Jump to one ledger before the end of the grace period for the executable proposal + e.jump(7 * ONE_DAY_LEDGERS - 1); + + // create another snapshot proposal + let _ = governor_client.propose(&prop_user_3, &title, &description, &snapshot); + + e.jump(1); + + // force checkpoint for supply and samwise + votes_client.deposit(&frodo, &10i128.pow(7)); + + // try and close proposal + governor_client.close(&proposal_id); + + let proposal = governor_client.get_proposal(&proposal_id).unwrap(); + assert_eq!(proposal.data.status, ProposalStatus::Successful); + assert_eq!(proposal.data.eta, e.ledger().sequence() + settings.timelock); + + let past_supply = votes_client.get_past_total_supply(&executable_vote_ledger); + assert_eq!( + past_supply, + frodo_votes + samwise_votes + 3 * 10i128.pow(7) + 10i128.pow(7) + ); + + let past_votes = votes_client.get_past_votes(&frodo, &executable_vote_ledger); + assert_eq!(past_votes, frodo_votes + 10i128.pow(7)); +} diff --git a/contracts/tests/tests/test_double_count.rs b/contracts/tests/tests/test_double_count.rs index e470890..9c5bc25 100644 --- a/contracts/tests/tests/test_double_count.rs +++ b/contracts/tests/tests/test_double_count.rs @@ -27,7 +27,7 @@ fn test_double_count() { let samwise = Address::generate(&e); let pippin = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, votes_address) = create_soroban_governor_wasm(&e, &bombadil, &settings); let votes_client = SorobanVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); diff --git a/contracts/tests/tests/test_wasm_happy_path.rs b/contracts/tests/tests/test_wasm_happy_path.rs index 7babbfe..4efe557 100644 --- a/contracts/tests/tests/test_wasm_happy_path.rs +++ b/contracts/tests/tests/test_wasm_happy_path.rs @@ -28,9 +28,9 @@ fn test_wasm_happy_path() { let pippin = Address::generate(&e); let merry = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (governor_address, token_address, votes_address) = - create_governor_wasm(&e, &bombadil, &settings); + create_governor_wasm(&e, &bombadil, &bombadil, &settings); let token_client = MockTokenClient::new(&e, &token_address); let votes_client = BondingVotesClient::new(&e, &votes_address); let governor_client = GovernorContractClient::new(&e, &governor_address); @@ -173,7 +173,7 @@ fn test_wasm_happy_path_soroban_token() { let pippin = Address::generate(&e); let merry = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (token_address, token_client) = create_stellar_token(&e, &bombadil); let (governor_address, votes_address) = create_soroban_governor_wasm(&e, &bombadil, &settings); let votes_client = SorobanVotesClient::new(&e, &votes_address); @@ -308,7 +308,7 @@ fn test_wasm_happy_path_admin_token() { let pippin = Address::generate(&e); let merry = Address::generate(&e); - let settings = default_governor_settings(&e); + let settings = default_governor_settings(); let (token_address, token_client) = create_stellar_token(&e, &bombadil); let (governor_address, votes_address) = create_soroban_admin_governor_wasm(&e, &bombadil, &settings); diff --git a/contracts/tests/tests/votes/test_get_past.rs b/contracts/tests/tests/votes/test_get_past.rs index 6ab87f3..c21ad08 100644 --- a/contracts/tests/tests/votes/test_get_past.rs +++ b/contracts/tests/tests/votes/test_get_past.rs @@ -236,20 +236,20 @@ fn test_past_checkpoints_get_pruned() { // occuring after the vote starts are recorded properly let cur_ledger = e.ledger().sequence(); let start_vote_0 = cur_ledger + ONE_DAY_LEDGERS - 1; - let start_vote_1 = cur_ledger + 4 * ONE_DAY_LEDGERS - 1; - let start_vote_2 = cur_ledger + 8 * ONE_DAY_LEDGERS - 1; - let start_vote_3 = cur_ledger + 10 * ONE_DAY_LEDGERS - 1; + let start_vote_1 = cur_ledger + 3 * ONE_DAY_LEDGERS - 1; + let start_vote_2 = cur_ledger + 15 * ONE_DAY_LEDGERS - 1; + let start_vote_3 = cur_ledger + 16 * ONE_DAY_LEDGERS - 1; votes_client.set_vote_sequence(&start_vote_0); votes_client.set_vote_sequence(&start_vote_1); votes_client.set_vote_sequence(&start_vote_2); votes_client.set_vote_sequence(&start_vote_3); - // Time = 10 days ago + // Time = 16 days ago let deposit_amount_frodo = 1_000 * 10i128.pow(7); votes_client.mint(&frodo, &deposit_amount_frodo); e.jump(ONE_DAY_LEDGERS); - // Time = 9 days ago (vote 0 passed by 1 ledger) + // Time = 15 days ago (vote 0 passed by 1 ledger) let deposit_amount_samwise = 250 * 10i128.pow(7); votes_client.mint(&samwise, &deposit_amount_samwise); @@ -257,19 +257,19 @@ fn test_past_checkpoints_get_pruned() { let transfer_1_amount = 100 * 10i128.pow(7); votes_client.transfer(&samwise, &frodo, &transfer_1_amount); - e.jump(3 * ONE_DAY_LEDGERS); - // Time = 6 days ago (vote 1 passed by 1 ledger) + e.jump(2 * ONE_DAY_LEDGERS); + // Time = 13 days ago (vote 1 passed by 1 ledger) let deposit_amount_pippin = 5_000 * 10i128.pow(7); votes_client.mint(&pippin, &deposit_amount_pippin); - e.jump(4 * ONE_DAY_LEDGERS); - // Time = 2 days ago (vote 2 passed by 1 ledger) + e.jump(12 * ONE_DAY_LEDGERS); + // Time = 1 days ago (vote 2 passed by 1 ledger) let transfer_2_amount = 125 * 10i128.pow(7); votes_client.transfer(&pippin, &frodo, &transfer_2_amount); - e.jump(2 * ONE_DAY_LEDGERS); + e.jump(ONE_DAY_LEDGERS); // Time = now (vote 3 passed by 1 ledger) // set a vote ledger to cause the votes_client.set_vote_sequence(&(e.ledger().sequence() + 2 * ONE_DAY_LEDGERS)); @@ -293,7 +293,7 @@ fn test_past_checkpoints_get_pruned() { let deposit_2_amount_samwise = 50 * 10i128.pow(7); votes_client.mint(&samwise, &deposit_2_amount_samwise); - let max_vote_period_check = e.ledger().sequence() - 7 * ONE_DAY_LEDGERS; + let max_vote_period_check = e.ledger().sequence() - 14 * ONE_DAY_LEDGERS; // verify current values assert_eq!( @@ -389,3 +389,66 @@ fn test_past_checkpoints_get_pruned() { deposit_amount_pippin - transfer_2_amount ); } + +#[test] +fn test_get_past_transfer_same_user() { + let e = Env::default(); + e.mock_all_auths(); + e.set_default_info(); + e.budget().reset_unlimited(); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + let frodo = Address::generate(&e); + let pippin = Address::generate(&e); + let merry = Address::generate(&e); + let governor = Address::generate(&e); + + let (_, token_client) = create_soroban_token_votes_wasm(&e, &bombadil, &governor); + + let vote_ledger = e.ledger().sequence() + 99; + token_client.set_vote_sequence(&vote_ledger); + + let initial_balance = 100 * 10i128.pow(7); + token_client.mint(&frodo, &initial_balance); + token_client.mint(&samwise, &initial_balance); + token_client.mint(&pippin, &initial_balance); + token_client.mint(&merry, &initial_balance); + + token_client.delegate(&pippin, &samwise); + + assert_eq!(token_client.get_votes(&frodo), initial_balance); + assert_eq!(token_client.get_votes(&samwise), initial_balance * 2); + assert_eq!(token_client.get_votes(&pippin), 0); + assert_eq!(token_client.get_votes(&merry), initial_balance); + + e.jump(10); + + // no checkpoint recorded without actual token changes + token_client.transfer(&frodo, &frodo, &100); + token_client.transfer(&pippin, &pippin, &100); + token_client.transfer(&merry, &bombadil, &0); + + assert_eq!(token_client.get_votes(&frodo), initial_balance); + assert_eq!(token_client.get_votes(&samwise), initial_balance * 2); + assert_eq!(token_client.get_votes(&pippin), 0); + assert_eq!(token_client.get_votes(&merry), initial_balance); + + // verify vote balances were not written + assert_eq!( + token_client.get_past_votes(&frodo, &(e.ledger().sequence() - 1)), + initial_balance + ); + assert_eq!( + token_client.get_past_votes(&samwise, &(e.ledger().sequence() - 1)), + initial_balance * 2 + ); + assert_eq!( + token_client.get_past_votes(&pippin, &(e.ledger().sequence() - 1)), + 0 + ); + assert_eq!( + token_client.get_past_votes(&merry, &(e.ledger().sequence() - 1)), + initial_balance + ); +} diff --git a/contracts/tests/tests/votes/test_token_actions.rs b/contracts/tests/tests/votes/test_token_actions.rs index ccb2319..d495986 100644 --- a/contracts/tests/tests/votes/test_token_actions.rs +++ b/contracts/tests/tests/votes/test_token_actions.rs @@ -2,7 +2,7 @@ use soroban_sdk::{ symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, - Address, Env, IntoVal, String, Symbol, + Address, Env, Error, IntoVal, String, Symbol, }; use tests::{ common::create_stellar_token, @@ -74,7 +74,7 @@ fn test_token_actions_soroban() { AuthorizedInvocation { function: AuthorizedFunction::Contract(( votes_client.address.clone(), - symbol_short!("approve"), + Symbol::new(&e, "approve"), (&user2, &user3, 500_i128, 200_u32).into_val(&e), )), sub_invocations: std::vec![] @@ -91,7 +91,7 @@ fn test_token_actions_soroban() { AuthorizedInvocation { function: AuthorizedFunction::Contract(( votes_client.address.clone(), - symbol_short!("transfer"), + Symbol::new(&e, "transfer"), (&user1, &user2, 600_i128).into_val(&e), )), sub_invocations: std::vec![] @@ -102,6 +102,7 @@ fn test_token_actions_soroban() { assert_eq!(votes_client.get_votes(&user1), 400); assert_eq!(votes_client.balance(&user2), 600); assert_eq!(votes_client.get_votes(&user2), 600); + assert_eq!(votes_client.total_supply(), 1000); votes_client.transfer_from(&user3, &user2, &user1, &400); assert_eq!( @@ -123,33 +124,81 @@ fn test_token_actions_soroban() { assert_eq!(votes_client.balance(&user2), 200); assert_eq!(votes_client.get_votes(&user2), 200); - votes_client.transfer(&user1, &user3, &300); - assert_eq!(votes_client.balance(&user1), 500); - assert_eq!(votes_client.get_votes(&user1), 500); - assert_eq!(votes_client.balance(&user3), 300); - assert_eq!(votes_client.get_votes(&user3), 300); + votes_client.burn_from(&user3, &user2, &50); + assert_eq!( + e.auths(), + std::vec![( + user3.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + votes_client.address.clone(), + Symbol::new(&e, "burn_from"), + (&user3, &user2, 50_i128).into_val(&e), + )), + sub_invocations: std::vec![] + } + )] + ); + assert_eq!(votes_client.balance(&user1), 800); + assert_eq!(votes_client.get_votes(&user1), 800); + assert_eq!(votes_client.balance(&user2), 150); + assert_eq!(votes_client.get_votes(&user2), 150); + assert_eq!(votes_client.total_supply(), 950); - // Increase to 500 - votes_client.approve(&user2, &user3, &500, &200); - assert_eq!(votes_client.allowance(&user2, &user3), 500); - votes_client.approve(&user2, &user3, &0, &200); + votes_client.burn(&user1, &50); assert_eq!( e.auths(), std::vec![( - user2.clone(), + user1.clone(), AuthorizedInvocation { function: AuthorizedFunction::Contract(( votes_client.address.clone(), - symbol_short!("approve"), - (&user2, &user3, 0_i128, 200_u32).into_val(&e), + Symbol::new(&e, "burn"), + (&user1, 50_i128).into_val(&e), )), sub_invocations: std::vec![] } )] ); + assert_eq!(votes_client.balance(&user1), 750); + assert_eq!(votes_client.get_votes(&user1), 750); + assert_eq!(votes_client.balance(&user2), 150); + assert_eq!(votes_client.get_votes(&user2), 150); + assert_eq!(votes_client.total_supply(), 900); + + votes_client.approve(&user2, &user3, &500, &200); + assert_eq!(votes_client.allowance(&user2, &user3), 500); + votes_client.approve(&user2, &user3, &0, &0); assert_eq!(votes_client.allowance(&user2, &user3), 0); } +#[test] +fn test_self_transfer() { + let e = Env::default(); + e.mock_all_auths(); + e.set_default_info(); + + let bombadil = Address::generate(&e); + let user1 = Address::generate(&e); + let governor = Address::generate(&e); + + let (_, votes_client) = create_soroban_token_votes_wasm(&e, &bombadil, &governor); + + let result = votes_client.try_transfer(&user1, &user1, &100); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(10)))); + + let deposit_amount = 1000; + votes_client.mint(&user1, &deposit_amount); + assert_eq!(votes_client.balance(&user1), deposit_amount); + + votes_client.transfer(&user1, &user1, &100); + assert_eq!(votes_client.balance(&user1), deposit_amount); + + votes_client.approve(&user1, &bombadil, &1000, &500); + votes_client.transfer_from(&bombadil, &user1, &user1, &100); + assert_eq!(votes_client.balance(&user1), deposit_amount); +} + #[test] #[should_panic(expected = "Error(Contract, #10)")] fn transfer_insufficient_balance_soroban() { @@ -170,6 +219,26 @@ fn transfer_insufficient_balance_soroban() { votes_client.transfer(&user1, &user2, &1001); } +#[test] +#[should_panic(expected = "Error(Contract, #8)")] +fn transfer_negative_amount() { + let e = Env::default(); + e.mock_all_auths(); + e.set_default_info(); + + let bombadil = Address::generate(&e); + let user1 = Address::generate(&e); + let user2 = Address::generate(&e); + let governor = Address::generate(&e); + + let (_, votes_client) = create_soroban_token_votes_wasm(&e, &bombadil, &governor); + + let deposit_amount = 1000; + votes_client.mint(&user1, &deposit_amount); + + votes_client.transfer(&user1, &user2, &-1); +} + #[test] #[should_panic(expected = "Error(Contract, #9)")] fn transfer_from_insufficient_allowance_soroban() { @@ -194,6 +263,30 @@ fn transfer_from_insufficient_allowance_soroban() { votes_client.transfer_from(&user3, &user1, &user2, &101); } +#[test] +#[should_panic(expected = "Error(Contract, #8)")] +fn transfer_from_negative_amount() { + let e = Env::default(); + e.mock_all_auths(); + e.set_default_info(); + + let bombadil = Address::generate(&e); + let user1 = Address::generate(&e); + let user2 = Address::generate(&e); + let user3 = Address::generate(&e); + let governor = Address::generate(&e); + + let (_, votes_client) = create_soroban_token_votes_wasm(&e, &bombadil, &governor); + + let deposit_amount = 1000; + votes_client.mint(&user1, &deposit_amount); + + votes_client.approve(&user1, &user3, &100, &200); + assert_eq!(votes_client.allowance(&user1, &user3), 100); + + votes_client.transfer_from(&user3, &user1, &user2, &-1); +} + #[test] #[should_panic(expected = "Error(Contract, #3)")] fn initialize_already_initialized_soroban() { @@ -215,3 +308,73 @@ fn initialize_already_initialized_soroban() { &String::from_str(&e, "2"), ); } + +#[test] +fn approve_invalid() { + let e = Env::default(); + e.mock_all_auths(); + e.set_default_info(); + + let bombadil = Address::generate(&e); + let user1 = Address::generate(&e); + let user2 = Address::generate(&e); + let governor = Address::generate(&e); + + let (_, votes_client) = create_soroban_token_votes_wasm(&e, &bombadil, &governor); + + let deposit_amount = 1001; + votes_client.mint(&user1, &deposit_amount); + + let result = votes_client.try_approve(&user1, &user2, &-1, &200); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(8)))); + + let result = votes_client.try_approve(&user1, &user2, &100, &(e.ledger().sequence() - 1)); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(9)))); +} + +#[test] +fn allowance_invalid() { + let e = Env::default(); + e.mock_all_auths(); + e.set_default_info(); + + let bombadil = Address::generate(&e); + let user1 = Address::generate(&e); + let user2 = Address::generate(&e); + let user3 = Address::generate(&e); + let governor = Address::generate(&e); + + let (_, votes_client) = create_soroban_token_votes_wasm(&e, &bombadil, &governor); + + let deposit_amount = 1000; + votes_client.mint(&user1, &deposit_amount); + + let exp_ledger = e.ledger().sequence() + 100; + votes_client.approve(&user1, &user2, &100, &exp_ledger); + + let result = votes_client.try_transfer_from(&user2, &user1, &user3, &101); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(9)))); + + let result = votes_client.try_burn_from(&user2, &user1, &101); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(9)))); + + e.jump(100); + + votes_client.transfer_from(&user2, &user1, &user3, &5); + assert_eq!(votes_client.allowance(&user1, &user2), 95); + assert_eq!(votes_client.balance(&user1), 995); + assert_eq!(votes_client.balance(&user3), 5); + + votes_client.burn_from(&user2, &user1, &5); + assert_eq!(votes_client.allowance(&user1, &user2), 90); + assert_eq!(votes_client.balance(&user1), 990); + assert_eq!(votes_client.total_supply(), 995); + + e.jump(1); + + let result = votes_client.try_transfer_from(&user2, &user1, &user3, &5); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(9)))); + + let result = votes_client.try_burn_from(&user2, &user1, &5); + assert_eq!(result.err(), Some(Ok(Error::from_contract_error(9)))); +} diff --git a/contracts/votes/Cargo.toml b/contracts/votes/Cargo.toml index cb24f74..00890e0 100644 --- a/contracts/votes/Cargo.toml +++ b/contracts/votes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soroban-votes" -version = "1.0.0" +version = "1.1.0" authors = ["Script3 Ltd. "] license = "MIT" edition = "2021" diff --git a/contracts/votes/src/balance.rs b/contracts/votes/src/balance.rs index d2d966b..664d58d 100644 --- a/contracts/votes/src/balance.rs +++ b/contracts/votes/src/balance.rs @@ -57,64 +57,70 @@ pub fn mint_balance(e: &Env, to: &Address, amount: i128) { /// ### Panics /// This function panics if the balance is less than the amount to remove. pub fn burn_balance(e: &Env, from: &Address, amount: i128) { - let balance = storage::get_balance(e, from); - if balance < amount { - panic_with_error!(e, TokenVotesError::BalanceError); - } + if amount > 0 { + let balance = storage::get_balance(e, from); + if balance < amount { + panic_with_error!(e, TokenVotesError::BalanceError); + } - let total_supply_checkpoint = storage::get_total_supply(e); - let (_, mut supply) = total_supply_checkpoint.to_checkpoint_data(); + let total_supply_checkpoint = storage::get_total_supply(e); + let (_, mut supply) = total_supply_checkpoint.to_checkpoint_data(); - #[cfg(feature = "bonding")] - emissions::update_emissions(e, supply, from, balance); + #[cfg(feature = "bonding")] + emissions::update_emissions(e, supply, from, balance); - supply -= amount; - if supply < 0 { - panic_with_error!(e, TokenVotesError::InsufficientVotesError); + supply -= amount; + if supply < 0 { + panic_with_error!(e, TokenVotesError::InsufficientVotesError); + } + storage::set_total_supply( + e, + &u128::from_checkpoint_data(e, e.ledger().sequence(), supply), + ); + + let vote_ledgers = storage::get_vote_ledgers(e); + add_supply_checkpoint(e, &vote_ledgers, total_supply_checkpoint); + move_voting_units( + e, + &vote_ledgers, + Some(&storage::get_delegate(e, from)), + None, + amount, + ); + + storage::set_balance(e, from, &(balance - amount)); } - storage::set_total_supply( - e, - &u128::from_checkpoint_data(e, e.ledger().sequence(), supply), - ); - - let vote_ledgers = storage::get_vote_ledgers(e); - add_supply_checkpoint(e, &vote_ledgers, total_supply_checkpoint); - move_voting_units( - e, - &vote_ledgers, - Some(&storage::get_delegate(e, from)), - None, - amount, - ); - - storage::set_balance(e, from, &(balance - amount)); } #[cfg(feature = "sep-0041")] pub fn transfer_balance(e: &Env, from: &Address, to: &Address, amount: i128) { - let from_balance = storage::get_balance(e, from); - if from_balance < amount { - panic_with_error!(e, TokenVotesError::BalanceError); - } - let to_balance = storage::get_balance(e, to); + if amount > 0 { + let from_balance = storage::get_balance(e, from); + if from_balance < amount { + panic_with_error!(e, TokenVotesError::BalanceError); + } + storage::set_balance(e, from, &(from_balance - amount)); - #[cfg(feature = "bonding")] - { - let total_supply_checkpoint = storage::get_total_supply(e); - let (_, supply) = total_supply_checkpoint.to_checkpoint_data(); - emissions::update_emissions(e, supply, from, from_balance); - emissions::update_emissions(e, supply, to, to_balance); - } + let to_balance = storage::get_balance(e, to); + storage::set_balance(e, to, &(to_balance + amount)); + + #[cfg(feature = "bonding")] + { + let total_supply_checkpoint = storage::get_total_supply(e); + let (_, supply) = total_supply_checkpoint.to_checkpoint_data(); + emissions::update_emissions(e, supply, from, from_balance); + if from != to { + emissions::update_emissions(e, supply, to, to_balance); + } + } - let vote_ledgers = storage::get_vote_ledgers(e); - move_voting_units( - e, - &vote_ledgers, - Some(&storage::get_delegate(e, from)), - Some(&storage::get_delegate(e, to)), - amount, - ); - - storage::set_balance(e, from, &(from_balance - amount)); - storage::set_balance(e, to, &(to_balance + amount)); + let vote_ledgers = storage::get_vote_ledgers(e); + move_voting_units( + e, + &vote_ledgers, + Some(&storage::get_delegate(e, from)), + Some(&storage::get_delegate(e, to)), + amount, + ); + } } diff --git a/contracts/votes/src/checkpoints.rs b/contracts/votes/src/checkpoints.rs index 4072b9c..a90c101 100644 --- a/contracts/votes/src/checkpoints.rs +++ b/contracts/votes/src/checkpoints.rs @@ -136,12 +136,15 @@ pub fn add_vote_ledger(e: &Env, sequence: u32) { vote_ledgers = vote_ledgers.slice(index..len); } } - if let Some(last) = vote_ledgers.last() { - if last == sequence { - return; + + // insert the new vote ledger in order + match vote_ledgers.binary_search(&sequence) { + // if the sequence is already in the list, we don't need to insert it + Ok(_) => (), + Err(index) => { + vote_ledgers.insert(index, sequence); } } - vote_ledgers.push_back(sequence); storage::set_vote_ledgers(&e, &vote_ledgers); } @@ -251,7 +254,7 @@ mod tests { const DEFAULT_LEDGER_INFO: LedgerInfo = LedgerInfo { timestamp: 1441065600, protocol_version: 20, - sequence_number: 172800, + sequence_number: 20 * 17280, network_id: [0_u8; 32], base_reserve: 10, min_temp_entry_ttl: 1000, @@ -719,4 +722,71 @@ mod tests { } }); } + + #[test] + fn test_add_vote_ledger_out_of_order() { + let e = Env::default(); + e.ledger().set(DEFAULT_LEDGER_INFO); + + let ledger = DEFAULT_LEDGER_INFO.sequence_number; + + let mut vote_ledgers = Vec::::new(&e); + vote_ledgers.push_back(ledger - MAX_CHECKPOINT_AGE_LEDGERS - 1); + vote_ledgers.push_back(ledger - ONE_DAY_LEDGERS / 2); + vote_ledgers.push_back(ledger - 10); + vote_ledgers.push_back(ledger + ONE_DAY_LEDGERS); + + let votes = e.register_contract(None, TokenVotes {}); + e.as_contract(&votes, || { + storage::set_vote_ledgers(&e, &vote_ledgers); + + let to_add_sequence = ledger; + add_vote_ledger(&e, to_add_sequence); + + // perform expected change to vote_ledgers array + vote_ledgers.pop_front_unchecked(); + vote_ledgers.insert(2, to_add_sequence); + + let new_vote_ledgers = storage::get_vote_ledgers(&e); + assert_eq!(new_vote_ledgers.len(), vote_ledgers.len()); + for i in 0..vote_ledgers.len() { + assert_eq!( + new_vote_ledgers.get_unchecked(i), + vote_ledgers.get_unchecked(i) + ); + } + }); + } + + #[test] + fn test_add_vote_ledger_from_empty() { + let e = Env::default(); + e.ledger().set(DEFAULT_LEDGER_INFO); + + let ledger = DEFAULT_LEDGER_INFO.sequence_number; + + let votes = e.register_contract(None, TokenVotes {}); + e.as_contract(&votes, || { + let vote_1 = ledger + ONE_DAY_LEDGERS; + add_vote_ledger(&e, vote_1); + let vote_2 = ledger + 100; + add_vote_ledger(&e, vote_2); + let vote_3 = ledger + 2 * ONE_DAY_LEDGERS; + add_vote_ledger(&e, vote_3); + let vote_4 = ledger + 101; + add_vote_ledger(&e, vote_4); + let vote_5 = ledger + ONE_DAY_LEDGERS; + add_vote_ledger(&e, vote_5); + + let new_vote_ledgers = storage::get_vote_ledgers(&e); + let vote_ledgers = vec![&e, vote_2, vote_4, vote_1, vote_3]; + assert_eq!(new_vote_ledgers.len(), vote_ledgers.len()); + for i in 0..vote_ledgers.len() { + assert_eq!( + new_vote_ledgers.get_unchecked(i), + vote_ledgers.get_unchecked(i) + ); + } + }); + } } diff --git a/contracts/votes/src/constants.rs b/contracts/votes/src/constants.rs index 7715060..4d872d0 100644 --- a/contracts/votes/src/constants.rs +++ b/contracts/votes/src/constants.rs @@ -1,8 +1,11 @@ pub(crate) const ONE_DAY_LEDGERS: u32 = 17280; // assumes 5s a ledger /// The maximum number of ledgers a checkpoint needs to exist for. Once a checkpoint is written, that means -/// a voting period has already started, and the max voting period is 7 days worth of ledgers. -pub(crate) const MAX_CHECKPOINT_AGE_LEDGERS: u32 = 8 * ONE_DAY_LEDGERS; +/// a voting period has already started. The longest a checkpoint will be needed is the max `vote_period` and +/// `grace_period` combined, which is 14 days. We add 1 day for buffer. +/// +/// See: https://github.com/script3/soroban-governor/blob/main/contracts/governor/src/constants.rs +pub(crate) const MAX_CHECKPOINT_AGE_LEDGERS: u32 = 15 * ONE_DAY_LEDGERS; /// The maximum number of ledgers a proposal can exist for. pub(crate) const MAX_PROPOSAL_AGE_LEDGERS: u32 = 31 * ONE_DAY_LEDGERS; diff --git a/contracts/votes/src/contract.rs b/contracts/votes/src/contract.rs index d548547..b12ab07 100644 --- a/contracts/votes/src/contract.rs +++ b/contracts/votes/src/contract.rs @@ -53,7 +53,11 @@ pub struct TokenVotes; impl Token for TokenVotes { fn allowance(e: Env, from: Address, spender: Address) -> i128 { let result = storage::get_allowance(&e, &from, &spender); - result.amount + if e.ledger().sequence() > result.expiration_ledger { + 0 + } else { + result.amount + } } fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) { diff --git a/contracts/votes/src/emissions.rs b/contracts/votes/src/emissions.rs index 75b717b..cbb3d4a 100644 --- a/contracts/votes/src/emissions.rs +++ b/contracts/votes/src/emissions.rs @@ -76,12 +76,12 @@ pub fn update_emissions(e: &Env, total_supply: i128, user: &Address, balance: i1 } } -/// Update the emissions for a balance change +/// Set the emissions for the vote token /// /// ### Arguments /// * `total_supply` - The total supply of the vote token -/// * `new_tokens` - The address of the user -/// * `balance` - The balance of the user +/// * `new_tokens` - The number of tokens being emitted +/// * `new_expiration` - The time the emission will expire pub fn set_emissions(e: &Env, total_supply: i128, new_tokens: i128, new_expiration: u64) { if new_expiration <= e.ledger().timestamp() || new_tokens <= 0 { panic_with_error!(e, TokenVotesError::InvalidEmissionConfigError); @@ -130,9 +130,11 @@ pub fn set_emissions(e: &Env, total_supply: i128, new_tokens: i128, new_expirati TokenVotesEvents::set_emissions(e, eps, new_expiration); } -/// Update the backstop emissions index for deposits +/// Update the emission data /// /// ### Arguments +/// * `emis_data` - The current emission data +/// * `emis_config` - The emission config /// * `total_supply` - The total supply of the vote token /// /// ### Returns @@ -172,8 +174,8 @@ fn update_emission_data( /// Update the user's emissions /// /// ### Arguments -/// * `user` - The address of the user -/// * `emis_data` - The emission data +/// * `user_data_opt` - The user data, or None if it does not exist +/// * `emis_data` - The emission data of the token /// * `balance` - The balance of the user /// /// ### Returns diff --git a/contracts/votes/src/events.rs b/contracts/votes/src/events.rs index 4395b21..3bb53f9 100644 --- a/contracts/votes/src/events.rs +++ b/contracts/votes/src/events.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{symbol_short, Address, Env, Symbol}; +use soroban_sdk::{Address, Env, Symbol}; pub struct TokenVotesEvents {} @@ -7,9 +7,9 @@ impl TokenVotesEvents { /// /// - topics - `["delegate", delegator: Address, delegatee: Address]` /// - data - `[old_delegatee: Address]` - pub fn delegate(env: &Env, delegator: Address, delegatee: Address, old_delegatee: Address) { - let topics = (symbol_short!("delegate"), delegator, delegatee); - env.events().publish(topics, old_delegatee); + pub fn delegate(e: &Env, delegator: Address, delegatee: Address, old_delegatee: Address) { + let topics = (Symbol::new(&e, "delegate"), delegator, delegatee); + e.events().publish(topics, old_delegatee); } /// Emitted when a delagate's votes are changed