exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

test_amount.c (19448B)


      1 /*
      2   This file is part of TALER
      3   (C) 2015, 2021 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 /**
     18  * @file util/test_amount.c
     19  * @brief Tests for amount logic
     20  * @author Christian Grothoff <christian@grothoff.org>
     21  */
     22 #include "taler/taler_util.h"
     23 
     24 
     25 /**
     26  * Test the price list (`struct TALER_AmountList`) API.
     27  *
     28  * @return 0 on success
     29  */
     30 static int
     31 test_amount_list (void)
     32 {
     33   struct TALER_AmountList al;
     34   struct TALER_AmountList cp;
     35   struct TALER_Amount a;
     36   const struct TALER_Amount *f;
     37 
     38   /* parsing, order preservation, round-trip */
     39   GNUNET_assert (GNUNET_OK ==
     40                  TALER_string_to_amount_list ("EUR:1.1;CHF:1;USD:2;JPY:200",
     41                                               &al));
     42   GNUNET_assert (4 == al.tal_len);
     43   GNUNET_assert (0 == strcmp ("EUR",
     44                               al.tal[0].currency));
     45   GNUNET_assert (0 == strcmp ("JPY",
     46                               al.tal[3].currency));
     47   GNUNET_assert (0 == strcmp ("EUR:1.1;CHF:1;USD:2;JPY:200",
     48                               TALER_amount_list2s (&al)));
     49 
     50   /* lookup is case-insensitive and, crucially, returns NULL for a
     51      currency that is not offered instead of a zero amount */
     52   f = TALER_amount_list_find (&al,
     53                               "chf");
     54   GNUNET_assert (NULL != f);
     55   GNUNET_assert (GNUNET_OK ==
     56                  TALER_string_to_amount ("CHF:1",
     57                                          &a));
     58   GNUNET_assert (0 == TALER_amount_cmp (&a,
     59                                         f));
     60   GNUNET_assert (NULL ==
     61                  TALER_amount_list_find (&al,
     62                                          "GBP"));
     63   /* ... unlike the accumulator, which answers "zero" */
     64   {
     65     struct TALER_AmountSet as = {
     66       .taa = al.tal,
     67       .taa_size = al.tal_len
     68     };
     69 
     70     f = TALER_amount_set_find ("GBP",
     71                                &as);
     72     GNUNET_assert (NULL != f);
     73     GNUNET_assert (TALER_amount_is_zero (f));
     74   }
     75 
     76   /* coverage */
     77   {
     78     const char *good[] = { "JPY", "EUR", "USD", "CHF" };
     79     const char *missing[] = { "EUR", "CHF", "USD" };
     80     const char *extra[] = { "EUR", "CHF", "USD", "JPY", "GBP" };
     81     const char *other[] = { "EUR", "CHF", "USD", "GBP" };
     82 
     83     GNUNET_assert (TALER_amount_list_covers (&al,
     84                                              good,
     85                                              4));
     86     GNUNET_assert (! TALER_amount_list_covers (&al,
     87                                                missing,
     88                                                3));
     89     GNUNET_assert (! TALER_amount_list_covers (&al,
     90                                                extra,
     91                                                5));
     92     GNUNET_assert (! TALER_amount_list_covers (&al,
     93                                                other,
     94                                                4));
     95   }
     96 
     97   /* copy is deep */
     98   TALER_amount_list_copy (&cp,
     99                           &al);
    100   GNUNET_assert (cp.tal != al.tal);
    101   GNUNET_assert (cp.tal_len == al.tal_len);
    102   GNUNET_assert (0 == strcmp (TALER_amount_list2s (&cp),
    103                               "EUR:1.1;CHF:1;USD:2;JPY:200"));
    104 
    105   /* multiplication applies to every currency */
    106   GNUNET_assert (GNUNET_OK ==
    107                  TALER_amount_list_multiply (&cp,
    108                                              3));
    109   GNUNET_assert (0 == strcmp ("EUR:3.3;CHF:3;USD:6;JPY:600",
    110                               TALER_amount_list2s (&cp)));
    111   /* a factor of one is a no-op, a factor of zero is refused */
    112   GNUNET_assert (GNUNET_OK ==
    113                  TALER_amount_list_multiply (&cp,
    114                                              1));
    115   GNUNET_assert (0 == strcmp ("EUR:3.3;CHF:3;USD:6;JPY:600",
    116                               TALER_amount_list2s (&cp)));
    117   GNUNET_assert (GNUNET_SYSERR ==
    118                  TALER_amount_list_multiply (&cp,
    119                                              0));
    120   TALER_amount_list_free (&cp);
    121 
    122   /* overflow in one currency must not multiply the others either */
    123   GNUNET_assert (GNUNET_OK ==
    124                  TALER_string_to_amount_list ("EUR:1;CHF:1000000000000",
    125                                               &cp));
    126   GNUNET_assert (GNUNET_SYSERR ==
    127                  TALER_amount_list_multiply (&cp,
    128                                              1000000));
    129   GNUNET_assert (0 == strcmp ("EUR:1;CHF:1000000000000",
    130                               TALER_amount_list2s (&cp)));
    131   TALER_amount_list_free (&cp);
    132 
    133   /* uniformity: all priced */
    134   GNUNET_assert (GNUNET_OK ==
    135                  TALER_amount_list_check_uniform (&al));
    136   TALER_amount_list_free (&al);
    137 
    138   /* uniformity: all free */
    139   GNUNET_assert (GNUNET_OK ==
    140                  TALER_string_to_amount_list ("EUR:0;CHF:0",
    141                                               &al));
    142   GNUNET_assert (GNUNET_NO ==
    143                  TALER_amount_list_check_uniform (&al));
    144   TALER_amount_list_free (&al);
    145 
    146   /* uniformity: mixed, the case the callers must refuse */
    147   GNUNET_assert (GNUNET_OK ==
    148                  TALER_string_to_amount_list ("EUR:1;CHF:0",
    149                                               &al));
    150   GNUNET_assert (GNUNET_SYSERR ==
    151                  TALER_amount_list_check_uniform (&al));
    152   TALER_amount_list_free (&al);
    153 
    154   /* the empty list is the canonical spelling of "free" */
    155   GNUNET_assert (GNUNET_OK ==
    156                  TALER_string_to_amount_list ("",
    157                                               &al));
    158   GNUNET_assert (0 == al.tal_len);
    159   GNUNET_assert (GNUNET_NO ==
    160                  TALER_amount_list_check_uniform (&al));
    161   GNUNET_assert (NULL ==
    162                  TALER_amount_list_find (&al,
    163                                          "EUR"));
    164   GNUNET_assert (0 == strcmp ("",
    165                               TALER_amount_list2s (&al)));
    166   GNUNET_assert (GNUNET_OK ==
    167                  TALER_amount_list_multiply (&al,
    168                                              7));
    169   TALER_amount_list_free (&al);
    170 
    171   /* a single amount is a valid one-element list, which is what
    172      keeps existing single-currency configurations working */
    173   GNUNET_assert (GNUNET_OK ==
    174                  TALER_string_to_amount_list ("EUR:1.1",
    175                                               &al));
    176   GNUNET_assert (1 == al.tal_len);
    177   TALER_amount_list_free (&al);
    178 
    179   /* rejected: a repeated currency ... */
    180   GNUNET_assert (GNUNET_SYSERR ==
    181                  TALER_string_to_amount_list ("EUR:1;CHF:1;EUR:2",
    182                                               &al));
    183   /* ... an empty component ... */
    184   GNUNET_assert (GNUNET_SYSERR ==
    185                  TALER_string_to_amount_list ("EUR:1;;CHF:1",
    186                                               &al));
    187   GNUNET_assert (GNUNET_SYSERR ==
    188                  TALER_string_to_amount_list ("EUR:1;",
    189                                               &al));
    190   /* ... and a malformed component */
    191   GNUNET_assert (GNUNET_SYSERR ==
    192                  TALER_string_to_amount_list ("EUR:1;CHF",
    193                                               &al));
    194   GNUNET_assert (GNUNET_SYSERR ==
    195                  TALER_string_to_amount_list ("EUR:1;CHF:x",
    196                                               &al));
    197   return 0;
    198 }
    199 
    200 
    201 int
    202 main (int argc,
    203       const char *const argv[])
    204 {
    205   struct TALER_Amount a1;
    206   struct TALER_Amount a2;
    207   struct TALER_Amount a3;
    208   struct TALER_Amount r;
    209   char *c;
    210 
    211   (void) argc;
    212   (void) argv;
    213   GNUNET_log_setup ("test-amout",
    214                     "WARNING",
    215                     NULL);
    216   /* test invalid conversions */
    217   GNUNET_log_skip (6, GNUNET_NO);
    218   /* non-numeric */
    219   GNUNET_assert (GNUNET_SYSERR ==
    220                  TALER_string_to_amount ("EUR:4a",
    221                                          &a1));
    222   /* non-numeric */
    223   GNUNET_assert (GNUNET_SYSERR ==
    224                  TALER_string_to_amount ("EUR:4.4a",
    225                                          &a1));
    226   /* non-numeric */
    227   GNUNET_assert (GNUNET_SYSERR ==
    228                  TALER_string_to_amount ("EUR:4.a4",
    229                                          &a1));
    230   /* no currency */
    231   GNUNET_assert (GNUNET_SYSERR ==
    232                  TALER_string_to_amount (":4.a4",
    233                                          &a1));
    234   /* precision too high */
    235   GNUNET_assert (GNUNET_SYSERR ==
    236                  TALER_string_to_amount ("EUR:4.123456789",
    237                                          &a1));
    238   /* value too big */
    239   GNUNET_assert (GNUNET_SYSERR ==
    240                  TALER_string_to_amount (
    241                    "EUR:1234567890123456789012345678901234567890123456789012345678901234567890",
    242                    &a1));
    243   GNUNET_log_skip (0, GNUNET_YES);
    244 
    245   /* test conversion without fraction */
    246   GNUNET_assert (GNUNET_OK ==
    247                  TALER_string_to_amount ("EUR:4",
    248                                          &a1));
    249   GNUNET_assert (0 == strcasecmp ("EUR",
    250                                   a1.currency));
    251   GNUNET_assert (4 == a1.value);
    252   GNUNET_assert (0 == a1.fraction);
    253 
    254   /* test conversion with leading zero in fraction */
    255   GNUNET_assert (GNUNET_OK ==
    256                  TALER_string_to_amount ("EUR:0.02",
    257                                          &a2));
    258   GNUNET_assert (0 == strcasecmp ("EUR",
    259                                   a2.currency));
    260   GNUNET_assert (0 == a2.value);
    261   GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 2 == a2.fraction);
    262   c = TALER_amount_to_string (&a2);
    263   GNUNET_assert (0 == strcasecmp ("EUR:0.02",
    264                                   c));
    265   GNUNET_free (c);
    266 
    267   /* test conversion with leading space and with fraction */
    268   GNUNET_assert (GNUNET_OK ==
    269                  TALER_string_to_amount (" EUR:4.12",
    270                                          &a2));
    271   GNUNET_assert (0 == strcasecmp ("EUR",
    272                                   a2.currency));
    273   GNUNET_assert (4 == a2.value);
    274   GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 12 == a2.fraction);
    275 
    276   /* test use of local currency */
    277   GNUNET_assert (GNUNET_OK ==
    278                  TALER_string_to_amount (" LOCAL:4444.1000",
    279                                          &a3));
    280   GNUNET_assert (0 == strcasecmp ("LOCAL",
    281                                   a3.currency));
    282   GNUNET_assert (4444 == a3.value);
    283   GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 10 == a3.fraction);
    284 
    285   /* test CMP with equal and unequal currencies */
    286   GNUNET_assert (GNUNET_NO ==
    287                  TALER_amount_cmp_currency (&a1,
    288                                             &a3));
    289   GNUNET_assert (GNUNET_YES ==
    290                  TALER_amount_cmp_currency (&a1,
    291                                             &a2));
    292 
    293   /* test subtraction failure (currency mismatch) */
    294   GNUNET_assert (TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE ==
    295                  TALER_amount_subtract (&a3,
    296                                         &a3,
    297                                         &a2));
    298   GNUNET_assert (GNUNET_SYSERR ==
    299                  TALER_amount_normalize (&a3));
    300 
    301   /* test subtraction failure (negative result) */
    302   GNUNET_assert (TALER_AAR_INVALID_NEGATIVE_RESULT ==
    303                  TALER_amount_subtract (&a3,
    304                                         &a1,
    305                                         &a2));
    306   GNUNET_assert (GNUNET_SYSERR ==
    307                  TALER_amount_normalize (&a3));
    308 
    309   /* test subtraction success cases */
    310   GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
    311                  TALER_amount_subtract (&a3,
    312                                         &a2,
    313                                         &a1));
    314   GNUNET_assert (TALER_AAR_RESULT_ZERO ==
    315                  TALER_amount_subtract (&a3,
    316                                         &a1,
    317                                         &a1));
    318   GNUNET_assert (0 == a3.value);
    319   GNUNET_assert (0 == a3.fraction);
    320   GNUNET_assert (GNUNET_NO ==
    321                  TALER_amount_normalize (&a3));
    322 
    323   /* test addition success */
    324   GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
    325                  TALER_amount_add (&a3,
    326                                    &a3,
    327                                    &a2));
    328   GNUNET_assert (GNUNET_NO ==
    329                  TALER_amount_normalize (&a3));
    330 
    331   /* test normalization */
    332   a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE;
    333   a3.value = 4;
    334   GNUNET_assert (GNUNET_YES ==
    335                  TALER_amount_normalize (&a3));
    336 
    337   /* test conversion to string */
    338   c = TALER_amount_to_string (&a3);
    339   GNUNET_assert (0 == strcmp ("EUR:6",
    340                               c));
    341   GNUNET_free (c);
    342 
    343   /* test normalization with fraction overflow */
    344   a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE + 1;
    345   a3.value = 4;
    346   GNUNET_assert (GNUNET_YES ==
    347                  TALER_amount_normalize (&a3));
    348   c = TALER_amount_to_string (&a3);
    349   GNUNET_assert (0 == strcmp ("EUR:6.00000001",
    350                               c));
    351   GNUNET_free (c);
    352 
    353   /* test normalization with overflow */
    354   a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE + 1;
    355   a3.value = UINT64_MAX - 1;
    356   GNUNET_assert (GNUNET_SYSERR ==
    357                  TALER_amount_normalize (&a3));
    358   c = TALER_amount_to_string (&a3);
    359   GNUNET_assert (NULL == c);
    360 
    361   /* test addition with overflow */
    362   a1.fraction = TALER_AMOUNT_FRAC_BASE - 1;
    363   a1.value = TALER_AMOUNT_MAX_VALUE - 5;
    364   a2.fraction = 2;
    365   a2.value = 5;
    366   GNUNET_assert (TALER_AAR_INVALID_RESULT_OVERFLOW ==
    367                  TALER_amount_add (&a3,
    368                                    &a1,
    369                                    &a2));
    370 
    371   /* test addition with underflow on fraction */
    372   a1.fraction = 1;
    373   a1.value = TALER_AMOUNT_MAX_VALUE;
    374   a2.fraction = 2;
    375   a2.value = 0;
    376   GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
    377                  TALER_amount_subtract (&a3,
    378                                         &a1,
    379                                         &a2));
    380   GNUNET_assert (TALER_AMOUNT_MAX_VALUE - 1 ==
    381                  a3.value);
    382   GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 ==
    383                  a3.fraction);
    384 
    385   /* test division */
    386   GNUNET_assert (GNUNET_OK ==
    387                  TALER_string_to_amount ("EUR:3.33",
    388                                          &a1));
    389   TALER_amount_divide (&a2,
    390                        &a1,
    391                        1);
    392   GNUNET_assert (0 == strcasecmp ("EUR",
    393                                   a2.currency));
    394   GNUNET_assert (3 == a2.value);
    395   GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 33 == a2.fraction);
    396 
    397   TALER_amount_divide (&a2,
    398                        &a1,
    399                        3);
    400   GNUNET_assert (0 == strcasecmp ("EUR",
    401                                   a2.currency));
    402   GNUNET_assert (1 == a2.value);
    403   GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 11 == a2.fraction);
    404 
    405   TALER_amount_divide (&a2,
    406                        &a1,
    407                        2);
    408   GNUNET_assert (0 == strcasecmp ("EUR",
    409                                   a2.currency));
    410   GNUNET_assert (1 == a2.value);
    411   GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 1000 * 665 == a2.fraction);
    412   TALER_amount_divide (&a2,
    413                        &a1,
    414                        TALER_AMOUNT_FRAC_BASE * 2);
    415   GNUNET_assert (0 == strcasecmp ("EUR",
    416                                   a2.currency));
    417   GNUNET_assert (0 == a2.value);
    418   GNUNET_assert (1 == a2.fraction);
    419 
    420   /* test rounding #1 */
    421   GNUNET_assert (GNUNET_OK ==
    422                  TALER_string_to_amount ("EUR:0.01",
    423                                          &r));
    424   GNUNET_assert (GNUNET_OK ==
    425                  TALER_string_to_amount ("EUR:4.001",
    426                                          &a1));
    427   GNUNET_assert (GNUNET_OK ==
    428                  TALER_string_to_amount ("EUR:4",
    429                                          &a2));
    430   GNUNET_assert (GNUNET_OK ==
    431                  TALER_amount_round_down (&a1,
    432                                           &r));
    433   GNUNET_assert (GNUNET_NO ==
    434                  TALER_amount_round_down (&a1,
    435                                           &r));
    436   GNUNET_assert (0 == TALER_amount_cmp (&a1,
    437                                         &a2));
    438 
    439   /* test rounding #2 */
    440   GNUNET_assert (GNUNET_OK ==
    441                  TALER_string_to_amount ("EUR:0.001",
    442                                          &r));
    443 
    444   GNUNET_assert (GNUNET_OK ==
    445                  TALER_string_to_amount ("EUR:4.001",
    446                                          &a1));
    447   GNUNET_assert (GNUNET_OK ==
    448                  TALER_string_to_amount ("EUR:4.001",
    449                                          &a2));
    450   GNUNET_assert (GNUNET_NO ==
    451                  TALER_amount_round_down (&a1,
    452                                           &r));
    453   GNUNET_assert (0 == TALER_amount_cmp (&a1,
    454                                         &a2));
    455 
    456   /* test rounding #3 */
    457   GNUNET_assert (GNUNET_OK ==
    458                  TALER_string_to_amount ("BTC:5",
    459                                          &r));
    460   GNUNET_assert (GNUNET_OK ==
    461                  TALER_string_to_amount ("BTC:12.3",
    462                                          &a1));
    463   GNUNET_assert (GNUNET_OK ==
    464                  TALER_string_to_amount ("BTC:10",
    465                                          &a2));
    466   GNUNET_assert (GNUNET_OK ==
    467                  TALER_amount_round_down (&a1,
    468                                           &r));
    469   GNUNET_assert (0 == TALER_amount_cmp (&a1,
    470                                         &a2));
    471 
    472   /* test multiplication */
    473   GNUNET_assert (GNUNET_OK ==
    474                  TALER_string_to_amount ("BTC:0",
    475                                          &a1));
    476   GNUNET_assert (TALER_AAR_RESULT_ZERO ==
    477                  TALER_amount_multiply (&a2,
    478                                         &a1,
    479                                         42));
    480   GNUNET_assert (0 == TALER_amount_cmp (&a1,
    481                                         &a2));
    482   GNUNET_assert (GNUNET_OK ==
    483                  TALER_string_to_amount ("BTC:5.001",
    484                                          &a1));
    485   GNUNET_assert (GNUNET_OK ==
    486                  TALER_string_to_amount ("BTC:5001",
    487                                          &r));
    488   GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
    489                  TALER_amount_multiply (&a2,
    490                                         &a1,
    491                                         1000));
    492   GNUNET_assert (0 == TALER_amount_cmp (&r,
    493                                         &a2));
    494   GNUNET_assert (1000 ==
    495                  TALER_amount_divide2 (&a2,
    496                                        &a1));
    497   GNUNET_assert (GNUNET_OK ==
    498                  TALER_string_to_amount ("BTC:5006.00099999",
    499                                          &r));
    500   GNUNET_assert (1000 ==
    501                  TALER_amount_divide2 (&r,
    502                                        &a1));
    503   GNUNET_assert (GNUNET_OK ==
    504                  TALER_string_to_amount ("BTC:5000.99999999",
    505                                          &r));
    506   GNUNET_assert (999 ==
    507                  TALER_amount_divide2 (&r,
    508                                        &a1));
    509   GNUNET_assert (GNUNET_OK ==
    510                  TALER_string_to_amount ("BTC:0",
    511                                          &a1));
    512   GNUNET_assert (INT_MAX ==
    513                  TALER_amount_divide2 (&a2,
    514                                        &a1));
    515   GNUNET_assert (0 ==
    516                  TALER_amount_divide2 (&a1,
    517                                        &a2));
    518   return test_amount_list ();
    519 }
    520 
    521 
    522 /* end of test_amount.c */