Repository: DA0-DA0/dao-contracts
Pull request #154 (2022-02-01 01:25:47)

Add UpdateStakingContract method and tests
Needed to provide a migration path for DAOs when new and better staking contracts inevitably come out.


Patch:
diff --git a/contracts/cw3-dao/src/contract.rs b/contracts/cw3-dao/src/contract.rs
index 714615d9..cd1ab41b 100644
--- a/contracts/cw3-dao/src/contract.rs
+++ b/contracts/cw3-dao/src/contract.rs
@@ -178,6 +178,9 @@ pub fn execute(
         ExecuteMsg::UpdateCw20TokenList { to_add, to_remove } => {
             execute_update_cw20_token_list(deps, env, info, to_add, to_remove)
         }
+        ExecuteMsg::UpdateStakingContract {
+            new_staking_contract,
+        } => execute_update_staking_contract(deps, env, info, new_staking_contract),
     }
 }
 
@@ -384,6 +387,26 @@ pub fn execute_update_config(
         .add_attribute("sender", info.sender))
 }
 
+pub fn execute_update_staking_contract(
+    deps: DepsMut,
+    env: Env,
+    info: MessageInfo,
+    new_staking_contract: Addr,
+) -> Result<Response<Empty>, ContractError> {
+    // Only contract can call this method
+    if env.contract.address != info.sender {
+        return Err(ContractError::Unauthorized {});
+    }
+    let new_staking_contract = deps.api.addr_validate(new_staking_contract.as_str())?;
+
+    // Replace the existing staking contract
+    STAKING_CONTRACT.save(deps.storage, &new_staking_contract)?;
+
+    Ok(Response::new()
+        .add_attribute("action", "update_staking_contract")
+        .add_attribute("new_staking_contract", new_staking_contract))
+}
+
 pub fn execute_update_cw20_token_list(
     deps: DepsMut,
     env: Env,
diff --git a/contracts/cw3-dao/src/msg.rs b/contracts/cw3-dao/src/msg.rs
index e22fa7ae..29bc74ef 100644
--- a/contracts/cw3-dao/src/msg.rs
+++ b/contracts/cw3-dao/src/msg.rs
@@ -162,6 +162,9 @@ pub enum ExecuteMsg {
         to_add: Vec<Addr>,
         to_remove: Vec<Addr>,
     },
+    /// Update Staking Contract (can only be called by DAO contract)
+    /// WARNING: this changes the contract controlling voting
+    UpdateStakingContract { new_staking_contract: Addr },
 }
 
 #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]


Test patch:
diff --git a/contracts/cw3-dao/src/tests.rs b/contracts/cw3-dao/src/tests.rs
index 9d54142e..cf790b60 100644
--- a/contracts/cw3-dao/src/tests.rs
+++ b/contracts/cw3-dao/src/tests.rs
@@ -1678,7 +1678,7 @@ fn test_update_config() {
         None,
     );
 
-    // nobody can call call update contract method
+    // Nobody can call call update config method directly
     let new_threshold = Threshold::ThresholdQuorum {
         threshold: Decimal::percent(51),
         quorum: Decimal::percent(10),
@@ -1766,6 +1766,98 @@ fn test_update_config() {
     )
 }
 
+#[test]
+fn test_update_staking_contract() {
+    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.clone(),
+        voting_period,
+        coins(100, NATIVE_TOKEN_DENOM),
+        None,
+        None,
+    );
+
+    // Nobody can call call update staking contract method directly
+    let update_staking_contract_msg = ExecuteMsg::UpdateStakingContract {
+        new_staking_contract: Addr::unchecked("Better_Staking_Contract"),
+    };
+    let res = app.execute_contract(
+        Addr::unchecked(VOTER1),
+        dao_addr.clone(),
+        &update_staking_contract_msg,
+        &[],
+    );
+    assert!(res.is_err());
+    let res = app.execute_contract(
+        Addr::unchecked(OWNER),
+        dao_addr.clone(),
+        &update_staking_contract_msg,
+        &[],
+    );
+    assert!(res.is_err());
+
+    let wasm_msg = WasmMsg::Execute {
+        contract_addr: dao_addr.clone().into(),
+        msg: to_binary(&update_staking_contract_msg).unwrap(),
+        funds: vec![],
+    };
+
+    // Update config 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 config was updated
+    let res: ConfigResponse = app
+        .wrap()
+        .query_wasm_smart(&dao_addr, &QueryMsg::GetConfig {})
+        .unwrap();
+
+    assert_eq!(
+        res,
+        ConfigResponse {
+            config: Config {
+                name: "dao-dao".to_string(),
+                description: "a great DAO!".to_string(),
+                threshold,
+                max_voting_period: voting_period,
+                proposal_deposit: Uint128::zero(),
+                refund_failed_proposals: None,
+                image_url: None,
+            },
+            gov_token: cw20_addr,
+            staking_contract: Addr::unchecked("Better_Staking_Contract"),
+        }
+    )
+}
+
 #[test]
 fn test_config_query() {
     let mut app = mock_app();