Skip to main content

andromeda_modules/
rates.rs

1use andromeda_std::{
2    amp::recipient::Recipient, andr_exec, andr_instantiate, andr_query, error::ContractError,
3};
4use cosmwasm_schema::{cw_serde, QueryResponses};
5use cosmwasm_std::{ensure, Coin, Decimal, Fraction, QuerierWrapper};
6
7#[andr_instantiate]
8#[cw_serde]
9pub struct InstantiateMsg {
10    pub rates: Vec<RateInfo>,
11}
12
13#[andr_exec]
14#[cw_serde]
15pub enum ExecuteMsg {
16    UpdateRates { rates: Vec<RateInfo> },
17}
18
19#[andr_query]
20#[cw_serde]
21#[derive(QueryResponses)]
22pub enum QueryMsg {
23    #[returns(PaymentsResponse)]
24    Payments {},
25}
26
27#[cw_serde]
28pub struct PaymentsResponse {
29    pub payments: Vec<RateInfo>,
30}
31
32#[cw_serde]
33pub struct RateInfo {
34    pub rate: Rate,
35    pub is_additive: bool,
36    pub description: Option<String>,
37    pub recipients: Vec<Recipient>,
38}
39
40#[cw_serde]
41/// An enum used to define various types of fees
42pub enum Rate {
43    /// A flat rate fee
44    Flat(Coin),
45    /// A percentage fee
46    Percent(PercentRate),
47    // External(PrimitivePointer),
48}
49
50#[cw_serde] // This is added such that both Rate::Flat and Rate::Percent have the same level of nesting which
51            // makes it easier to work with on the frontend.
52pub struct PercentRate {
53    pub percent: Decimal,
54}
55
56impl From<Decimal> for Rate {
57    fn from(decimal: Decimal) -> Self {
58        Rate::Percent(PercentRate { percent: decimal })
59    }
60}
61
62impl Rate {
63    /// Validates that a given rate is non-zero. It is expected that the Rate is not an
64    /// External Rate.
65    pub fn is_non_zero(&self) -> Result<bool, ContractError> {
66        match self {
67            Rate::Flat(coin) => Ok(!coin.amount.is_zero()),
68            Rate::Percent(PercentRate { percent }) => Ok(!percent.is_zero()),
69            // Rate::External(_) => Err(ContractError::UnexpectedExternalRate {}),
70        }
71    }
72
73    /// Validates `self` and returns an "unwrapped" version of itself wherein if it is an External
74    /// Rate, the actual rate value is retrieved from the Primitive Contract.
75    pub fn validate(&self, querier: &QuerierWrapper) -> Result<Rate, ContractError> {
76        let rate = self.clone().get_rate(querier)?;
77        ensure!(rate.is_non_zero()?, ContractError::InvalidRate {});
78
79        if let Rate::Percent(PercentRate { percent }) = rate {
80            ensure!(percent <= Decimal::one(), ContractError::InvalidRate {});
81        }
82
83        Ok(rate)
84    }
85
86    /// If `self` is Flat or Percent it returns itself. Otherwise it queries the primitive contract
87    /// and retrieves the actual Flat or Percent rate.
88    fn get_rate(self, _querier: &QuerierWrapper) -> Result<Rate, ContractError> {
89        match self {
90            Rate::Flat(_) => Ok(self),
91            Rate::Percent(_) => Ok(self),
92            // Rate::External(primitive_pointer) => {
93            //     let primitive = primitive_pointer.into_value(querier)?;
94            //     match primitive {
95            //         None => Err(ContractError::ParsingError {
96            //             err: "Stored primitive is None".to_string(),
97            //         }),
98            //         Some(primitive) => match primitive {
99            //             Primitive::Coin(coin) => Ok(Rate::Flat(coin)),
100            //             Primitive::Decimal(value) => Ok(Rate::from(value)),
101            //             _ => Err(ContractError::ParsingError {
102            //                 err: "Stored rate is not a coin or Decimal".to_string(),
103            //             }),
104            //         },
105            //     }
106            // }
107        }
108    }
109}
110
111/// An attribute struct used for any events that involve a payment
112pub struct PaymentAttribute {
113    /// The amount paid
114    pub amount: Coin,
115    /// The address the payment was made to
116    pub receiver: String,
117}
118
119impl ToString for PaymentAttribute {
120    fn to_string(&self) -> String {
121        format!("{}<{}", self.receiver, self.amount)
122    }
123}
124
125/// Calculates a fee amount given a `Rate` and payment amount.
126///
127/// ## Arguments
128/// * `fee_rate` - The `Rate` of the fee to be paid
129/// * `payment` - The amount used to calculate the fee
130///
131/// Returns the fee amount in a `Coin` struct.
132pub fn calculate_fee(fee_rate: Rate, payment: &Coin) -> Result<Coin, ContractError> {
133    match fee_rate {
134        Rate::Flat(rate) => Ok(Coin::new(rate.amount.u128(), rate.denom)),
135        Rate::Percent(PercentRate { percent }) => {
136            // [COM-03] Make sure that fee_rate between 0 and 100.
137            ensure!(
138                // No need for rate >=0 due to type limits (Question: Should add or remove?)
139                percent <= Decimal::one() && !percent.is_zero(),
140                ContractError::InvalidRate {}
141            );
142            let mut fee_amount = payment.amount * percent;
143
144            // Always round any remainder up and prioritise the fee receiver.
145            // Inverse of percent will always exist.
146            let reversed_fee = fee_amount * percent.inv().unwrap();
147            if payment.amount > reversed_fee {
148                // [COM-1] Added checked add to fee_amount rather than direct increment
149                fee_amount = fee_amount.checked_add(1u128.into())?;
150            }
151            Ok(Coin::new(fee_amount.u128(), payment.denom.clone()))
152        } // Rate::External(_) => Err(ContractError::UnexpectedExternalRate {}),
153    }
154}
155
156#[cfg(test)]
157mod tests {
158
159    use cosmwasm_std::{coin, Uint128};
160
161    use super::*;
162
163    // #[test]
164    // fn test_validate_external_rate() {
165    //     let deps = mock_dependencies_custom(&[]);
166
167    //     let rate = Rate::External(PrimitivePointer {
168    //         address: MOCK_PRIMITIVE_CONTRACT.to_owned(),
169
170    //         key: Some("percent".to_string()),
171    //     });
172    //     let validated_rate = rate.validate(&deps.as_ref().querier).unwrap();
173    //     let expected_rate = Rate::from(Decimal::percent(1));
174    //     assert_eq!(expected_rate, validated_rate);
175
176    //     let rate = Rate::External(PrimitivePointer {
177    //         address: MOCK_PRIMITIVE_CONTRACT.to_owned(),
178    //         key: Some("flat".to_string()),
179    //     });
180    //     let validated_rate = rate.validate(&deps.as_ref().querier).unwrap();
181    //     let expected_rate = Rate::Flat(coin(1u128, "uusd"));
182    //     assert_eq!(expected_rate, validated_rate);
183    // }
184
185    #[test]
186    fn test_calculate_fee() {
187        let payment = coin(101, "uluna");
188        let expected = Ok(coin(5, "uluna"));
189        let fee = Rate::from(Decimal::percent(4));
190
191        let received = calculate_fee(fee, &payment);
192
193        assert_eq!(expected, received);
194
195        assert_eq!(expected, received);
196
197        let payment = coin(125, "uluna");
198        let fee = Rate::Flat(Coin {
199            amount: Uint128::from(5_u128),
200            denom: "uluna".to_string(),
201        });
202
203        let received = calculate_fee(fee, &payment);
204
205        assert_eq!(expected, received);
206    }
207}