Skip to content

Commit

Permalink
Merge pull request #45 from WHELP-project/44-create-new-contract-skel…
Browse files Browse the repository at this point in the history
…eton

Fee splitter: initial skeleton
  • Loading branch information
ueco-jb authored Jan 18, 2024
2 parents 8e20ed2 + 2b06635 commit 19cfb91
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 7 deletions.
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contracts/factory/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,8 +698,8 @@ pub fn deregister_pool_and_staking(
Ok(pairs
.unwrap_or_default()
.iter()
.filter(|&pair| pair != pair_addr)
.cloned()
.filter(|pair| pair != pair_addr)
.collect::<Vec<_>>())
},
)?;
Expand Down
6 changes: 6 additions & 0 deletions contracts/fee_splitter/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[alias]
wasm = "build --release --lib --target wasm32-unknown-unknown"
wasm-debug = "build --lib --target wasm32-unknown-unknown"
unit-test = "test --lib"
integration-test = "test --test integration"
schema = "run --bin fee_splitter_schema"
36 changes: 36 additions & 0 deletions contracts/fee_splitter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "fee-splitter"
version = { workspace = true }
authors = ["Jakub <[email protected]>", "Kalo <[email protected]"]
edition = { workspace = true }
description = "Fee Splitter - split payments and distribute between multiple recipients"
license = { workspace = true }

[lib]
crate-type = ["cdylib", "rlib"]

[features]
backtraces = ["cosmwasm-std/backtraces"]
library = []

[dependencies]
coreum-wasm-sdk = { workspace = true }
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw-storage-plus = { workspace = true }
cw2 = { workspace = true }
cw20 = { workspace = true }
cw-utils = { workspace = true }
thiserror = { workspace = true }
dex = { workspace = true }
dex-stake = { workspace = true }
itertools = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }
bindings-test = { workspace = true }
cw-multi-test = { workspace = true }
cw20-base = { workspace = true }
# dex-factory = { workspace = true }
dex-pool = { workspace = true }
dex-stake = { workspace = true }
1 change: 1 addition & 0 deletions contracts/fee_splitter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WIP
173 changes: 173 additions & 0 deletions contracts/fee_splitter/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use coreum_wasm_sdk::core::{CoreumMsg, CoreumQueries};
use cosmwasm_std::{
coin, entry_point, to_json_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps,
DepsMut, Env, MessageInfo, StdError, StdResult, WasmMsg,
};
use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg};
use cw_storage_plus::Item;

use crate::{
error::ContractError,
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
state::Config,
};

/// Saves factory settings
pub const CONFIG: Item<Config> = Item::new("config");

pub type Response = cosmwasm_std::Response<CoreumMsg>;
pub type SubMsg = cosmwasm_std::SubMsg<CoreumMsg>;

/// Contract name that is used for migration.
const _CONTRACT_NAME: &str = "fee-splitter";
/// Contract version that is used for migration.
const _CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

/// Creates a new contract with the specified parameters packed in the `msg` variable.
///
/// * **msg** is message which contains the parameters used for creating the contract.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut<CoreumQueries>,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let is_weights_valid = msg
.addresses
.iter()
.map(|&(_, weight)| weight)
.fold(Decimal::zero(), |acc, x| acc + x)
.le(&Decimal::from_ratio(1u32, 1u32));

if !is_weights_valid {
return Err(ContractError::InvalidWeights {});
}

let config = Config {
addresses: msg.addresses,
};

CONFIG.save(deps.storage, &config)?;

Ok(Response::new().add_attribute("initialized", "contract"))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: Deps<CoreumQueries>,
env: Env,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg.clone() {
ExecuteMsg::SendTokens {
native_denoms,
cw20_addresses,
} => execute_send_tokens(deps, env, native_denoms, cw20_addresses),
}
}

fn execute_send_tokens(
deps: Deps<CoreumQueries>,
env: Env,
native_denoms: Vec<String>,
cw20_addresses: Vec<String>,
) -> Result<Response, ContractError> {
let config = query_config(deps)?;

let contract_address = env.contract.address.to_string();
// gather balances of native tokens, either from function parameter or all
let native_balances = native_denoms
.into_iter()
.map(|denom| deps.querier.query_balance(&env.contract.address, denom))
.collect::<StdResult<Vec<Coin>>>()?;

// gather addresses of cw20 token contract, either from arguments or configuration
let cw20_addresses = cw20_addresses
.into_iter()
.map(|address| deps.api.addr_validate(&address))
.collect::<StdResult<Vec<Addr>>>()?;

let mut messages: Vec<CosmosMsg<CoreumMsg>> = vec![];

for (address, weight) in config.addresses {
let amount = native_balances
.iter()
.filter_map(|bcoin| {
let amount = bcoin.amount * weight;
if amount.is_zero() {
None
} else {
Some(coin((bcoin.amount * weight).u128(), &bcoin.denom))
}
})
.collect::<Vec<Coin>>();
if !amount.is_empty() {
let native_message = CosmosMsg::Bank(BankMsg::Send {
to_address: address.to_string(),
amount,
});
messages.push(native_message);
}

cw20_addresses
.iter()
// filter out if balance is zero in order to avoid empty transfer error
.filter_map(|token| {
match deps.querier.query_wasm_smart::<BalanceResponse>(
token,
&Cw20QueryMsg::Balance {
address: contract_address.clone(),
},
) {
Ok(r) => {
if !r.balance.is_zero() {
Some((token, r.balance))
} else {
None
}
}
// the only victim of current design
Err(_) => None,
}
})
.try_for_each(|(token, balance)| {
let msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: token.to_string(),
msg: to_json_binary(&Cw20ExecuteMsg::Transfer {
recipient: address.to_string(),
amount: balance * weight,
})?,
funds: vec![],
});
messages.push(msg);
Ok::<(), StdError>(())
})?;
}
Ok(Response::new().add_messages(messages))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps<CoreumQueries>, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Config {} => to_json_binary(&query_config(deps)?),
}
}

