Repository: DA0-DA0/dao-contracts
Pull request #282 (2022-05-03 03:39:06)

Remove errors and make update config force a distributions stake-cw20-reward-distributor
This PR includes a bunch of random fixs for the rewards distribution contract. 

## Remove Errors

This PR remove `out_of_funds` and `no_pending_payments`. Instead of pushing an error the contract will just not distribute any funds, but still update the `last_payment_block` variable to the current block. This imo is a better behavior for when the contract is out of funds. With these errors, when the contract is refunded it will have to pay out for all the previous blocks. 

## Distribution when updating config

This PR makes it so a distribution happens before config is updated. I feels updating the config should not affect blocks in the past. In current implementation if I update the config and a distribution hasn't occured for a while, when I update the config these undistributed past blocks will be paid at the new updated rate.

## Improve Response Attributes

This PR also improves the attributes added to each response. 

## Test for dao deployment strategy

Added a new test that runs through the sequence of how a DAO will deploy this contract.


Patch:
diff --git a/contracts/stake-cw20-reward-distributor/src/contract.rs b/contracts/stake-cw20-reward-distributor/src/contract.rs
index b01a817b..a3facc88 100644
--- a/contracts/stake-cw20-reward-distributor/src/contract.rs
+++ b/contracts/stake-cw20-reward-distributor/src/contract.rs
@@ -34,9 +34,9 @@ pub fn instantiate(
     }
 
     let config = Config {
-        owner,
-        staking_addr,
-        reward_token,
+        owner: owner.clone(),
+        staking_addr: staking_addr.clone(),
+        reward_token: reward_token.clone(),
         reward_rate: msg.reward_rate,
     };
     CONFIG.save(deps.storage, &config)?;
@@ -44,7 +44,12 @@ pub fn instantiate(
     // Initialize last payment block
     LAST_PAYMENT_BLOCK.save(deps.storage, &env.block.height)?;
 
-    Ok(Response::new().add_attribute("action", "instantiate"))
+    Ok(Response::new()
+        .add_attribute("action", "instantiate")
+        .add_attribute("owner", owner.into_string())
+        .add_attribute("staking_addr", staking_addr.into_string())
+        .add_attribute("reward_token", reward_token.into_string())
+        .add_attribute("reward_rate", msg.reward_rate))
 }
 
 #[cfg_attr(not(feature = "library"), entry_point)]
@@ -60,7 +65,15 @@ pub fn execute(
             staking_addr,
             reward_rate,
             reward_token,
-        } => execute_update_config(deps, info, owner, staking_addr, reward_rate, reward_token),
+        } => execute_update_config(
+            deps,
+            info,
+            env,
+            owner,
+            staking_addr,
+            reward_rate,
+            reward_token,
+        ),
         ExecuteMsg::Distribute {} => execute_distribute(deps, env),
         ExecuteMsg::Withdraw {} => execute_withdraw(deps, info, env),
     }
