diff --git a/Cargo.lock b/Cargo.lock index 87815c4d5..a9354b470 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4841,6 +4841,7 @@ dependencies = [ [[package]] name = "vending-minter-merkle-wl-featured" version = "3.13.0" + dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/Cargo.toml b/Cargo.toml index e0ea099d6..4f0ae7e42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "contracts/factories/*", "contracts/minters/*", "contracts/whitelists/*", + "contracts/splits", "test-suite/", "e2e", ] diff --git a/contracts/splits/src/contract.rs b/contracts/splits/src/contract.rs index f95a23c31..243382fa4 100644 --- a/contracts/splits/src/contract.rs +++ b/contracts/splits/src/contract.rs @@ -1,13 +1,12 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_json_binary, Addr, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, - StdResult, SubMsg, Uint128, + coins, ensure, to_json_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, + MessageInfo, Reply, Response, StdResult, SubMsg, Uint128, }; use cw2::set_contract_version; use cw4::{Cw4Contract, Member, MemberListResponse, MemberResponse}; use cw_utils::{maybe_addr, parse_reply_instantiate_data}; -use sg_std::NATIVE_DENOM; use crate::error::ContractError; use crate::msg::{ExecuteMsg, Group, InstantiateMsg, QueryMsg}; @@ -71,7 +70,9 @@ pub fn execute( ExecuteMsg::UpdateAdmin { admin } => { Ok(ADMIN.execute_update_admin(deps, info, maybe_addr(api, admin)?)?) } - ExecuteMsg::Distribute {} => execute_distribute(deps.as_ref(), env, info), + ExecuteMsg::Distribute { denom_list } => { + execute_distribute(deps.as_ref(), env, info, denom_list) + } } } @@ -79,6 +80,7 @@ pub fn execute_distribute( deps: Deps, env: Env, info: MessageInfo, + denom_list: Option>, ) -> Result { if !can_distribute(deps, info)? { return Err(ContractError::Unauthorized {}); @@ -94,32 +96,45 @@ pub fn execute_distribute( count: members_count, }); } - - let funds = deps - .querier - .query_balance(env.contract.address, NATIVE_DENOM)?; - if funds.amount.is_zero() { - return Err(ContractError::NoFunds {}); + let mut funds: Vec = Vec::new(); + if let Some(denom_list) = denom_list { + for denom in denom_list.iter() { + let balance = deps + .querier + .query_balance(env.contract.address.clone(), denom)?; + if balance.amount.is_zero() { + continue; + } + funds.push(balance); + } + } else { + funds = deps.querier.query_all_balances(env.contract.address)?; } - // To avoid rounding errors, distribute funds modulo the total weight. - // Keep remaining balance in the contract. - let multiplier = funds.amount / Uint128::from(total_weight); - if multiplier.is_zero() { - return Err(ContractError::NotEnoughFunds { min: total_weight }); - } + ensure!(!funds.is_empty(), ContractError::NoFunds {}); - let msgs = members - .iter() - .filter(|m| m.weight > 0) - .map(|member| { - let amount = multiplier * Uint128::from(member.weight); - BankMsg::Send { - to_address: member.addr.clone(), - amount: coins(amount.u128(), funds.denom.clone()), + let mut msgs: Vec = Vec::new(); + for member in members.iter().filter(|m| m.weight > 0) { + for coin in funds.iter() { + // To avoid rounding errors, distribute funds modulo the total weight. + // Keep remaining balance in the contract. + let multiplier = coin.amount / Uint128::from(total_weight); + if multiplier.is_zero() { + continue; } - }) - .collect::>(); + + let amount = Uint128::from(member.weight) * multiplier; + msgs.push(CosmosMsg::Bank(BankMsg::Send { + to_address: member.addr.clone(), + amount: coins(amount.u128(), coin.denom.clone()), + })); + } + } + + ensure!( + !msgs.is_empty(), + ContractError::NotEnoughFunds { min: total_weight } + ); Ok(Response::new() .add_attribute("action", "distribute") diff --git a/contracts/splits/src/msg.rs b/contracts/splits/src/msg.rs index 62a4ad1fa..4adb387bc 100644 --- a/contracts/splits/src/msg.rs +++ b/contracts/splits/src/msg.rs @@ -17,7 +17,7 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { UpdateAdmin { admin: Option }, - Distribute {}, + Distribute { denom_list: Option> }, } #[cw_serde] diff --git a/test-suite/src/splits/tests/integration_tests.rs b/test-suite/src/splits/tests/integration_tests.rs index 72a44a19b..a0eb95143 100644 --- a/test-suite/src/splits/tests/integration_tests.rs +++ b/test-suite/src/splits/tests/integration_tests.rs @@ -270,7 +270,7 @@ mod tests { let (splits_addr, _) = setup_test_case(&mut app, vec![], false); - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; let err = app .execute_contract(Addr::unchecked(OWNER), splits_addr, &msg, &[]) @@ -286,10 +286,10 @@ mod tests { let (splits_addr, _) = setup_test_case(&mut app, init_funds, false); - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; app.execute_contract( - Addr::unchecked("non_memeber".to_string()), + Addr::unchecked("non_member".to_string()), splits_addr, &msg, &[], @@ -305,7 +305,7 @@ mod tests { let (splits_addr, _) = setup_test_case_with_internal_group(&mut app, init_funds); - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; app.execute_contract(Addr::unchecked(OWNER), splits_addr.clone(), &msg, &[]) .unwrap(); @@ -345,7 +345,7 @@ mod tests { setup_test_case_with_internal_group(&mut app, init_funds); let total_weight = Cw4Contract(group_addr).total_weight(&app.wrap()).unwrap(); - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; let err = app .execute_contract(Addr::unchecked(OWNER), splits_addr, &msg, &[]) @@ -369,7 +369,7 @@ mod tests { let multiplier = init_funds[0].amount / Uint128::from(total_weight); let contract_balance = init_funds[0].amount - multiplier * Uint128::from(total_weight); - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; let _ = app .execute_contract(Addr::unchecked(OWNER), splits_addr.clone(), &msg, &[]) @@ -403,7 +403,7 @@ mod tests { let (splits_addr, _) = setup_test_case_with_overflow_group(&mut app, init_funds); - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; let err = app .execute_contract(Addr::unchecked(OWNER), splits_addr, &msg, &[]) .unwrap_err(); @@ -433,7 +433,7 @@ mod tests { .execute_contract(Addr::unchecked(OWNER), group_addr, &msg, &[]) .unwrap(); - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; let _ = app .execute_contract(Addr::unchecked(OWNER), splits_addr, &msg, &[]) .unwrap(); @@ -465,7 +465,7 @@ mod tests { let contract_balance = init_funds[0].amount - multiplier * Uint128::from(total_weight); let mut payouts = vec![]; - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; let _ = app .execute_contract(Addr::unchecked(OWNER), splits_addr.clone(), &msg, &[]) @@ -534,7 +534,7 @@ mod tests { ); // distribute again and check accounting - let msg = ExecuteMsg::Distribute {}; + let msg = ExecuteMsg::Distribute { denom_list: None }; let _ = app .execute_contract(Addr::unchecked(OWNER), splits_addr.clone(), &msg, &[]) .unwrap(); diff --git a/test-suite/src/vending_minter/tests/splits.rs b/test-suite/src/vending_minter/tests/splits.rs index 23326b97a..1a002c11c 100644 --- a/test-suite/src/vending_minter/tests/splits.rs +++ b/test-suite/src/vending_minter/tests/splits.rs @@ -95,7 +95,7 @@ fn mint_and_split() { ); assert!(res.is_ok()); - let dist_msg = SplitsExecuteMsg::Distribute {}; + let dist_msg = SplitsExecuteMsg::Distribute { denom_list: None }; let res = app.execute_contract(Addr::unchecked(OWNER), splits_addr, &dist_msg, &[]); assert!(res.is_ok());