pub fn query_config(deps: Deps<CoreumQueries>) -> StdResult<Config> {
let config = CONFIG.load(deps.storage)?;
let resp = Config {
addresses: config.addresses,
};

Ok(resp)
}

#[cfg(test)]
mod tests {
#[test]
#[ignore]
fn instantiate_with_invalid_weights_should_throw_error() {
todo!()
}
}
15 changes: 15 additions & 0 deletions contracts/fee_splitter/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use cosmwasm_std::StdError;
use thiserror::Error;

/// This enum describes factory contract errors
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),

#[error("Provided weights exceed maximum allowed value")]
InvalidWeights {},

#[error("Unauthorized")]
Unauthorized {},
}
7 changes: 7 additions & 0 deletions contracts/fee_splitter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod contract;
pub mod error;
pub mod msg;
pub mod state;

#[cfg(test)]
mod testing;
31 changes: 31 additions & 0 deletions contracts/fee_splitter/src/msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::Decimal;

#[cw_serde]
pub struct InstantiateMsg {
// List of addresses and their weights.
// Weights must sum up to 1.0
pub addresses: Vec<(String, Decimal)>,
// List of cw20 token addresses to check for balance
pub cw20_contracts: Vec<String>,
}

#[cw_serde]
pub enum ExecuteMsg {
// Transfers tokens send to this contract based on weights from configuration.
// Any user can execute it
SendTokens {
// Provide denoms of native tokens to check
native_denoms: Vec<String>,
// Provide addresses of cw20 contracts to check
// If None, contract will query adresses from Config
cw20_addresses: Vec<String>,
},
}

#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(crate::state::Config)]
Config {},
}
9 changes: 9 additions & 0 deletions contracts/fee_splitter/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Decimal;

#[cw_serde]
pub struct Config {
// List of addresses and their weights.
// Weights must sum up to 1.0
pub addresses: Vec<(String, Decimal)>,
}
1 change: 1 addition & 0 deletions contracts/fee_splitter/src/testing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

10 changes: 5 additions & 5 deletions contracts/pool/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ fn provide_liquidity() {
}],
);
let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
let transfer_from_msg = res.messages.get(0).expect("no message");
let transfer_from_msg = res.messages.first().expect("no message");
let mint_min_liquidity_msg = res.messages.get(1).expect("no message");
let mint_receiver_msg = res.messages.get(2).expect("no message");
assert_eq!(
Expand Down Expand Up @@ -585,7 +585,7 @@ fn provide_liquidity() {

// Only accept 100, then 50 share will be generated with 100 * (100 / 200)
let res: Response = execute(deps.as_mut(), env, info, msg).unwrap();
let transfer_from_msg = res.messages.get(0).expect("no message");
let transfer_from_msg = res.messages.first().expect("no message");
let mint_msg = res.messages.get(1).expect("no message");
assert_eq!(
transfer_from_msg,
Expand Down Expand Up @@ -956,7 +956,7 @@ fn withdraw_liquidity() {

let log_withdrawn_share = res.attributes.get(2).expect("no log");
let log_refund_assets = res.attributes.get(3).expect("no log");
let msg_refund_0 = res.messages.get(0).expect("no message");
let msg_refund_0 = res.messages.first().expect("no message");
let msg_refund_1 = res.messages.get(1).expect("no message");
let msg_burn_liquidity = res.messages.get(2).expect("no message");
assert_eq!(
Expand Down Expand Up @@ -1246,7 +1246,7 @@ fn try_native_to_token() {
);

let res = execute(deps.as_mut(), env, info, msg).unwrap();
let msg_transfer = res.messages.get(0).expect("no message");
let msg_transfer = res.messages.first().expect("no message");

// Current price is 1.5, so expected return without spread is 1000
// 952380952 = 20000000000 - (30000000000 * 20000000000) / (30000000000 + 1500000000)
Expand Down Expand Up @@ -1473,7 +1473,7 @@ fn try_token_to_native() {
let info = mock_info("asset0000", &[]);

let res = execute(deps.as_mut(), env, info, msg).unwrap();
let msg_transfer = res.messages.get(0).expect("no message");
let msg_transfer = res.messages.first().expect("no message");

// Current price is 1.5, so expected return without spread is 1000
// 952380952,3809524 = 20000000000 - (30000000000 * 20000000000) / (30000000000 + 1500000000)
Expand Down
2 changes: 1 addition & 1 deletion contracts/pool_stable/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub fn instantiate(

msg.validate_fees()?;

let params: StablePoolParams = from_json(&msg.init_params.unwrap())?;
let params: StablePoolParams = from_json(msg.init_params.unwrap())?;

if params.amp == 0 || params.amp > MAX_AMP {
return Err(ContractError::IncorrectAmp { max_amp: MAX_AMP });
Expand Down

0 comments on commit 19cfb91

Please sign in to comment.