diff --git a/artifacts/broker_bank.wasm b/artifacts/broker_bank.wasm index e26970c..405c9fe 100644 Binary files a/artifacts/broker_bank.wasm and b/artifacts/broker_bank.wasm differ diff --git a/artifacts/broker_staking.wasm b/artifacts/broker_staking.wasm index 976aecc..1e9475d 100644 Binary files a/artifacts/broker_staking.wasm and b/artifacts/broker_staking.wasm differ diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt index 162ea1b..c999a17 100644 --- a/artifacts/checksums.txt +++ b/artifacts/checksums.txt @@ -1,7 +1,6 @@ -3878c8fec84001d1260da6d8b7ea91af5bc3d3433d8cab293ae24de94a9fdda0 broker_bank.wasm -dfd48116b2181f477d42b7d0d697057e16814c0b703ef592a8757ca681c6e5ad broker_staking.wasm +e3d3422db32e4e6ebe0247a22f6be733ccf6b637bfeb4ff8ffcd8122a68d7853 broker_bank.wasm +40288f4cda1e48740269ab6425ca234b9b99419c165aaf1d410fa37c40843e49 broker_staking.wasm 382c05baf544f2886de849933ecf59e8bc3bcdcdd552d5a63537bd6d63f2ecf1 controller.wasm -05948cad982935aa30a9c2011649ab40a38172ab50df5781a3e24f72b76dcc56 core_compounder.wasm ed4a89ae4669b22863fcabd18e3bd7e40d039899f863a07eb0449eed059a898e core_token_vesting_v2.wasm b56a880d4c67d9f353f549b502256f73159f89b50aa6dae683948e117efa4792 cw3_flex_multisig.wasm 1ecff403bbf3b5fcedccb5de76a0ef5f1fdbcc5f60890e3388f5425584899f0b incentives.wasm diff --git a/contracts/broker-staking/src/contract.rs b/contracts/broker-staking/src/contract.rs index a15ae9e..0bdfa74 100644 --- a/contracts/broker-staking/src/contract.rs +++ b/contracts/broker-staking/src/contract.rs @@ -7,8 +7,8 @@ use broker_bank::state::{IS_HALTED, OPERATORS, TO_ADDRS}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - Response, StakingMsg, StdResult, Uint128, + to_json_binary, Binary, Coin, CosmosMsg, Deps, DepsMut, DistributionMsg, + Env, MessageInfo, Response, StakingMsg, StdResult, Uint128, }; use crate::msg::{ExecuteMsg, StakeMsg, UnstakeMsg}; @@ -59,9 +59,38 @@ pub fn execute( ExecuteMsg::WithdrawAll { to } => { withdraw_all(deps, env, info, to, contract_addr) } + ExecuteMsg::ClaimRewards {} => claim_rewards(deps, env, info), } } +pub fn claim_rewards( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> Result { + let is_halted = IS_HALTED.load(deps.storage)?; + assert_not_halted(is_halted)?; + + Permissions::assert_operator(deps.storage, info.sender.to_string())?; + + // query current delegations + let delegations = + deps.querier.query_all_delegations(&env.contract.address)?; + + let mut messages: Vec = vec![]; + for delegation in delegations.iter() { + messages.push(CosmosMsg::Distribution( + DistributionMsg::WithdrawDelegatorReward { + validator: delegation.validator.clone(), + }, + )); + } + + Ok(Response::new() + .add_messages(messages) + .add_attribute("action", "claim_rewards")) +} + pub fn unstake( deps: DepsMut, _env: Env, diff --git a/contracts/broker-staking/src/msg.rs b/contracts/broker-staking/src/msg.rs index fa4a66a..1505339 100644 --- a/contracts/broker-staking/src/msg.rs +++ b/contracts/broker-staking/src/msg.rs @@ -31,6 +31,8 @@ pub enum ExecuteMsg { to: Option, }, + ClaimRewards {}, + /// Unstake allows to unstake a given amount of tokens from a set of /// validators. The UnstakeMsgs defines the tokens amount and address /// of the validator. diff --git a/contracts/broker-staking/src/testing.rs b/contracts/broker-staking/src/testing.rs index 815a178..abfa970 100644 --- a/contracts/broker-staking/src/testing.rs +++ b/contracts/broker-staking/src/testing.rs @@ -4,8 +4,8 @@ use crate::contract::{execute, query}; use crate::msg::{ExecuteMsg, StakeMsg, UnstakeMsg}; use cosmwasm_std::{self as cw_std}; use cw_std::{ - coin, from_json, testing, BankMsg, Coin, CosmosMsg, Response, StakingMsg, - Uint128, + coin, from_json, testing, BankMsg, Coin, CosmosMsg, DistributionMsg, + Response, StakingMsg, Uint128, }; use nibiru_std::errors::TestResult; use serde::Serialize; @@ -752,3 +752,80 @@ fn exec_unstake() -> TestResult { } Ok(()) } + +#[test] +fn test_withdraw_rewards() -> TestResult { + let to_addrs: [String; 2] = ["mm_kucoin", "mm_bybit"].map(|s| s.to_string()); + let opers: [String; 1] = ["valid_oper"].map(|s| s.to_string()); + let test_cases: Vec = vec![ + // Success + TestCaseExec { + to_addrs: to_addrs.to_vec(), + opers: opers.to_vec(), + sender: "owner", + exec_msg: ExecuteMsg::ClaimRewards {}, + err: None, + contract_funds_start: None, + resp_msgs: vec![], + }, + // Success - oper can do that + TestCaseExec { + to_addrs: to_addrs.to_vec(), + opers: opers.to_vec(), + sender: "valid_oper", + exec_msg: ExecuteMsg::ClaimRewards {}, + err: None, + contract_funds_start: None, + resp_msgs: vec![], + }, + // Fail - non oper can't + TestCaseExec { + to_addrs: to_addrs.to_vec(), + opers: opers.to_vec(), + sender: "invalid_oper", + exec_msg: ExecuteMsg::ClaimRewards {}, + err: Some("insufficient permissions"), + contract_funds_start: None, + resp_msgs: vec![], + }, + ]; + for tc in &test_cases { + let to_addrs = &tc.to_addrs; + let opers = &tc.opers; + // instantiate smart contract from the owner + let (mut deps, env, _info) = + setup_contract(to_addrs.clone(), opers.clone())?; + + if let Some(funds_start) = &tc.contract_funds_start { + // Set up a mock querier with contract balance + let contract_addr = env.contract.address.to_string(); + let balances: &[(&str, &[Coin])] = + &[(contract_addr.as_str(), funds_start.as_slice())]; + let querier = testing::MockQuerier::new(balances); + deps.querier = querier; + } + + // send the exec msg + let info = mock_info_for_sender(tc.sender); + let res = execute(deps.as_mut(), env, info, tc.exec_msg.clone()); + + if let Some(want_err) = tc.err { + let got_err = res.expect_err("errors should occur in this test"); + let is_contained = got_err.to_string().contains(want_err); + assert!(is_contained, "got error {}", got_err); + return Ok(()); + } + assert!(res.is_ok(), "got {res:?}"); + + let resp = res?; + let got_resp_msgs: Vec = resp + .messages + .iter() + .map(|sub_msg| CosmosMsgExt(&sub_msg.msg)) + .collect(); + let want_resp_msgs: Vec = + tc.resp_msgs.iter().map(CosmosMsgExt).collect(); + assert_eq!(want_resp_msgs, got_resp_msgs); + } + Ok(()) +}