Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PauseDAO, closes #146 #155

Merged
merged 7 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# DAO DAO Contracts

| Contract | Description |
| :------------------------------------------- | :------------------------------------------------------------------|
| [cw-distribution](contracts/cw-distribution) | A contract for distributing staking rewards. |
| [cw3-dao](contracts/cw3-dao) | A DAO with voting power based on staked governance tokens. |
| [cw3-multisig](contracts/cw3-multisig) | A multisig contract. |
| [cw4-registry](contracts/cw4-registry) | A contract for indexing multisig group members. |
| [stake-cw20](contracts/stake-cw20) | A cw20 staking contract. |
| [stake-cw20-gov](contracts/stake-cw20-gov) | A cw20 staking contract, with vote delegation (used by `cw3-dao`). |
| Contract | Description |
| :------------------------------------------- | :--------------------------------------------------------- |
| [cw-distribution](contracts/cw-distribution) | A contract for distributing staking rewards. |
| [cw3-dao](contracts/cw3-dao) | A DAO with voting power based on staked governance tokens. |
| [cw3-multisig](contracts/cw3-multisig) | A multisig contract. |
| [cw4-registry](contracts/cw4-registry) | A contract for indexing multisig group members. |
| [stake-cw20](contracts/stake-cw20) | A cw20 staking contract. |
| [stake-cw20-gov](contracts/stake-cw20-gov) | A cw20 staking contract, with vote delegation. |

