1use cosmwasm_std::{Coin, MessageInfo, Uint128};
2use thiserror::Error;
3
4pub fn nonpayable(info: &MessageInfo) -> Result<(), PaymentError> {
6 if info.funds.is_empty() {
7 Ok(())
8 } else {
9 Err(PaymentError::NonPayable {})
10 }
11}
12
13pub fn one_coin(info: &MessageInfo) -> Result<Coin, PaymentError> {
16 match info.funds.len() {
17 0 => Err(PaymentError::NoFunds {}),
18 1 => {
19 let coin = &info.funds[0];
20 if coin.amount.is_zero() {
21 Err(PaymentError::NoFunds {})
22 } else {
23 Ok(coin.clone())
24 }
25 }
26 _ => Err(PaymentError::MultipleDenoms {}),
27 }
28}
29
30pub fn must_pay(info: &MessageInfo, denom: &str) -> Result<Uint128, PaymentError> {
33 let coin = one_coin(info)?;
34 if coin.denom != denom {
35 Err(PaymentError::MissingDenom(denom.to_string()))
36 } else {
37 Ok(coin.amount)
38 }
39}
40
41pub fn may_pay(info: &MessageInfo, denom: &str) -> Result<Uint128, PaymentError> {
44 if info.funds.is_empty() {
45 Ok(Uint128::zero())
46 } else if info.funds.len() == 1 && info.funds[0].denom == denom {
47 Ok(info.funds[0].amount)
48 } else {
49 let wrong = info.funds.iter().find(|c| c.denom != denom).unwrap();
51 Err(PaymentError::ExtraDenom(wrong.denom.to_string()))
52 }
53}
54
55#[derive(Error, Debug, PartialEq, Eq)]
56pub enum PaymentError {
57 #[error("Must send reserve token '{0}'")]
58 MissingDenom(String),
59
60 #[error("Received unsupported denom '{0}'")]
61 ExtraDenom(String),
62
63 #[error("Sent more than one denomination")]
64 MultipleDenoms {},
65
66 #[error("No funds sent")]
67 NoFunds {},
68
69 #[error("This message does no accept funds")]
70 NonPayable {},
71}
72
73#[cfg(test)]
74mod test {
75 use super::*;
76 use cosmwasm_std::testing::mock_info;
77 use cosmwasm_std::{coin, coins};
78
79 const SENDER: &str = "sender";
80
81 #[test]
82 fn nonpayable_works() {
83 let no_payment = mock_info(SENDER, &[]);
84 nonpayable(&no_payment).unwrap();
85
86 let payment = mock_info(SENDER, &coins(100, "uatom"));
87 let res = nonpayable(&payment);
88 assert_eq!(res.unwrap_err(), PaymentError::NonPayable {});
89 }
90
91 #[test]
92 fn may_pay_works() {
93 let atom: &str = "uatom";
94 let no_payment = mock_info(SENDER, &[]);
95 let atom_payment = mock_info(SENDER, &coins(100, atom));
96 let eth_payment = mock_info(SENDER, &coins(100, "wei"));
97 let mixed_payment = mock_info(SENDER, &[coin(50, atom), coin(120, "wei")]);
98
99 let res = may_pay(&no_payment, atom).unwrap();
100 assert_eq!(res, Uint128::zero());
101
102 let res = may_pay(&atom_payment, atom).unwrap();
103 assert_eq!(res, Uint128::new(100));
104
105 let err = may_pay(ð_payment, atom).unwrap_err();
106 assert_eq!(err, PaymentError::ExtraDenom("wei".to_string()));
107
108 let err = may_pay(&mixed_payment, atom).unwrap_err();
109 assert_eq!(err, PaymentError::ExtraDenom("wei".to_string()));
110 }
111
112 #[test]
113 fn must_pay_works() {
114 let atom: &str = "uatom";
115 let no_payment = mock_info(SENDER, &[]);
116 let atom_payment = mock_info(SENDER, &coins(100, atom));
117 let zero_payment = mock_info(SENDER, &coins(0, atom));
118 let eth_payment = mock_info(SENDER, &coins(100, "wei"));
119 let mixed_payment = mock_info(SENDER, &[coin(50, atom), coin(120, "wei")]);
120
121 let res = must_pay(&atom_payment, atom).unwrap();
122 assert_eq!(res, Uint128::new(100));
123
124 let err = must_pay(&no_payment, atom).unwrap_err();
125 assert_eq!(err, PaymentError::NoFunds {});
126
127 let err = must_pay(&zero_payment, atom).unwrap_err();
128 assert_eq!(err, PaymentError::NoFunds {});
129
130 let err = must_pay(ð_payment, atom).unwrap_err();
131 assert_eq!(err, PaymentError::MissingDenom(atom.to_string()));
132
133 let err = must_pay(&mixed_payment, atom).unwrap_err();
134 assert_eq!(err, PaymentError::MultipleDenoms {});
135 }
136}