1use cosmwasm_std::{Coin, MessageInfo, Uint256};
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<Uint256, 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<Uint256, PaymentError> {
44 if info.funds.is_empty() {
45 Ok(Uint256::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::{message_info, mock_dependencies};
77 use cosmwasm_std::{coin, coins};
78
79 const SENDER: &str = "sender";
80
81 #[test]
82 fn nonpayable_works() {
83 let deps = mock_dependencies();
84 let sender = deps.api.addr_make(SENDER);
85
86 let no_payment = message_info(&sender, &[]);
87 nonpayable(&no_payment).unwrap();
88
89 let payment = message_info(&sender, &coins(100, "uatom"));
90 let res = nonpayable(&payment);
91 assert_eq!(res.unwrap_err(), PaymentError::NonPayable {});
92 }
93
94 #[test]
95 fn may_pay_works() {
96 let deps = mock_dependencies();
97 let sender = deps.api.addr_make(SENDER);
98
99 let atom: &str = "uatom";
100 let no_payment = message_info(&sender, &[]);
101 let atom_payment = message_info(&sender, &coins(100, atom));
102 let eth_payment = message_info(&sender, &coins(100, "wei"));
103 let mixed_payment = message_info(&sender, &[coin(50, atom), coin(120, "wei")]);
104
105 let res = may_pay(&no_payment, atom).unwrap();
106 assert_eq!(res, Uint256::zero());
107
108 let res = may_pay(&atom_payment, atom).unwrap();
109 assert_eq!(res, Uint256::new(100));
110
111 let err = may_pay(ð_payment, atom).unwrap_err();
112 assert_eq!(err, PaymentError::ExtraDenom("wei".to_string()));
113
114 let err = may_pay(&mixed_payment, atom).unwrap_err();
115 assert_eq!(err, PaymentError::ExtraDenom("wei".to_string()));
116 }
117
118 #[test]
119 fn must_pay_works() {
120 let deps = mock_dependencies();
121 let sender = deps.api.addr_make(SENDER);
122
123 let atom: &str = "uatom";
124 let no_payment = message_info(&sender, &[]);
125 let atom_payment = message_info(&sender, &coins(100, atom));
126 let zero_payment = message_info(&sender, &coins(0, atom));
127 let eth_payment = message_info(&sender, &coins(100, "wei"));
128 let mixed_payment = message_info(&sender, &[coin(50, atom), coin(120, "wei")]);
129
130 let res = must_pay(&atom_payment, atom).unwrap();
131 assert_eq!(res, Uint256::new(100));
132
133 let err = must_pay(&no_payment, atom).unwrap_err();
134 assert_eq!(err, PaymentError::NoFunds {});
135
136 let err = must_pay(&zero_payment, atom).unwrap_err();
137 assert_eq!(err, PaymentError::NoFunds {});
138
139 let err = must_pay(ð_payment, atom).unwrap_err();
140 assert_eq!(err, PaymentError::MissingDenom(atom.to_string()));
141
142 let err = must_pay(&mixed_payment, atom).unwrap_err();
143 assert_eq!(err, PaymentError::MultipleDenoms {});
144 }
145}