1pub mod actions;
2pub mod context;
3pub mod denom;
4pub mod expiration;
5pub mod milliseconds;
6pub mod rates;
7pub mod reply;
8pub mod response;
9pub mod withdraw;
10
11pub use milliseconds::*;
12
13use crate::error::ContractError;
14use cosmwasm_std::{
15 ensure, has_coins, to_json_binary, BankMsg, Binary, Coin, CosmosMsg, SubMsg, Uint128,
16};
17use cw20::Cw20Coin;
18
19use serde::Serialize;
20use std::collections::BTreeMap;
21
22use cosmwasm_schema::cw_serde;
23#[cw_serde]
24pub enum OrderBy {
25 Asc,
26 Desc,
27}
28
29pub fn encode_binary<T>(val: &T) -> Result<Binary, ContractError>
30where
31 T: Serialize,
32{
33 match to_json_binary(val) {
34 Ok(encoded_val) => Ok(encoded_val),
35 Err(err) => Err(err.into()),
36 }
37}
38
39#[cw_serde]
40pub enum Funds {
41 Native(Coin),
42 Cw20(Cw20Coin),
43}
44
45impl Funds {
46 pub fn try_get_coin(&self) -> Result<Coin, ContractError> {
48 match self {
49 Funds::Native(coin) => Ok(coin.clone()),
50 Funds::Cw20(_) => Err(ContractError::ParsingError {
51 err: "Funds is not of type Native".to_string(),
52 }),
53 }
54 }
55}
56
57pub fn merge_sub_msgs(msgs: Vec<SubMsg>) -> Vec<SubMsg> {
65 let mut map: BTreeMap<String, Vec<Coin>> = BTreeMap::new();
69
70 let mut merged_msgs: Vec<SubMsg> = vec![];
71 for msg in msgs.into_iter() {
72 match msg.msg {
73 CosmosMsg::Bank(BankMsg::Send { to_address, amount }) => {
74 let current_coins = map.get(&to_address);
75 match current_coins {
76 Some(current_coins) => {
77 map.insert(
78 to_address.to_owned(),
79 merge_coins(current_coins.to_vec(), amount),
80 );
81 }
82 None => {
83 map.insert(to_address.to_owned(), amount);
84 }
85 }
86 }
87 _ => merged_msgs.push(msg),
88 }
89 }
90
91 for (to_address, amount) in map.into_iter() {
92 merged_msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
93 to_address,
94 amount,
95 })));
96 }
97
98 merged_msgs
99}
100
101pub fn merge_coins(coins: Vec<Coin>, coins_to_add: Vec<Coin>) -> Vec<Coin> {
111 let mut new_coins: Vec<Coin> = if !coins.is_empty() {
112 merge_coins(vec![], coins.to_vec())
113 } else {
114 vec![]
115 };
116 for coin in coins_to_add {
121 let mut same_denom_coins = new_coins.iter_mut().filter(|c| c.denom == coin.denom);
122 if let Some(same_denom_coin) = same_denom_coins.next() {
123 same_denom_coin.amount += coin.amount
124 } else {
125 new_coins.push(coin);
126 }
127 }
128
129 new_coins
130}
131
132pub fn has_coins_merged(coins: &[Coin], required: &[Coin]) -> bool {
140 let merged_coins = merge_coins(vec![], coins.to_vec());
141 let merged_required = merge_coins(vec![], required.to_vec());
142
143 for required_funds in merged_required {
144 if !has_coins(&merged_coins, &required_funds) {
145 return false;
146 };
147 }
148
149 true
150}
151
152pub fn deduct_funds(coins: &mut [Coin], funds: &Coin) -> Result<(), ContractError> {
158 let coin_amount: Vec<&mut Coin> = coins
159 .iter_mut()
160 .filter(|c| c.denom.eq(&funds.denom))
161 .collect();
162
163 let mut remainder = funds.amount;
164 for same_coin in coin_amount {
165 if same_coin.amount > remainder {
166 same_coin.amount = same_coin.amount.checked_sub(remainder)?;
167 return Ok(());
168 } else {
169 remainder = remainder.checked_sub(same_coin.amount)?;
170 same_coin.amount = Uint128::zero();
171 }
172 }
173
174 ensure!(
175 remainder == Uint128::zero(),
176 ContractError::InsufficientFunds {}
177 );
178
179 Ok(())
180}
181
182#[cfg(test)]
183mod test {
184 use cosmwasm_std::{coin, Uint128, WasmMsg};
185 use cw20::Expiration;
186
187 use super::*;
188
189 #[cw_serde]
190 struct TestStruct {
191 name: String,
192 expiration: Expiration,
193 }
194
195 #[test]
196 fn test_merge_coins() {
197 let coins = vec![coin(100, "uusd"), coin(100, "uluna")];
198 let funds_to_add = vec![
199 coin(25, "uluna"),
200 coin(50, "uusd"),
201 coin(100, "ucad"),
202 coin(50, "uluna"),
203 coin(100, "uluna"),
204 coin(100, "ucad"),
205 ];
206
207 let res = merge_coins(coins, funds_to_add);
208 assert_eq!(
209 vec![coin(150, "uusd"), coin(275, "uluna"), coin(200, "ucad")],
210 res
211 );
212 }
213
214 #[test]
215 fn test_merge_sub_messages() {
216 let sub_msgs: Vec<SubMsg> = vec![
217 SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
218 to_address: "A".to_string(),
219 amount: vec![coin(100, "uusd"), coin(50, "uluna")],
220 })),
221 SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
222 to_address: "A".to_string(),
223 amount: vec![coin(100, "uusd"), coin(50, "ukrw")],
224 })),
225 SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
226 to_address: "B".to_string(),
227 amount: vec![coin(100, "uluna")],
228 })),
229 SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
230 to_address: "B".to_string(),
231 amount: vec![coin(50, "uluna")],
232 })),
233 SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
234 contract_addr: "C".to_string(),
235 funds: vec![],
236 msg: encode_binary(&"").unwrap(),
237 })),
238 ];
239
240 let merged_msgs = merge_sub_msgs(sub_msgs);
241 assert_eq!(
242 vec![
243 SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
244 contract_addr: "C".to_string(),
245 funds: vec![],
246 msg: encode_binary(&"").unwrap(),
247 })),
248 SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
249 to_address: "A".to_string(),
250 amount: vec![coin(200, "uusd"), coin(50, "uluna"), coin(50, "ukrw")],
251 })),
252 SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
253 to_address: "B".to_string(),
254 amount: vec![coin(150, "uluna")],
255 })),
256 ],
257 merged_msgs
258 );
259
260 assert_eq!(3, merged_msgs.len());
261 }
262
263 #[test]
264 fn test_deduct_funds() {
265 let mut funds: Vec<Coin> = vec![coin(5, "uluna"), coin(100, "uusd"), coin(100, "uluna")];
266
267 deduct_funds(&mut funds, &coin(10, "uluna")).unwrap();
268
269 assert_eq!(Uint128::zero(), funds[0].amount);
270 assert_eq!(String::from("uluna"), funds[0].denom);
271 assert_eq!(Uint128::from(95u128), funds[2].amount);
272 assert_eq!(String::from("uluna"), funds[2].denom);
273
274 let mut funds: Vec<Coin> = vec![Coin {
275 denom: String::from("uluna"),
276 amount: Uint128::from(5u64),
277 }];
278
279 let e = deduct_funds(&mut funds, &coin(10, "uluna")).unwrap_err();
280
281 assert_eq!(ContractError::InsufficientFunds {}, e);
282 }
283 #[test]
284 fn test_has_coins_merged() {
285 let available_coins: Vec<Coin> = vec![
286 coin(50, "uluna"),
287 coin(200, "uusd"),
288 coin(50, "ukrw"),
289 coin(25, "uluna"),
290 coin(25, "uluna"),
291 ];
292 let required_funds: Vec<Coin> = vec![
293 coin(50, "uluna"),
294 coin(100, "uusd"),
295 coin(50, "ukrw"),
296 coin(50, "uluna"),
297 ];
298
299 assert!(has_coins_merged(&available_coins, &required_funds));
300
301 let insufficient_funds: Vec<Coin> =
302 vec![coin(10, "uluna"), coin(100, "uusd"), coin(50, "ukrw")];
303
304 assert!(!has_coins_merged(&insufficient_funds, &required_funds));
305 }
306}