@@ -69,6 +82,7 @@ pub fn execute(
 pub fn execute_update_config(
     deps: DepsMut,
     info: MessageInfo,
+    env: Env,
     owner: String,
     staking_addr: String,
     reward_rate: Uint128,
@@ -79,6 +93,9 @@ pub fn execute_update_config(
         return Err(ContractError::Unauthorized {});
     }
 
+    let distribution_msg = get_distribution_msg(deps.as_ref(), &env)?;
+    LAST_PAYMENT_BLOCK.save(deps.storage, &env.block.height)?;
+
     let owner = deps.api.addr_validate(&owner)?;
     let staking_addr = deps.api.addr_validate(&staking_addr)?;
     if !validate_staking(deps.as_ref(), staking_addr.clone()) {
@@ -91,13 +108,20 @@ pub fn execute_update_config(
     }
 
     let config = Config {
-        owner,
-        staking_addr,
-        reward_token,
+        owner: owner.clone(),
+        staking_addr: staking_addr.clone(),
+        reward_token: reward_token.clone(),
         reward_rate,
     };
     CONFIG.save(deps.storage, &config)?;
-    Ok(Response::default())
+
+    Ok(Response::new()
+        .add_messages(distribution_msg)
+        .add_attribute("action", "update_config")
+        .add_attribute("owner", owner.into_string())
+        .add_attribute("staking_addr", staking_addr.into_string())
+        .add_attribute("reward_token", reward_token.into_string())
+        .add_attribute("reward_rate", reward_rate))
 }
 
 pub fn validate_cw20(deps: Deps, cw20_addr: Addr) -> bool {
@@ -116,16 +140,12 @@ pub fn validate_staking(deps: Deps, staking_addr: Addr) -> bool {
     response.is_ok()
 }
 
-pub fn execute_distribute(deps: DepsMut, env: Env) -> Result<Response, ContractError> {
+fn get_distribution_msg(deps: Deps, env: &Env) -> Result<Option<CosmosMsg>, ContractError> {
     let config = CONFIG.load(deps.storage)?;
     let last_payment_block = LAST_PAYMENT_BLOCK.load(deps.storage)?;
     let block_diff = env.block.height - last_payment_block;
     let pending_rewards: Uint128 = config.reward_rate * Uint128::new(block_diff.into());
 
-    if pending_rewards == Uint128::zero() {
-        return Err(ContractError::NoPendingPayments {});
-    }
-
     let balance_info: cw20::BalanceResponse = deps.querier.query_wasm_smart(
         config.reward_token.clone(),
         &cw20::Cw20QueryMsg::Balance {
@@ -133,12 +153,11 @@ pub fn execute_distribute(deps: DepsMut, env: Env) -> Result<Response, ContractE
         },
     )?;
 
-    if balance_info.balance == Uint128::zero() {
-        return Err(ContractError::OutOfFunds {});
-    }
-
     let amount = min(balance_info.balance, pending_rewards);
-    LAST_PAYMENT_BLOCK.save(deps.storage, &env.block.height)?;
+
+    if amount == Uint128::zero() {
+        return Ok(None);
+    }
 
     let msg = to_binary(&cw20::Cw20ExecuteMsg::Send {
         contract: config.staking_addr.clone().into_string(),
@@ -151,7 +170,16 @@ pub fn execute_distribute(deps: DepsMut, env: Env) -> Result<Response, ContractE
         funds: vec![],
     }
     .into();
-    Ok(Response::default().add_message(send_msg))
+
+    Ok(Some(send_msg))
+}
+
+pub fn execute_distribute(deps: DepsMut, env: Env) -> Result<Response, ContractError> {
+    let msg = get_distribution_msg(deps.as_ref(), &env)?;
+    LAST_PAYMENT_BLOCK.save(deps.storage, &env.block.height)?;
+    Ok(Response::new()
+        .add_messages(msg)
+        .add_attribute("action", "distribute"))
 }
 
 pub fn execute_withdraw(
@@ -172,7 +200,7 @@ pub fn execute_withdraw(
     )?;
 
     let msg = to_binary(&cw20::Cw20ExecuteMsg::Transfer {
-        recipient: config.owner.into(),
+        recipient: config.owner.clone().into(),
         amount: balance_info.balance,
     })?;
     let send_msg: CosmosMsg = WasmMsg::Execute {
@@ -182,7 +210,11 @@ pub fn execute_withdraw(
     }
     .into();
 
-    Ok(Response::new().add_message(send_msg))
+    Ok(Response::new()
+        .add_message(send_msg)
+        .add_attribute("action", "withdraw")
+        .add_attribute("amount", balance_info.balance)
+        .add_attribute("recipient", config.owner.into_string()))
 }
 
 #[cfg_attr(not(feature = "library"), entry_point)]
diff --git a/contracts/stake-cw20-reward-distributor/src/error.rs b/contracts/stake-cw20-reward-distributor/src/error.rs
index 198dc1de..44fdc288 100644
--- a/contracts/stake-cw20-reward-distributor/src/error.rs
+++ b/contracts/stake-cw20-reward-distributor/src/error.rs
@@ -9,12 +9,6 @@ pub enum ContractError {
     #[error("Unauthorized")]
     Unauthorized {},
 
-    #[error("No Pending Payments")]
-    NoPendingPayments {},
-
-    #[error("Out of Funds")]
-    OutOfFunds {},
-
     #[error("Invalid Cw20")]
     InvalidCw20 {},
 


Test patch:
diff --git a/contracts/stake-cw20-reward-distributor/src/tests.rs b/contracts/stake-cw20-reward-distributor/src/tests.rs
index 5ae7a07c..55ab6bfd 100644
--- a/contracts/stake-cw20-reward-distributor/src/tests.rs
+++ b/contracts/stake-cw20-reward-distributor/src/tests.rs
@@ -262,6 +262,24 @@ fn test_distribute() {
     )
     .unwrap();
 
+    let staking_balance = get_balance_cw20(&app, cw20_addr.clone(), staking_addr.clone());
+    assert_eq!(staking_balance, Uint128::new(1000));
+
+    let distributor_info = get_info(&app, distributor_addr.clone());
+    assert_eq!(distributor_info.balance, Uint128::new(0));
+    assert_eq!(distributor_info.last_payment_block, app.block_info().height);
+
+    // Pays out nothing
+    app.update_block(|mut block| block.height += 1100);
+    let _err = app
+        .execute_contract(
+            Addr::unchecked(OWNER),
+            distributor_addr.clone(),
+            &ExecuteMsg::Distribute {},
+            &[],
+        )
+        .unwrap();
+
     let staking_balance = get_balance_cw20(&app, cw20_addr, staking_addr);
     assert_eq!(staking_balance, Uint128::new(1000));
 
@@ -438,3 +456,62 @@ fn test_withdraw() {
     let owner_balance = get_balance_cw20(&app, cw20_addr, Addr::unchecked(OWNER));
     assert_eq!(owner_balance, Uint128::new(990));
 }
+
+#[test]
+fn test_dao_deploy() {
+    // DAOs will deploy this contract with following steps
+    // Contract is instantiated by any address with 0 reward rate
+    // Dao updates reward rate and funds in same transaction
+    let mut app = App::default();
+
+    let cw20_addr = instantiate_cw20(
+        &mut app,
+        vec![cw20::Cw20Coin {
+            address: OWNER.to_string(),
+            amount: Uint128::from(1000u64),
+        }],
+    );
+    let staking_addr = instantiate_staking(&mut app, cw20_addr.clone());
+
+    let msg = InstantiateMsg {
+        owner: OWNER.to_string(),
+        staking_addr: staking_addr.to_string(),
+        reward_rate: Uint128::new(0),
+        reward_token: cw20_addr.to_string(),
+    };
+    let distributor_addr = instantiate_distributor(&mut app, msg);
+
+    app.update_block(|mut block| block.height += 1000);
+
+    let msg = ExecuteMsg::UpdateConfig {
+        owner: OWNER.to_string(),
+        staking_addr: staking_addr.to_string(),
+        reward_rate: Uint128::new(1),
+        reward_token: cw20_addr.to_string(),
+    };
+    app.execute_contract(Addr::unchecked(OWNER), distributor_addr.clone(), &msg, &[])
+        .unwrap();
+
+    let msg = cw20::Cw20ExecuteMsg::Transfer {
+        recipient: distributor_addr.to_string(),
+        amount: Uint128::from(1000u128),
+    };
+    app.execute_contract(Addr::unchecked(OWNER), cw20_addr.clone(), &msg, &[])
+        .unwrap();
+
+    app.update_block(|mut block| block.height += 10);
+    app.execute_contract(
+        Addr::unchecked(OWNER),
+        distributor_addr.clone(),
+        &ExecuteMsg::Distribute {},
+        &[],
+    )
+    .unwrap();
+
+    let staking_balance = get_balance_cw20(&app, cw20_addr, staking_addr);
+    assert_eq!(staking_balance, Uint128::new(10));
+
+    let distributor_info = get_info(&app, distributor_addr);
+    assert_eq!(distributor_info.balance, Uint128::new(990));
+    assert_eq!(distributor_info.last_payment_block, app.block_info().height);
+}