monetary/
payment.rs

1use cosmwasm_std::{MessageInfo, Uint128};
2
3use crate::{AmountU128, CheckedCoin, Denom, MonetaryError};
4
5/// Requires exactly one denom sent, which matches the requested denom.
6/// Returns the amount if only one denom and non-zero amount. Errors otherwise.
7pub fn must_pay<T>(info: &MessageInfo, denom: &Denom<T>) -> Result<AmountU128<T>, MonetaryError> {
8    if info.funds.len() != 1 {
9        return Err(MonetaryError::DenomNotFound(denom.to_string()));
10    }
11
12    let coin = &info.funds[0];
13    if coin.amount.is_zero() || coin.denom != denom.repr() {
14        return Err(MonetaryError::DenomNotFound(denom.to_string()));
15    }
16
17    Ok(AmountU128::new(coin.amount))
18}
19
20/// Similar to must_pay, but it any payment is optional. Returns an error if a different
21/// denom was sent. Otherwise, returns the amount of `denom` sent, or 0 if nothing sent.
22pub fn may_pay<T>(info: &MessageInfo, denom: &Denom<T>) -> Result<AmountU128<T>, MonetaryError> {
23    if info.funds.is_empty() {
24        Ok(AmountU128::new(Uint128::zero()))
25    } else if info.funds.len() == 1 {
26        if info.funds[0].denom == denom.repr() {
27            Ok(AmountU128::new(info.funds[0].amount))
28        } else {
29            Err(MonetaryError::DenomNotFound(denom.to_string()))
30        }
31    } else {
32        Err(MonetaryError::TooManyDenoms {})
33    }
34}
35
36pub fn amount<T>(info: &MessageInfo, denom: &Denom<T>) -> AmountU128<T> {
37    info.funds
38        .iter()
39        .find(|coin| coin.denom == denom.repr())
40        .map(|coin| AmountU128::new(coin.amount))
41        .unwrap_or_else(|| AmountU128::zero())
42}
43
44pub fn coin<T>(amount: impl Into<Uint128>, denom: &Denom<T>) -> CheckedCoin<T> {
45    CheckedCoin::new(denom.clone(), AmountU128::new(amount.into()))
46}
47
48pub fn coins<T>(amount: impl Into<Uint128>, denom: &Denom<T>) -> Vec<CheckedCoin<T>> {
49    vec![coin(amount, denom)]
50}
51
52#[cfg(test)]
53mod test {
54    use crate::denom;
55
56    use super::*;
57    use cosmwasm_std::testing::mock_info;
58    use cosmwasm_std::{coin, coins};
59
60    const SENDER: &str = "sender";
61
62    #[denom]
63    pub struct Atom;
64
65    #[test]
66    fn may_pay_works() {
67        let atom: Denom<Atom> = Denom::new("uatom");
68        let no_payment = mock_info(SENDER, &[]);
69        let atom_payment = mock_info(SENDER, &coins(100, &atom));
70        let eth_payment = mock_info(SENDER, &coins(100, "wei"));
71        let mixed_payment = mock_info(SENDER, &[coin(50, &atom), coin(120, "wei")]);
72
73        let res = may_pay(&no_payment, &atom).unwrap();
74        assert_eq!(res, AmountU128::zero());
75
76        let res = may_pay(&atom_payment, &atom).unwrap();
77        assert_eq!(res, AmountU128::new(100u128.into()));
78
79        let err = may_pay(&eth_payment, &atom).unwrap_err();
80        assert_eq!(err, MonetaryError::DenomNotFound("uatom".to_string()));
81
82        let err = may_pay(&mixed_payment, &atom).unwrap_err();
83        assert_eq!(err, MonetaryError::TooManyDenoms {});
84    }
85
86    #[test]
87    fn must_pay_works() {
88        let atom: Denom<Atom> = Denom::new("uatom");
89        let no_payment = mock_info(SENDER, &[]);
90        let atom_payment = mock_info(SENDER, &coins(100, &atom));
91        let zero_payment = mock_info(SENDER, &coins(0, &atom));
92        let eth_payment = mock_info(SENDER, &coins(100, "wei"));
93        let mixed_payment = mock_info(SENDER, &[coin(50, &atom), coin(120, "wei")]);
94
95        let res = must_pay(&atom_payment, &atom).unwrap();
96        assert_eq!(res, AmountU128::new(100u128.into()));
97
98        let err = must_pay(&no_payment, &atom).unwrap_err();
99        assert_eq!(err, MonetaryError::DenomNotFound("uatom".to_string()));
100
101        let err = must_pay(&zero_payment, &atom).unwrap_err();
102        assert_eq!(err, MonetaryError::DenomNotFound("uatom".to_string()));
103
104        let err = must_pay(&eth_payment, &atom).unwrap_err();
105        assert_eq!(err, MonetaryError::DenomNotFound("uatom".to_string()));
106
107        let err = must_pay(&mixed_payment, &atom).unwrap_err();
108        assert_eq!(err, MonetaryError::DenomNotFound("uatom".to_string()));
109    }
110}