Skip to content

Commit

Permalink
unit tests wip
Browse files Browse the repository at this point in the history
  • Loading branch information
bekauz committed Nov 17, 2023
1 parent 6f46e8a commit c9e8cdf
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 34 deletions.
27 changes: 14 additions & 13 deletions contracts/proposal/dao-proposal-single/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ pub fn instantiate(
.pre_propose_info
.into_initial_policy_and_messages(dao.clone())?;

// TODO: validate timelock?

let config = Config {
threshold: msg.threshold,
max_voting_period,
Expand Down Expand Up @@ -319,7 +317,9 @@ pub fn execute_veto(

// vetoer can veto the proposal iff the timelock is active/not expired
if expiration.is_expired(&env.block) {
return Err(ContractError::TimelockError(TimelockError::TimelockExpired { }))
return Err(ContractError::TimelockError(
TimelockError::TimelockExpired {},
));
}

let old_status = prop.status;
Expand Down Expand Up @@ -375,6 +375,7 @@ pub fn execute_execute(
.ok_or(ContractError::NoSuchProposal { id: proposal_id })?;

let config = CONFIG.load(deps.storage)?;
// TODO: add exception for vetoer execution
if config.only_members_execute {
let power = get_voting_power(
deps.as_ref(),
Expand All @@ -387,23 +388,23 @@ pub fn execute_execute(
}
}

// Check here that the proposal is passed. Allow it to be executed
// even if it is expired so long as it passed during its voting
// period.
// Check here that the proposal is passed or timelocked.
// Allow it to be executed even if it is expired so long
// as it passed during its voting period.
prop.update_status(&env.block);
let old_status = prop.status;
match prop.status {
Status::Passed => (),
Status::Timelocked { expiration } => {
if let Some(ref timelock) = prop.timelock {
// Check if the sender is the vetoer
if timelock.check_is_vetoer(&info).is_ok() {
// check if they can execute early
timelock.check_early_execute_enabled()?;
} else if !expiration.is_expired(&env.block) {
// anyone can execute the proposal iff timelock
// expiration is due. otherwise we error.
return Err(ContractError::TimelockError(TimelockError::Timelocked {}))
match timelock.vetoer == info.sender {
// if sender is the vetoer we validate the early exec flag
true => timelock.check_early_execute_enabled()?,
// otherwise timelock must be expired in order to execute
false => if !expiration.is_expired(&env.block) {
return Err(ContractError::TimelockError(TimelockError::Timelocked {}));
}
}
}
}
Expand Down
255 changes: 251 additions & 4 deletions contracts/proposal/dao-proposal-single/src/testing/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ fn test_proposal_message_timelock_execution() {
let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno");
assert_eq!(cw20_balance, Uint128::zero());
assert_eq!(native_balance, Uint128::zero());

vote_on_proposal(
&mut app,
&proposal_module,
Expand All @@ -448,7 +448,7 @@ fn test_proposal_message_timelock_execution() {
Vote::Yes,
);
let proposal = query_proposal(&app, &proposal_module, proposal_id);

// Proposal is timelocked to the moment of prop passing + timelock delay
assert_eq!(
proposal.proposal.status,
Expand All @@ -459,8 +459,8 @@ fn test_proposal_message_timelock_execution() {

mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno"));

// Test even oversite can't execute when Timelocked and early execute is
// not enabled.
// vetoer can't execute when timelock is active and
// early execute not enabled.
let err: ContractError = app
.execute_contract(
Addr::unchecked("oversight"),
Expand Down Expand Up @@ -504,6 +504,253 @@ fn test_proposal_message_timelock_execution() {
assert_eq!(proposal.proposal.status, Status::Executed);
}

// only the authorized vetoer can veto an open proposal
#[test]
fn test_open_proposal_veto_unauthorized() {
let mut app = App::default();
let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app);
instantiate.close_proposal_on_execution_failure = false;
let timelock = Timelock {
delay: Duration::Time(100),
vetoer: "oversight".to_string(),
early_execute: false,
veto_before_passed: true,
};
instantiate.timelock = Some(timelock.clone());
let core_addr = instantiate_with_staked_balances_governance(
&mut app,
instantiate,
Some(vec![Cw20Coin {
address: CREATOR_ADDR.to_string(),
amount: Uint128::new(85),
}]),
);
let proposal_module = query_single_proposal_module(&app, &core_addr);
let gov_token = query_dao_token(&app, &core_addr);

mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000);
let proposal_id = make_proposal(
&mut app,
&proposal_module,
CREATOR_ADDR,
vec![
WasmMsg::Execute {
contract_addr: gov_token.to_string(),
msg: to_binary(&cw20::Cw20ExecuteMsg::Mint {
recipient: CREATOR_ADDR.to_string(),
amount: Uint128::new(10_000_000),
})
.unwrap(),
funds: vec![],
}
.into(),
BankMsg::Send {
to_address: CREATOR_ADDR.to_string(),
amount: coins(10, "ujuno"),
}
.into(),
],
);

// only the vetoer can veto
let err: ContractError = app
.execute_contract(
Addr::unchecked("not-oversight"),
proposal_module.clone(),
&ExecuteMsg::Veto { proposal_id },
&[],
)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ContractError::TimelockError(TimelockError::Unauthorized {})
);
}

// open proposal can only be vetoed if `veto_before_passed` flag is enabled
#[test]
fn test_open_proposal_veto_with_early_veto_flag_disabled() {
let mut app = App::default();
let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app);
instantiate.close_proposal_on_execution_failure = false;
let timelock = Timelock {
delay: Duration::Time(100),
vetoer: "oversight".to_string(),
early_execute: false,
veto_before_passed: false,
};
instantiate.timelock = Some(timelock.clone());
let core_addr = instantiate_with_staked_balances_governance(
&mut app,
instantiate,
Some(vec![Cw20Coin {
address: CREATOR_ADDR.to_string(),
amount: Uint128::new(85),
}]),
);
let proposal_module = query_single_proposal_module(&app, &core_addr);
let gov_token = query_dao_token(&app, &core_addr);

mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000);
let proposal_id = make_proposal(
&mut app,
&proposal_module,
CREATOR_ADDR,
vec![
WasmMsg::Execute {
contract_addr: gov_token.to_string(),
msg: to_binary(&cw20::Cw20ExecuteMsg::Mint {
recipient: CREATOR_ADDR.to_string(),
amount: Uint128::new(10_000_000),
})
.unwrap(),
funds: vec![],
}
.into(),
BankMsg::Send {
to_address: CREATOR_ADDR.to_string(),
amount: coins(10, "ujuno"),
}
.into(),
],
);

let err: ContractError = app
.execute_contract(
Addr::unchecked("oversight"),
proposal_module.clone(),
&ExecuteMsg::Veto { proposal_id },
&[],
)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
err,
ContractError::TimelockError(TimelockError::NoVetoBeforePassed {})
);
}

#[test]
fn test_open_proposal_veto_early() {
let mut app = App::default();
let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app);
instantiate.close_proposal_on_execution_failure = false;
let timelock = Timelock {
delay: Duration::Time(100),
vetoer: "oversight".to_string(),
early_execute: false,
veto_before_passed: true,
};
instantiate.timelock = Some(timelock.clone());
let core_addr = instantiate_with_staked_balances_governance(
&mut app,
instantiate,
Some(vec![Cw20Coin {
address: CREATOR_ADDR.to_string(),
amount: Uint128::new(85),
}]),
);
let proposal_module = query_single_proposal_module(&app, &core_addr);
let gov_token = query_dao_token(&app, &core_addr);

mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000);
let proposal_id = make_proposal(
&mut app,
&proposal_module,
CREATOR_ADDR,
vec![
WasmMsg::Execute {
contract_addr: gov_token.to_string(),
msg: to_binary(&cw20::Cw20ExecuteMsg::Mint {
recipient: CREATOR_ADDR.to_string(),
amount: Uint128::new(10_000_000),
})
.unwrap(),
funds: vec![],
}
.into(),
BankMsg::Send {
to_address: CREATOR_ADDR.to_string(),
amount: coins(10, "ujuno"),
}
.into(),
],
);

app.execute_contract(
Addr::unchecked("oversight"),
proposal_module.clone(),
&ExecuteMsg::Veto { proposal_id },
&[],
)
.unwrap();

let proposal = query_proposal(&app, &proposal_module, proposal_id);
assert_eq!(
proposal.proposal.status,
Status::Vetoed {}
);
}

// only the vetoer can veto during timelock period
#[test]
fn test_timelocked_proposal_veto_unauthorized() {
todo!()
}

// vetoer can only veto the proposal before the timelock expires
#[test]
fn test_timelocked_proposal_veto_expired_timelock() {
todo!()
}

#[test]
fn test_timelocked_proposal_veto_no_timelock_config() {
todo!()
// what
}

// vetoer can only exec timelocked prop if the early exec flag is enabled
#[test]
fn test_timelocked_proposal_execute_no_early_exec() {
todo!()
}

#[test]
fn test_timelocked_proposal_execute_early() {
todo!()
}

// only vetoer can exec timelocked prop early
#[test]
fn test_timelocked_proposal_execute_active_timelock_unauthorized() {
todo!()
}

// anyone can exec the prop after the timelock expires
#[test]
fn test_timelocked_proposal_execute_expired_timelock_not_vetoer() {
todo!()
}

#[test]
fn test_timelocked_proposal_no_votes_accepted() {
todo!()
}

#[test]
fn test_vetoed_proposal_no_votes_accepted() {
todo!()
}

#[test]
fn test_update_vetoer_address() {
todo!()
}

#[test]
fn test_proposal_message_timelock_veto() {
let mut app = App::default();
Expand Down
6 changes: 3 additions & 3 deletions packages/dao-voting/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub enum Status {
Closed,
/// The proposal's execution failed.
ExecutionFailed,
/// Proposal is timelocked and can not be until the timelock expires
/// During this time the proposal may be vetoed.
/// The proposal is timelocked. Only the configured vetoer
/// can execute or veto until the timelock expires.
Timelocked { expiration: Expiration },
/// The proposal has been vetoed.
Vetoed,
Expand All @@ -33,7 +33,7 @@ impl std::fmt::Display for Status {
Status::Executed => write!(f, "executed"),
Status::Closed => write!(f, "closed"),
Status::ExecutionFailed => write!(f, "execution_failed"),
Status::Timelocked { expiration } => write!(f, "timelocked {:?}", expiration),
Status::Timelocked { expiration } => write!(f, "timelocked_until {:?}", expiration),
Status::Vetoed => write!(f, "vetoed"),
}
}
Expand Down
16 changes: 2 additions & 14 deletions packages/dao-voting/src/timelock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{MessageInfo, StdError, Timestamp, BlockInfo};
use cw_utils::{Duration, Expiration};
use cosmwasm_std::{MessageInfo, StdError};
use cw_utils::Duration;
use thiserror::Error;

#[derive(Error, Debug, PartialEq)]
Expand Down Expand Up @@ -53,18 +53,6 @@ impl Timelock {
}
}

pub fn check_is_expired(
&self,
current_time: Timestamp,
expires: Timestamp,
) -> Result<(), TimelockError> {
if expires.seconds() > current_time.seconds() {
Ok(())
} else {
Err(TimelockError::Timelocked {})
}
}

/// Checks whether the message sender is the vetoer.
pub fn check_is_vetoer(&self, info: &MessageInfo) -> Result<(), TimelockError> {
if self.vetoer == info.sender.to_string() {
Expand Down

0 comments on commit c9e8cdf

Please sign in to comment.