NOTE: _These contracts have yet to be audited. Please see the [disclaimer](#Disclaimer)._

Expand Down
42 changes: 42 additions & 0 deletions contracts/cw3-dao/schema/execute_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,27 @@
},
"additionalProperties": false
},
{
"description": "Pauses DAO governance (can only be called by DAO contract)",
"type": "object",
"required": [
"pause_d_a_o"
],
"properties": {
"pause_d_a_o": {
"type": "object",
"required": [
"expiration"
],
"properties": {
"expiration": {
"$ref": "#/definitions/Expiration"
}
}
}
},
"additionalProperties": false
},
{
"description": "Update DAO config (can only be called by DAO contract)",
"type": "object",
Expand Down Expand Up @@ -117,6 +138,27 @@
}
},
"additionalProperties": false
},
{
"description": "Update Staking Contract (can only be called by DAO contract) WARNING: this changes the contract controlling voting",
"type": "object",
"required": [
"update_staking_contract"
],
"properties": {
"update_staking_contract": {
"type": "object",
"required": [
"new_staking_contract"
],
"properties": {
"new_staking_contract": {
"$ref": "#/definitions/Addr"
}
}
}
},
"additionalProperties": false
}
],
"definitions": {
Expand Down
29 changes: 28 additions & 1 deletion contracts/cw3-dao/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::query::{
VoteTallyResponse, VoterResponse,
};
use crate::state::{
next_id, Ballot, Config, Proposal, Votes, BALLOTS, CONFIG, GOV_TOKEN, PROPOSALS,
next_id, Ballot, Config, Proposal, Votes, BALLOTS, CONFIG, DAO_PAUSED, GOV_TOKEN, PROPOSALS,
STAKING_CONTRACT, STAKING_CONTRACT_CODE_ID, STAKING_CONTRACT_UNSTAKING_DURATION,
TREASURY_TOKENS,
};
Expand Down Expand Up @@ -174,6 +174,7 @@ pub fn execute(
}
ExecuteMsg::Execute { proposal_id } => execute_execute(deps, env, info, proposal_id),
ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id),
ExecuteMsg::PauseDAO { expiration } => execute_pause_dao(deps, env, info, expiration),
ExecuteMsg::UpdateConfig(config) => execute_update_config(deps, env, info, config),
ExecuteMsg::UpdateCw20TokenList { to_add, to_remove } => {
execute_update_cw20_token_list(deps, env, info, to_add, to_remove)
Expand All @@ -194,6 +195,14 @@ pub fn execute_propose(
// we ignore earliest
latest: Option<Expiration>,
) -> Result<Response<Empty>, ContractError> {
// Check if DAO is Paused
let paused = DAO_PAUSED.may_load(deps.storage)?;
if let Some(expiration) = paused {
if !expiration.is_expired(&env.block) {
return Err(ContractError::Paused {});
}
}

let cfg = CONFIG.load(deps.storage)?;
let gov_token = GOV_TOKEN.load(deps.storage)?;

Expand Down Expand Up @@ -367,6 +376,24 @@ pub fn execute_close(
.add_attribute("proposal_id", proposal_id.to_string()))
}

pub fn execute_pause_dao(
deps: DepsMut,
env: Env,
info: MessageInfo,
expiration: Expiration,
) -> Result<Response<Empty>, ContractError> {
// Only contract can call this method
if env.contract.address != info.sender {
return Err(ContractError::Unauthorized {});
}

DAO_PAUSED.save(deps.storage, &expiration)?;

Ok(Response::new()
.add_attribute("action", "pause_dao")
.add_attribute("expiration", expiration.to_string()))
}

pub fn execute_update_config(
deps: DepsMut,
env: Env,
Expand Down
5 changes: 4 additions & 1 deletion contracts/cw3-dao/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub enum ContractError {
#[error("Already voted on this proposal")]
AlreadyVoted {},

#[error("Proposal must have passed and not yet been executed")]
#[error("Cannot execute completed or unpassed proposals")]
WrongExecuteStatus {},

#[error("Cannot close completed or passed proposals")]
Expand All @@ -50,4 +50,7 @@ pub enum ContractError {

#[error("Request size ({size}) is above limit of ({max})")]
OversizedRequest { size: u64, max: u64 },

#[error("DAO is paused")]
Paused {},
}
2 changes: 1 addition & 1 deletion contracts/cw3-dao/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub fn get_voting_power_at_height(deps: Deps, address: Addr, height: u64) -> Std
// Get voting power at height
let balance: VotingPowerAtHeightResponse = deps.querier.query_wasm_smart(
staking_contract,
&StakingContractQueryMsg::VotingPowerAtHeight {
&StakingContractQueryMsg::StakedBalanceAtHeight {
address: address.to_string(),
height: Some(height),
},
Expand Down
2 changes: 2 additions & 0 deletions contracts/cw3-dao/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ pub enum ExecuteMsg {
Execute { proposal_id: u64 },
/// Close a failed proposal
Close { proposal_id: u64 },
/// Pauses DAO governance (can only be called by DAO contract)
PauseDAO { expiration: Expiration },
/// Update DAO config (can only be called by DAO contract)
UpdateConfig(Config),
/// Updates token list
Expand Down
1 change: 1 addition & 0 deletions contracts/cw3-dao/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ pub struct Ballot {
// Unique items
pub const CONFIG: Item<Config> = Item::new("config");
pub const PROPOSAL_COUNT: Item<u64> = Item::new("proposal_count");
pub const DAO_PAUSED: Item<Expiration> = Item::new("dao_paused");

// Total weight and voters are queried from this contract
pub const STAKING_CONTRACT: Item<Addr> = Item::new("staking_contract");
Expand Down
85 changes: 85 additions & 0 deletions contracts/cw3-dao/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1858,6 +1858,91 @@ fn test_update_staking_contract() {
)
}

#[test]
fn test_pause_dao() {
let mut app = mock_app();

let voting_period = Duration::Time(2000000);
let threshold = Threshold::ThresholdQuorum {
threshold: Decimal::percent(20),
quorum: Decimal::percent(10),
};
let (dao_addr, _cw20_addr, _staking_addr) = setup_test_case(
&mut app,
threshold,
voting_period,
coins(100, NATIVE_TOKEN_DENOM),
None,
None,
);

// Pause DAO until height
const PAUSE_HEIGHT: u64 = 100000000;
let pause_dao_msg = ExecuteMsg::PauseDAO {
expiration: Expiration::AtHeight(PAUSE_HEIGHT),
};

// Nobody can call call update staking contract method directly
let res = app.execute_contract(
Addr::unchecked(VOTER1),
dao_addr.clone(),
&pause_dao_msg,
&[],
);
assert!(res.is_err());
let res = app.execute_contract(
Addr::unchecked(OWNER),
dao_addr.clone(),
&pause_dao_msg,
&[],
);
assert!(res.is_err());

let wasm_msg = WasmMsg::Execute {
contract_addr: dao_addr.clone().into(),
msg: to_binary(&pause_dao_msg).unwrap(),
funds: vec![],
};

// Pause DAO proposal must be made
let proposal_msg = ExecuteMsg::Propose(ProposeMsg {
title: String::from("Change params"),
description: String::from("Updates threshold and max voting params"),
msgs: vec![wasm_msg.into()],
latest: None,
});
let res = app
.execute_contract(Addr::unchecked(OWNER), dao_addr.clone(), &proposal_msg, &[])
.unwrap();
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();

// Imediately passes on yes vote
let yes_vote = ExecuteMsg::Vote(VoteMsg {
proposal_id,
vote: Vote::Yes,
});
let res = app.execute_contract(Addr::unchecked(VOTER3), dao_addr.clone(), &yes_vote, &[]);
assert!(res.is_ok());

// Execute
let execution = ExecuteMsg::Execute { proposal_id };
let res = app.execute_contract(Addr::unchecked(OWNER), dao_addr.clone(), &execution, &[]);
assert!(res.is_ok());

// Check that DAO is paused, Propose should fail
let res = app
.execute_contract(Addr::unchecked(OWNER), dao_addr.clone(), &proposal_msg, &[])
.unwrap_err();
assert_eq!(res.to_string(), ContractError::Paused {}.to_string());

// Pause expiration height is reached
app.update_block(|b| b.height = PAUSE_HEIGHT + 1);

// Propose succeeds
let res = app.execute_contract(Addr::unchecked(OWNER), dao_addr, &proposal_msg, &[]);
assert!(res.is_ok());
}

#[test]
fn test_config_query() {
let mut app = mock_app();
Expand Down
10 changes: 10 additions & 0 deletions types/contracts/cw3-dao/execute_msg.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ proposal_id: number
[k: string]: unknown
}
} | {
pause_d_a_o: {
expiration: Expiration
[k: string]: unknown
}
} | {
update_config: Config
} | {
update_cw20_token_list: {
to_add: Addr[]
to_remove: Addr[]
[k: string]: unknown
}
} | {
update_staking_contract: {
new_staking_contract: Addr
[k: string]: unknown
}
})

export interface ProposeMsg {
Expand Down
24 changes: 24 additions & 0 deletions types/contracts/stake-cw20-gov/execute_msg.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,30 @@ delegate_votes: {
recipient: string
[k: string]: unknown
}
} | {
update_config: {
admin: Addr
duration?: (Duration | null)
[k: string]: unknown
}
})
/**
* A human readable address.
*
* In Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.
*
* This type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.
*
* This type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.
*/
export type Addr = string
/**
* Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined
*/
export type Duration = ({
height: number
} | {
time: number
})

/**
Expand Down
2 changes: 1 addition & 1 deletion types/contracts/stake-cw20-gov/query_msg.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ height?: (number | null)
[k: string]: unknown
}
} | {
unstaking_duration: {
get_config: {
[k: string]: unknown
}
} | {
Expand Down
Loading