1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
use crate::types::denom::{Denom, DenomConversion};
use crate::types::error::ContractError;
use result_extensions::ResultExtensions;

/// Converts the source denom amount to the target denom's amount, accounting for any remaining
/// funds.
///
/// # Parameters
/// * `source_amount` The amount of source denom to convert to target denom.
/// * `source_denom` The denom defining the source amount.
/// * `target_denom` The denom defining the target amount, allowing the relation between source and
/// target to dictate the results.
pub fn convert_denom(
    source_amount: u128,
    source_denom: &Denom,
    target_denom: &Denom,
) -> Result<DenomConversion, ContractError> {
    let source_precision = source_denom.precision.u64();
    let target_precision = target_denom.precision.u64();
    let precision_diff = u32::try_from((source_precision as i64 - target_precision as i64).abs())
        .map_err(|e| ContractError::ConversionError {
            message: format!("source precision [{source_precision}] and target precision [{target_precision}] have too large a difference to convert: {e:?}")
        })?;
    let precision_modifier = 10u128.pow(precision_diff);
    let (target_amount, remainder) = match source_precision {
        // If source precision is greater, the value needs some of its values trimmed off for target
        // conversion amount.
        s if s > target_precision => {
            let target_amount = source_amount / precision_modifier;
            let remainder = source_amount % precision_modifier;
            (target_amount, remainder)
        }
        // If source precision is lesser, the value should get zeroes added to become the target.
        // The value increases, so there is never a remainder.
        s if s < target_precision => {
            let target_amount = source_amount * precision_modifier;
            (target_amount, 0u128)
        }
        // If the precisions are equal, then it is a 1 to 1 conversion and the result is the input
        _ => (source_amount, 0u128),
    };
    DenomConversion {
        source_amount,
        target_amount,
        remainder,
    }
    .to_ok()
}

#[cfg(test)]
pub mod tests {
    use crate::types::denom::Denom;
    use crate::util::conversion_utils::convert_denom;

    #[test]
    fn test_source_precision_greater_than_target_precision() {
        let amount = 123456789;
        let source_denom = Denom::new("source", 4);
        let target_denom = Denom::new("target", 1);
        let very_large_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            123456, very_large_result.target_amount,
            "Value {amount}: The resulting amount should be all values that fit into the target destination type",
        );
        assert_eq!(
            789, very_large_result.remainder,
            "Value {amount}: The remainder amount should equate to all precision that could not be converted",
        );
        let amount = 1000;
        let just_large_enough_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            1, just_large_enough_result.target_amount,
            "Value {amount}: The resulting amount should be just the value before the decimal place",
        );
        assert_eq!(
            0, just_large_enough_result.remainder,
            "Value {amount}: There should be no remainder because all values after the decimal place were zeroes",
        );
        let amount = 1101;
        let small_overflow_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            1, small_overflow_result.target_amount,
            "Value {amount}: The resulting amount should be the value before the decimal place",
        );
        assert_eq!(
            101, small_overflow_result.remainder,
            "Value {amount}: The remainder should properly contain the overflow",
        );
        let amount = 123;
        let full_overflow_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            0, full_overflow_result.target_amount,
            "Value {amount}: The resulting amount should be zero because all converted amounts were remainders",
        );
        assert_eq!(
            123, full_overflow_result.remainder,
            "Value {amount}: The remainder should be the whole value due to overflow past precision conversion",
        );
        let amount = 0;
        let zero_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            0, zero_result.target_amount,
            "Value {amount}: The target amount should be zero because the initial value was zero",
        );
        assert_eq!(
            0, zero_result.remainder,
            "Value {amount}: The remainder should be zero because the initial value was zero",
        );
    }

    #[test]
    fn test_source_precision_lower_than_target_precision() {
        let amount = 123456789;
        let source_denom = Denom::new("source", 1);
        let target_denom = Denom::new("target", 4);
        let very_large_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            123456789000, very_large_result.target_amount,
            "Value {amount}: The target amount should have extra zeroes for the increased precision",
        );
        assert_eq!(
            0, very_large_result.remainder,
            "Value {amount}: A conversion with lower source precision than target should never have a remainder",
        );
        let amount = 2;
        let simple_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            2000, simple_result.target_amount,
            "Value {amount}: The target amount should have extra zeroes for the increased precision",
        );
        assert_eq!(
            0, simple_result.remainder,
            "Value {amount}: A conversion with lower source precision than target should never have a remainder",
        );
        let amount = 0;
        let zero_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            0, zero_result.target_amount,
            "Value {amount}: The target amount should be zero because the input was zero",
        );
        assert_eq!(
            0, zero_result.remainder,
            "Value {amount}: A conversion with lower source precision than target should never have a remainder",
        );
    }

    #[test]
    fn test_source_precision_equal_to_target_precision() {
        let amount = 123456789;
        let source_denom = Denom::new("source", 3);
        let target_denom = Denom::new("target", 3);
        let large_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            amount, large_result.target_amount,
            "Value {amount}: The target amount should equate to the input because there is no precision diff",
        );
        assert_eq!(
            0, large_result.remainder,
            "Value {amount}: The remainder should be zero because no conversion was necessary",
        );
        let amount = 6;
        let simple_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            amount, simple_result.target_amount,
            "Value {amount}: The target amount should equate to the input because there is no precision diff",
        );
        assert_eq!(
            0, simple_result.remainder,
            "Value {amount}: The remainder should be zero because no conversion was necessary",
        );
        let amount = 0;
        let zero_result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            0, zero_result.target_amount,
            "Value {amount}: The target amount should be zero because the input was zero",
        );
        assert_eq!(
            0, zero_result.remainder,
            "Value {amount}: The remainder should be zero because the input was zero",
        );
    }

    #[test]
    fn test_example_use_case() {
        let amount = 987123456;
        let source_denom = Denom::new("trading", 6);
        let target_denom = Denom::new("deposit", 2);
        let result = convert_denom(amount, &source_denom, &target_denom)
            .expect("The conversion should succeed with valid inputs");
        assert_eq!(
            98712, result.target_amount,
            "Input {amount}: Expected the proper target amount output from input",
        );
        assert_eq!(
            3456, result.remainder,
            "Input {amount}: Expected the proper remainder amount from input",
        );
    }
}