taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

routine.rs (49041B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{
     18     fmt::Debug,
     19     future::Future,
     20     time::{Duration, Instant},
     21 };
     22 
     23 use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as _};
     24 use axum::{Router, http::StatusCode};
     25 use jiff::{SignedDuration, Timestamp};
     26 use serde::de::DeserializeOwned;
     27 use taler_api::{
     28     crypto::{check_eddsa_signature, eddsa_sign},
     29     subject::fmt_in_subject,
     30 };
     31 use taler_common::{
     32     api::{
     33         EddsaPublicKey, HashCode, ShortHashCode,
     34         params::PageParams,
     35         prepared::{RegistrationResponse, TransferSubject, TransferType},
     36         revenue::RevenueIncomingHistory,
     37         wire::{
     38             IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferList,
     39             TransferRequest, TransferResponse, TransferState, TransferStatus,
     40         },
     41     },
     42     db::IncomingType,
     43     error_code::ErrorCode,
     44     types::{amount::amount, base32::Base32, payto::PaytoURI, url},
     45 };
     46 use tokio::time::sleep;
     47 
     48 use crate::{
     49     json,
     50     server::{TestResponse, TestServer as _},
     51 };
     52 
     53 const UNKNOWN: &str = "payto://malformed/unused?receiver-name=Malformed";
     54 
     55 pub trait Page: DeserializeOwned + Debug {
     56     fn ids(&self) -> Vec<i64>;
     57 }
     58 
     59 impl Page for IncomingHistory {
     60     fn ids(&self) -> Vec<i64> {
     61         self.incoming_transactions
     62             .iter()
     63             .map(|it| match it {
     64                 IncomingBankTransaction::Reserve { row_id, .. }
     65                 | IncomingBankTransaction::Wad { row_id, .. }
     66                 | IncomingBankTransaction::Kyc { row_id, .. } => *row_id as i64,
     67             })
     68             .collect()
     69     }
     70 }
     71 
     72 impl Page for OutgoingHistory {
     73     fn ids(&self) -> Vec<i64> {
     74         self.outgoing_transactions
     75             .iter()
     76             .map(|it| it.row_id as i64)
     77             .collect()
     78     }
     79 }
     80 
     81 impl Page for RevenueIncomingHistory {
     82     fn ids(&self) -> Vec<i64> {
     83         self.incoming_transactions
     84             .iter()
     85             .map(|it| it.row_id as i64)
     86             .collect()
     87     }
     88 }
     89 
     90 impl Page for TransferList {
     91     fn ids(&self) -> Vec<i64> {
     92         self.transfers.iter().map(|it| it.row_id as i64).collect()
     93     }
     94 }
     95 
     96 pub async fn latest_id<T: Page>(router: &Router) -> i64 {
     97     let res = router.get("?limit=-1").await;
     98     if res.status == StatusCode::NO_CONTENT {
     99         0
    100     } else {
    101         res.assert_ids::<T>(1)[0]
    102     }
    103 }
    104 
    105 pub async fn routine_pagination<T: Page>(
    106     server: &Router,
    107     mut register: Tasks<impl AsyncFnMut(usize)>,
    108 ) {
    109     // Check supported
    110     if !server.get("").await.is_implemented() {
    111         return;
    112     }
    113 
    114     // Check history is following specs
    115     let assert_history =
    116         async |args: &str, size: usize| server.get(format!("?{args}")).await.assert_ids::<T>(size);
    117     // Get latest registered id
    118     let latest_id = async || latest_id::<T>(server).await;
    119 
    120     for i in 0..20 {
    121         (register.lambda)(i).await;
    122     }
    123 
    124     let id = latest_id().await;
    125 
    126     // default
    127     assert_history("", 20).await;
    128 
    129     // forward range
    130     assert_history("limit=10", 10).await;
    131     assert_history("limit=10&offset=4", 10).await;
    132 
    133     // backward range
    134     assert_history("limit=-10", 10).await;
    135     assert_history(&format!("limit=-10&{}", id - 4), 10).await;
    136 }
    137 
    138 pub async fn assert_time<R: Debug>(range: std::ops::Range<u128>, task: impl Future<Output = R>) {
    139     let start = Instant::now();
    140     task.await;
    141     let elapsed = start.elapsed().as_millis();
    142     if !range.contains(&elapsed) {
    143         panic!("Expected to last {range:?} got {elapsed:?}")
    144     }
    145 }
    146 
    147 pub async fn routine_history<T: Page>(
    148     server: &Router,
    149     mut register: Tasks<impl AsyncFnMut(usize)>,
    150     mut ignore: Tasks<impl AsyncFnMut(usize)>,
    151 ) {
    152     // Check history is following specs
    153     macro_rules! assert_history {
    154         ($args:expr, $size:expr) => {
    155             async {
    156                 server
    157                     .get(&format!("?{}", $args))
    158                     .await
    159                     .assert_ids::<T>($size)
    160             }
    161         };
    162     }
    163     // Get latest registered id
    164     let latest_id = async || assert_history!("limit=-1", 1).await[0];
    165 
    166     // Check error when no transactions
    167     assert_history!("limit=7".to_owned(), 0).await;
    168 
    169     let mut register_iter = (0..register.len).peekable();
    170     let mut ignore_iter = (0..ignore.len).peekable();
    171     while register_iter.peek().is_some() || ignore_iter.peek().is_some() {
    172         if let Some(idx) = register_iter.next() {
    173             (register.lambda)(idx).await
    174         }
    175         if let Some(idx) = ignore_iter.next() {
    176             (ignore.lambda)(idx).await
    177         }
    178     }
    179     let nb_register = register.len;
    180     let nb_ignore = ignore.len;
    181     let nb_total = nb_register + nb_ignore;
    182 
    183     // Check ignored
    184     assert_history!(format_args!("limit={nb_total}"), nb_register).await;
    185     // Check skip ignored
    186     assert_history!(format_args!("limit={nb_register}"), nb_register).await;
    187 
    188     // Check no polling when we cannot have more transactions
    189     assert_time(
    190         0..200,
    191         assert_history!(
    192             format_args!("limit=-{}&timeout_ms=1000", nb_register + 1),
    193             nb_register
    194         ),
    195     )
    196     .await;
    197     // Check no polling when already find transactions even if less than delta
    198     assert_time(
    199         0..200,
    200         assert_history!(
    201             format_args!("limit={}&timeout_ms=1000", nb_register + 1),
    202             nb_register
    203         ),
    204     )
    205     .await;
    206 
    207     // Check polling
    208     let id = latest_id().await;
    209     tokio::join!(
    210         // Check polling succeed
    211         assert_time(
    212             100..400,
    213             assert_history!(format_args!("limit=2&offset={id}&timeout_ms=1000"), 1)
    214         ),
    215         assert_time(
    216             200..500,
    217             assert_history!(
    218                 format_args!(
    219                     "limit=1&offset={}&timeout_ms=200",
    220                     id as usize + nb_total * 3
    221                 ),
    222                 0
    223             )
    224         ),
    225         async {
    226             sleep(Duration::from_millis(100)).await;
    227             (register.lambda)(0).await
    228         }
    229     );
    230 
    231     // Test triggers
    232     for i in 0..register.len {
    233         let id = latest_id().await;
    234         tokio::join!(
    235             // Check polling succeed
    236             assert_time(
    237                 100..400,
    238                 assert_history!(format_args!("limit=7&offset={id}&timeout_ms=1000"), 1)
    239             ),
    240             async {
    241                 sleep(Duration::from_millis(100)).await;
    242                 (register.lambda)(i).await
    243             }
    244         );
    245     }
    246 
    247     // Test doesn't trigger
    248     let id = latest_id().await;
    249     tokio::join!(
    250         // Check polling succeed
    251         assert_time(
    252             200..500,
    253             assert_history!(format_args!("limit=7&offset={id}&timeout_ms=200"), 0)
    254         ),
    255         async {
    256             sleep(Duration::from_millis(100)).await;
    257             for i in 0..ignore.len {
    258                 (ignore.lambda)(i).await
    259             }
    260         }
    261     );
    262 
    263     routine_pagination::<T>(server, register).await;
    264 }
    265 
    266 impl TestResponse {
    267     #[track_caller]
    268     fn assert_ids<T: Page>(&self, size: usize) -> Vec<i64> {
    269         if size == 0 {
    270             self.assert_no_content();
    271             return vec![];
    272         }
    273         let body = self.assert_ok_json::<T>();
    274         let page = body.ids();
    275         let params = self.query::<PageParams>().check().unwrap();
    276 
    277         // testing the size is like expected
    278         assert_eq!(size, page.len(), "bad page length: {page:?}\n{body:?}");
    279         if params.limit < 0 {
    280             // testing that the first id is at most the 'offset' query param.
    281             assert!(
    282                 params
    283                     .offset
    284                     .map(|offset| page[0] <= offset)
    285                     .unwrap_or(true),
    286                 "bad page offset: {params:?} {page:?}"
    287             );
    288             // testing that the id decreases.
    289             assert!(
    290                 page.as_slice().is_sorted_by(|a, b| a > b),
    291                 "bad page order: {page:?}"
    292             )
    293         } else {
    294             // testing that the first id is at least the 'offset' query param.
    295             assert!(
    296                 params
    297                     .offset
    298                     .map(|offset| page[0] >= offset)
    299                     .unwrap_or(true),
    300                 "bad page offset: {params:?} {page:?}"
    301             );
    302             // testing that the id increases.
    303             assert!(page.as_slice().is_sorted(), "bad page order: {page:?}")
    304         }
    305         page
    306     }
    307 }
    308 
    309 // Get currency from config
    310 async fn get_currency(server: &Router) -> String {
    311     let config = server
    312         .get("/config")
    313         .await
    314         .assert_ok_json::<serde_json::Value>();
    315     let currency = config["currency"].as_str().unwrap();
    316     currency.to_owned()
    317 }
    318 
    319 /// Test standard behavior of the transfer endpoints
    320 pub async fn transfer_routine(
    321     wire_gateway: &Router,
    322     default_status: TransferState,
    323     credit_account: &PaytoURI,
    324 ) {
    325     let currency = &get_currency(wire_gateway).await;
    326     let default_amount = amount(format!("{currency}:42"));
    327     let request_uid = HashCode::rand();
    328     let wtid = ShortHashCode::rand();
    329     let valid_req = json!({
    330         "request_uid": request_uid,
    331         "amount": default_amount,
    332         "exchange_base_url": "http://exchange.taler/",
    333         "wtid": wtid,
    334         "credit_account": credit_account,
    335     });
    336 
    337     // Check empty db
    338     {
    339         wire_gateway.get("/transfers").await.assert_no_content();
    340         wire_gateway
    341             .get(format!("/transfers?status={}", default_status.as_ref()))
    342             .await
    343             .assert_no_content();
    344     }
    345 
    346     // TODO check subject formatting
    347 
    348     let routine = async |req: &TransferRequest| {
    349         // Check OK
    350         let first = wire_gateway
    351             .post("/transfer")
    352             .json(req)
    353             .await
    354             .assert_ok_json::<TransferResponse>();
    355         // Check idempotent
    356         let second = wire_gateway
    357             .post("/transfer")
    358             .json(req)
    359             .await
    360             .assert_ok_json::<TransferResponse>();
    361         assert_eq!(first.row_id, second.row_id);
    362         assert_eq!(first.timestamp, second.timestamp);
    363 
    364         // Check by id
    365         let tx = wire_gateway
    366             .get(format!("/transfers/{}", first.row_id))
    367             .await
    368             .assert_ok_json::<TransferStatus>();
    369         assert_eq!(default_status, tx.status);
    370         assert_eq!(default_amount, tx.amount);
    371         assert_eq!("http://exchange.taler/", tx.origin_exchange_url);
    372         assert_eq!(req.wtid, tx.wtid);
    373         assert_eq!(first.timestamp, tx.timestamp);
    374         assert_eq!(req.metadata, tx.metadata);
    375         assert_eq!(credit_account, &tx.credit_account);
    376 
    377         // Check page
    378         let list = wire_gateway
    379             .get("/transfers?limit=-1")
    380             .await
    381             .assert_ok_json::<TransferList>();
    382         let tx = &list.transfers[0];
    383         assert_eq!(first.row_id, tx.row_id);
    384         assert_eq!(default_status, tx.status);
    385         assert_eq!(default_amount, tx.amount);
    386         assert_eq!(first.timestamp, tx.timestamp);
    387         assert_eq!(credit_account, &tx.credit_account);
    388     };
    389 
    390     let req = TransferRequest {
    391         request_uid,
    392         amount: default_amount,
    393         exchange_base_url: url("http://exchange.taler/"),
    394         metadata: None,
    395         wtid,
    396         credit_account: credit_account.clone(),
    397     };
    398     // Simple
    399     routine(&req).await;
    400     // With metadata
    401     routine(&TransferRequest {
    402         request_uid: HashCode::rand(),
    403         wtid: ShortHashCode::rand(),
    404         metadata: Some("test:medatata".into()),
    405         ..req
    406     })
    407     .await;
    408 
    409     // Check create transfer errors
    410     {
    411         // Check request uid reuse
    412         wire_gateway
    413             .post("/transfer")
    414             .json(&json!(valid_req + {
    415                 "wtid": ShortHashCode::rand()
    416             }))
    417             .await
    418             .assert_error(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED);
    419         // Check wtid reuse
    420         wire_gateway
    421             .post("/transfer")
    422             .json(&json!(valid_req + {
    423                 "request_uid": HashCode::rand(),
    424             }))
    425             .await
    426             .assert_error(ErrorCode::BANK_TRANSFER_WTID_REUSED);
    427 
    428         // Check currency mismatch
    429         wire_gateway
    430             .post("/transfer")
    431             .json(&json!(valid_req + {
    432                 "amount": "BAD:42"
    433             }))
    434             .await
    435             .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH);
    436 
    437         // Base Base32
    438         wire_gateway
    439             .post("/transfer")
    440             .json(&json!(valid_req + {
    441                 "wtid": "I love chocolate"
    442             }))
    443             .await
    444             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    445         wire_gateway
    446             .post("/transfer")
    447             .json(&json!(valid_req + {
    448                 "wtid": Base32::<31>::rand()
    449             }))
    450             .await
    451             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    452         wire_gateway
    453             .post("/transfer")
    454             .json(&json!(valid_req + {
    455                 "request_uid": "I love chocolate"
    456             }))
    457             .await
    458             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    459         wire_gateway
    460             .post("/transfer")
    461             .json(&json!(valid_req + {
    462                 "request_uid": Base32::<65>::rand()
    463             }))
    464             .await
    465             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    466 
    467         // Missing receiver-name
    468         let res = wire_gateway
    469             .post("/transfer")
    470             .json(&json!(valid_req + {
    471                 "credit_account": credit_account.as_ref().as_str().split('?').next().unwrap()
    472             }))
    473             .await;
    474         if !res.status.is_success() {
    475             res.assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    476         }
    477 
    478         // Unsupported payto kind
    479         wire_gateway
    480             .post("/transfer")
    481             .json(&json!(valid_req + { "credit_account": UNKNOWN }))
    482             .await
    483             .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    484         // Malformed payto
    485         wire_gateway
    486             .post("/transfer")
    487             .json(&json!(valid_req + { "credit_account": "http://email@test.com" }))
    488             .await
    489             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    490 
    491         // Bad base URL
    492         for base_url in [
    493             "not-a-url",
    494             "file://not.http.com/",
    495             "no.transport.com/",
    496             "https://not.a/base/url",
    497         ] {
    498             wire_gateway
    499                 .post("/transfer")
    500                 .json(&json!(valid_req + { "exchange_base_url": base_url }))
    501                 .await
    502                 .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    503         }
    504 
    505         // Malformed metadata
    506         for metadata in ["bad_id", "bad id", "bad@id.com", &"A".repeat(41)] {
    507             wire_gateway
    508                 .post("/transfer")
    509                 .json(&json!(valid_req + { "metadata": metadata }))
    510                 .await
    511                 .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    512         }
    513     }
    514 
    515     // Check transfer by id errors
    516     {
    517         // Check unknown transaction
    518         wire_gateway
    519             .get("/transfers/42")
    520             .await
    521             .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
    522     }
    523 
    524     // Check transfer page
    525     {
    526         for _ in 0..4 {
    527             wire_gateway
    528                 .post("/transfer")
    529                 .json(&json!(valid_req + {
    530                     "request_uid": HashCode::rand(),
    531                     "wtid": ShortHashCode::rand(),
    532                 }))
    533                 .await
    534                 .assert_ok_json::<TransferResponse>();
    535         }
    536         {
    537             let list = wire_gateway
    538                 .get("/transfers")
    539                 .await
    540                 .assert_ok_json::<TransferList>();
    541             assert_eq!(list.transfers.len(), 6);
    542             assert_eq!(
    543                 list,
    544                 wire_gateway
    545                     .get(format!("/transfers?status={}", default_status.as_ref()))
    546                     .await
    547                     .assert_ok_json::<TransferList>()
    548             )
    549         }
    550 
    551         // Pagination test
    552         routine_pagination::<TransferList>(
    553             &wire_gateway.suffix("/transfers"),
    554             crate::tasks!({
    555                 wire_gateway
    556                     .post("/transfer")
    557                     .json(json!({
    558                         "request_uid": HashCode::rand(),
    559                         "amount": amount(format!("{currency}:0.1")),
    560                         "exchange_base_url": url("http://exchange.taler"),
    561                         "wtid": ShortHashCode::rand(),
    562                         "credit_account": credit_account,
    563                     }))
    564                     .await
    565                     .assert_ok_json::<TransferResponse>();
    566             }),
    567         )
    568         .await;
    569     }
    570 }
    571 
    572 async fn add_incoming_routine(
    573     wire_gateway: &Router,
    574     prepared_transfer: &Router,
    575     currency: &str,
    576     kind: IncomingType,
    577     debit_acount: &PaytoURI,
    578     credit_account: &PaytoURI,
    579 ) {
    580     let (path, key) = match kind {
    581         IncomingType::reserve => ("/admin/add-incoming", "reserve_pub"),
    582         IncomingType::kyc => ("/admin/add-kycauth", "account_pub"),
    583         IncomingType::map => ("/admin/add-mapped", "authorization_pub"),
    584     };
    585     let key_pair = Ed25519KeyPair::generate().unwrap();
    586     let pub_key = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
    587     // Valid
    588     prepared_transfer
    589         .post("/registration")
    590         .json(json!({
    591             "credit_account": credit_account,
    592             "type": "reserve",
    593             "recurrent": false,
    594             "credit_amount": format!("{currency}:44"),
    595             "alg": "EdDSA",
    596             "account_pub": pub_key,
    597             "authorization_pub": pub_key,
    598             "authorization_sig": eddsa_sign(&key_pair, pub_key.as_ref()),
    599         }))
    600         .await
    601         .assert_ok_json::<RegistrationResponse>();
    602     let valid_req = json!({
    603         "amount": format!("{currency}:44"),
    604         key: pub_key,
    605         "debit_account": debit_acount,
    606     });
    607 
    608     // Check OK
    609     wire_gateway.post(path).json(&valid_req).await.assert_ok();
    610 
    611     match kind {
    612         IncomingType::reserve => {
    613             // Trigger conflict due to reused reserve_pub
    614             wire_gateway
    615                 .post(path)
    616                 .json(&json!(valid_req + {
    617                     "amount": format!("{currency}:44.1"),
    618                 }))
    619                 .await
    620                 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT)
    621         }
    622         IncomingType::kyc => {
    623             // Non conflict on reuse
    624             wire_gateway.post(path).json(&valid_req).await.assert_ok();
    625         }
    626         IncomingType::map => {
    627             // Trigger conflict due to reused authorization_pub
    628             wire_gateway
    629                 .post(path)
    630                 .json(&valid_req)
    631                 .await
    632                 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
    633             // Trigger conflict due to unknown authorization_pub
    634             wire_gateway
    635                 .post(path)
    636                 .json(&json!(valid_req + {
    637                    key: EddsaPublicKey::rand()
    638                 }))
    639                 .await
    640                 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_UNKNOWN);
    641         }
    642     }
    643 
    644     // Currency mismatch
    645     wire_gateway
    646         .post(path)
    647         .json(&json!(valid_req + { "amount": "BAD:33" }))
    648         .await
    649         .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH);
    650 
    651     // Bad BASE32 reserve_pub
    652     wire_gateway
    653         .post(path)
    654         .json(&json!(valid_req + { key: "I love chocolate" }))
    655         .await
    656         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    657     wire_gateway
    658         .post(path)
    659         .json(&json!(valid_req + { key: Base32::<31>::rand() }))
    660         .await
    661         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    662 
    663     // Unsupported payto kind
    664     wire_gateway
    665         .post(path)
    666         .json(&json!(valid_req + { "debit_account": UNKNOWN }))
    667         .await
    668         .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    669 
    670     // Malformed payto
    671     wire_gateway
    672         .post(path)
    673         .json(&json!(valid_req + { "debit_account": "http://email@test.com" }))
    674         .await
    675         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    676 }
    677 
    678 pub struct Tasks<F: AsyncFnMut(usize)> {
    679     pub len: usize,
    680     pub lambda: F,
    681 }
    682 
    683 #[macro_export]
    684 macro_rules! tasks {
    685     // Create new
    686     ( $( $(if $cond:expr =>)? $body:block ),* $(,)? ) => {
    687         $crate::tasks!(@build 0usize;
    688             $( $(if $cond =>)? $body ),*
    689         )
    690     };
    691 
    692     // Append to existing
    693     ( $existing:expr ; $( $(if $cond:expr =>)? $body:block ),* $(,)? ) => {{
    694         let mut existing = $existing;
    695         let mut extra = $crate::tasks![
    696             $( $(if $cond =>)? $body ),*
    697         ];
    698 
    699         $crate::routine::Tasks {
    700             len: existing.len + extra.len,
    701             lambda: async move |i: usize| {
    702                 if existing.len == 0 && extra.len == 0 {
    703                     return;
    704                 }
    705 
    706                 let i = i % (existing.len + extra.len);
    707 
    708                 if i < existing.len {
    709                     (existing.lambda)(i).await;
    710                 } else {
    711                     (extra.lambda)(i - existing.len).await;
    712                 }
    713             }
    714         }
    715     }};
    716 
    717     // Internal builder
    718     (@build $idx:expr; $( $(if $cond:expr =>)? $body:block ),* ) => {{
    719         let conditions = [ $( true $( && $cond )? ),* ];
    720         let len = conditions.iter().filter(|&&x| x).count();
    721 
    722         $crate::routine::Tasks {
    723             len,
    724             lambda: async move |i: usize| {
    725                 if len == 0 {
    726                     return;
    727                 }
    728 
    729                 let i = i % len;
    730                 let mut current = 0usize;
    731 
    732                 $crate::tasks!(
    733                     @dispatch i, current, conditions, 0usize;
    734                     $( $(if $cond =>)? $body ),*
    735                 );
    736 
    737                 let _ = (i, &mut current); // suppress lints
    738 
    739                 unreachable!()
    740             }
    741         }
    742     }};
    743 
    744     // Recursive dispatcher
    745     (@dispatch $i:expr, $current:ident, $conditions:ident, $idx:expr;) => {};
    746 
    747     (@dispatch
    748         $i:expr,
    749         $current:ident,
    750         $conditions:ident,
    751         $idx:expr;
    752         $(if $cond:expr =>)? $body:block
    753         $(, $($rest:tt)*)?
    754     ) => {{
    755         if $conditions[$idx] {
    756             if $i == $current {
    757                 (async $body).await;
    758                 return;
    759             }
    760             $current += 1;
    761         }
    762 
    763         $crate::tasks!(
    764             @dispatch
    765             $i,
    766             $current,
    767             $conditions,
    768             $idx + 1usize;
    769             $($($rest)*)?
    770         );
    771     }};
    772 }
    773 
    774 /// Test standard behavior of the revenue endpoints
    775 pub async fn revenue_routine(
    776     wire_gateway: &Router,
    777     revenue_api: &Router,
    778     debit_acount: &PaytoURI,
    779     kyc: bool,
    780     register: Tasks<impl AsyncFnMut(usize)>,
    781     ignore: Tasks<impl AsyncFnMut(usize)>,
    782 ) {
    783     let currency = &get_currency(revenue_api).await;
    784     routine_history::<RevenueIncomingHistory>(
    785         &revenue_api.suffix("/history"),
    786         tasks!(register;
    787             {
    788                 wire_gateway
    789                         .post("/admin/add-incoming")
    790                         .json(json!({
    791                             "amount": format!("{currency}:1"),
    792                             "reserve_pub": EddsaPublicKey::rand(),
    793                             "debit_account": debit_acount,
    794                         }))
    795                         .await
    796                         .assert_ok_json::<TransferResponse>();
    797             },
    798             if kyc => {
    799                 wire_gateway
    800                         .post("/admin/add-kycauth")
    801                         .json(json!({
    802                             "amount": format!("{currency}:2"),
    803                             "account_pub": EddsaPublicKey::rand(),
    804                             "debit_account": debit_acount,
    805                         }))
    806                         .await
    807                         .assert_ok_json::<TransferResponse>();
    808             }
    809         ),
    810         ignore,
    811     )
    812     .await;
    813 }
    814 
    815 /// Test standard behavior of the outgoing history endpoint
    816 pub async fn out_history_routine(
    817     wire_gateway: &Router,
    818     register: Tasks<impl AsyncFnMut(usize)>,
    819     ignore: Tasks<impl AsyncFnMut(usize)>,
    820 ) {
    821     routine_history::<OutgoingHistory>(&wire_gateway.suffix("/history/outgoing"), register, ignore)
    822         .await;
    823 }
    824 
    825 /// Test standard behavior of the incoming history endpoint
    826 pub async fn in_history_routine(
    827     wire_gateway: &Router,
    828     prepared_transfer: &Router,
    829     debit_account: &PaytoURI,
    830     credit_account: &PaytoURI,
    831     kyc: bool,
    832     register: Tasks<impl AsyncFnMut(usize)>,
    833     ignored: Tasks<impl AsyncFnMut(usize)>,
    834 ) {
    835     let currency = &get_currency(wire_gateway).await;
    836     let mut key = Ed25519KeyPair::generate().unwrap();
    837 
    838     routine_history::<IncomingHistory>(
    839         &wire_gateway.suffix("/history/incoming"),
    840         tasks!(register;
    841             {
    842                 wire_gateway
    843                     .post("/admin/add-incoming")
    844                     .json(json!({
    845                         "amount": format!("{currency}:1"),
    846                         "reserve_pub": EddsaPublicKey::rand(),
    847                         "debit_account": debit_account,
    848                     }))
    849                     .await
    850                     .assert_ok_json::<TransferResponse>();
    851             },
    852             {
    853                 key = Ed25519KeyPair::generate().unwrap();
    854                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    855                 let reserve_pub = EddsaPublicKey::rand();
    856                 let amount = format!("{currency}:2");
    857                 prepared_transfer
    858                     .post("/registration")
    859                     .json(json!({
    860                         "credit_account": credit_account,
    861                         "credit_amount": amount,
    862                         "type": "reserve",
    863                         "alg": "EdDSA",
    864                         "account_pub": reserve_pub,
    865                         "authorization_pub": auth_pub,
    866                         "authorization_sig": eddsa_sign(&key, reserve_pub.as_ref()),
    867                         "recurrent": true
    868                     }))
    869                     .await
    870                     .assert_ok_json::<RegistrationResponse>();
    871                 wire_gateway
    872                     .post("/admin/add-mapped")
    873                     .json(json!({
    874                         "amount": amount,
    875                         "authorization_pub": auth_pub,
    876                         "debit_account": debit_account,
    877                     }))
    878                     .await
    879                     .assert_ok_json::<TransferResponse>();
    880                 wire_gateway
    881                     .post("/admin/add-mapped")
    882                     .json(json!({
    883                         "amount": amount,
    884                         "authorization_pub": auth_pub,
    885                         "debit_account": debit_account,
    886                     }))
    887                     .await
    888                     .assert_ok_json::<TransferResponse>();
    889             },
    890             {
    891                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    892                 let reserve_pub = EddsaPublicKey::rand();
    893                 prepared_transfer
    894                     .post("/registration")
    895                     .json(json!({
    896                         "credit_account": credit_account,
    897                         "credit_amount": format!("{currency}:3"),
    898                         "type": "reserve",
    899                         "alg": "EdDSA",
    900                         "account_pub": reserve_pub,
    901                         "authorization_pub": auth_pub,
    902                         "authorization_sig": eddsa_sign(&key, reserve_pub.as_ref()),
    903                         "recurrent": true
    904                     }))
    905                     .await
    906                     .assert_ok_json::<RegistrationResponse>();
    907             },
    908             if kyc => {
    909                 wire_gateway
    910                     .post("/admin/add-kycauth")
    911                     .json(json!({
    912                         "amount": format!("{currency}:4"),
    913                         "account_pub": EddsaPublicKey::rand(),
    914                         "debit_account": debit_account,
    915                     }))
    916                     .await
    917                     .assert_ok_json::<TransferResponse>();
    918             },
    919             if kyc => {
    920                 key = Ed25519KeyPair::generate().unwrap();
    921                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    922                 let account_pub = EddsaPublicKey::rand();
    923                 let amount = format!("{currency}:5");
    924                 prepared_transfer
    925                     .post("/registration")
    926                     .json(json!({
    927                         "credit_account": credit_account,
    928                         "credit_amount": amount,
    929                         "type": "kyc",
    930                         "alg": "EdDSA",
    931                         "account_pub": account_pub,
    932                         "authorization_pub": auth_pub,
    933                         "authorization_sig": eddsa_sign(&key, account_pub.as_ref()),
    934                         "recurrent": true
    935                     }))
    936                     .await
    937                     .assert_ok_json::<RegistrationResponse>();
    938                 wire_gateway
    939                     .post("/admin/add-mapped")
    940                     .json(json!({
    941                         "amount": amount,
    942                         "authorization_pub": auth_pub,
    943                         "debit_account": debit_account,
    944                     }))
    945                     .await
    946                     .assert_ok_json::<TransferResponse>();
    947                 wire_gateway
    948                     .post("/admin/add-mapped")
    949                     .json(json!({
    950                         "amount": amount,
    951                         "authorization_pub": auth_pub,
    952                         "debit_account": debit_account,
    953                     }))
    954                     .await
    955                     .assert_ok_json::<TransferResponse>();
    956             },
    957             if kyc => {
    958                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    959                 let account_pub = EddsaPublicKey::rand();
    960                 prepared_transfer
    961                     .post("/registration")
    962                     .json(json!({
    963                         "credit_account": credit_account,
    964                         "credit_amount": format!("{currency}:6"),
    965                         "type": "kyc",
    966                         "alg": "EdDSA",
    967                         "account_pub": account_pub,
    968                         "authorization_pub": auth_pub,
    969                         "authorization_sig": eddsa_sign(&key, account_pub.as_ref()),
    970                         "recurrent": true
    971                     }))
    972                     .await
    973                     .assert_ok_json::<RegistrationResponse>();
    974             }
    975         ),
    976         ignored,
    977     )
    978     .await;
    979 }
    980 
    981 /// Test standard behavior of the admin add incoming endpoints
    982 pub async fn admin_add_incoming_routine(
    983     wire_gateway: &Router,
    984     prepared_transfer: &Router,
    985     debit_acount: &PaytoURI,
    986     credit_account: &PaytoURI,
    987     kyc: bool,
    988 ) {
    989     let currency = &get_currency(wire_gateway).await;
    990     add_incoming_routine(
    991         wire_gateway,
    992         prepared_transfer,
    993         currency,
    994         IncomingType::reserve,
    995         debit_acount,
    996         credit_account,
    997     )
    998     .await;
    999     add_incoming_routine(
   1000         wire_gateway,
   1001         prepared_transfer,
   1002         currency,
   1003         IncomingType::map,
   1004         debit_acount,
   1005         credit_account,
   1006     )
   1007     .await;
   1008     if kyc {
   1009         add_incoming_routine(
   1010             wire_gateway,
   1011             prepared_transfer,
   1012             currency,
   1013             IncomingType::kyc,
   1014             debit_acount,
   1015             credit_account,
   1016         )
   1017         .await;
   1018     }
   1019 }
   1020 
   1021 #[derive(Debug, PartialEq, Eq)]
   1022 pub enum Status {
   1023     Simple,
   1024     Pending,
   1025     Bounced,
   1026     Incomplete,
   1027     Reserve(EddsaPublicKey),
   1028     Kyc(EddsaPublicKey),
   1029 }
   1030 
   1031 /// Test standard registration behavior of the registration endpoints
   1032 pub async fn registration_routine<F1: Future<Output = Vec<Status>>>(
   1033     wire_gateway: &Router,
   1034     prepared_transfer: &Router,
   1035     debit_acount: &PaytoURI,
   1036     credit_account: &PaytoURI,
   1037     unknown_account: &PaytoURI,
   1038     mut in_status: impl FnMut() -> F1,
   1039 ) {
   1040     pub use Status::*;
   1041     let mut check_in = async |state: &[Status]| {
   1042         let current = in_status().await;
   1043         assert_eq!(state, current);
   1044     };
   1045 
   1046     let currency = &get_currency(wire_gateway).await;
   1047     let amount = amount(format!("{currency}:42"));
   1048     let key_pair1 = Ed25519KeyPair::generate().unwrap();
   1049     let auth_pub1 = EddsaPublicKey::try_from(key_pair1.public_key().as_ref()).unwrap();
   1050     let req = json!({
   1051         "credit_account": credit_account,
   1052         "type": "reserve",
   1053         "recurrent": false,
   1054         "credit_amount": amount,
   1055         "alg": "EdDSA",
   1056         "account_pub": auth_pub1,
   1057         "authorization_pub": auth_pub1,
   1058         "authorization_sig": eddsa_sign(&key_pair1, auth_pub1.as_ref()),
   1059     });
   1060 
   1061     let register = async |auth_pub: &EddsaPublicKey| {
   1062         wire_gateway
   1063             .post("/admin/add-mapped")
   1064             .json(json!({
   1065                 "amount": format!("{currency}:42"),
   1066                 "authorization_pub": auth_pub,
   1067                 "debit_account": debit_acount,
   1068             }))
   1069             .await
   1070     };
   1071 
   1072     /* ----- Registration ----- */
   1073     let routine = async |ty: TransferType,
   1074                          account_pub: &EddsaPublicKey,
   1075                          recurrent: bool,
   1076                          fmt: IncomingType| {
   1077         let req = json!(req + {
   1078             "type": ty,
   1079             "account_pub": account_pub,
   1080             "authorization_pub": auth_pub1,
   1081             "authorization_sig": eddsa_sign(&key_pair1, account_pub.as_ref()),
   1082             "recurrent": recurrent
   1083         });
   1084         // Valid
   1085         let res = prepared_transfer
   1086             .post("/registration")
   1087             .json(&req)
   1088             .await
   1089             .assert_ok_json::<RegistrationResponse>();
   1090 
   1091         // Idempotent
   1092         assert_eq!(
   1093             res,
   1094             prepared_transfer
   1095                 .post("/registration")
   1096                 .json(&req)
   1097                 .await
   1098                 .assert_ok_json::<RegistrationResponse>()
   1099         );
   1100 
   1101         assert!(!res.subjects.is_empty());
   1102 
   1103         for sub in res.subjects {
   1104             if let TransferSubject::Simple { subject, .. } = sub {
   1105                 assert_eq!(subject, fmt_in_subject(fmt, &auth_pub1).to_string());
   1106             };
   1107         }
   1108     };
   1109     for ty in [TransferType::reserve, TransferType::kyc] {
   1110         routine(ty, &auth_pub1, false, ty.into()).await;
   1111         routine(ty, &auth_pub1, true, IncomingType::map).await;
   1112     }
   1113 
   1114     let acc_pub1 = EddsaPublicKey::rand();
   1115     for ty in [TransferType::reserve, TransferType::kyc] {
   1116         routine(ty, &acc_pub1, false, IncomingType::map).await;
   1117         routine(ty, &acc_pub1, true, IncomingType::map).await;
   1118     }
   1119 
   1120     // Bad signature
   1121     prepared_transfer
   1122         .post("/registration")
   1123         .json(&json!(req + { "authorization_sig": eddsa_sign(&key_pair1, b"lol")}))
   1124         .await
   1125         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1126 
   1127     // Unknown account
   1128     prepared_transfer
   1129         .post("/registration")
   1130         .json(&json!(req + { "credit_account": unknown_account }))
   1131         .await
   1132         .assert_error(ErrorCode::BANK_UNKNOWN_CREDITOR);
   1133 
   1134     // Unsupported payto kind
   1135     prepared_transfer
   1136         .post("/registration")
   1137         .json(&json!(req + { "credit_account": UNKNOWN }))
   1138         .await
   1139         .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
   1140 
   1141     // Malformed payto
   1142     prepared_transfer
   1143         .post("/registration")
   1144         .json(&json!(req + {"credit_account": "http://email@test.com" }))
   1145         .await
   1146         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
   1147 
   1148     // Reserve pub reuse
   1149     prepared_transfer
   1150         .post("/registration")
   1151         .json(&json!(req + {
   1152             "account_pub": acc_pub1,
   1153             "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()),
   1154         }))
   1155         .await
   1156         .assert_ok_json::<RegistrationResponse>();
   1157     {
   1158         let key_pair = Ed25519KeyPair::generate().unwrap();
   1159         let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
   1160         prepared_transfer
   1161             .post("/registration")
   1162             .json(&json!(req + {
   1163                 "account_pub": acc_pub1,
   1164                 "authorization_pub": auth_pub,
   1165                 "authorization_sig": eddsa_sign(&key_pair, acc_pub1.as_ref()),
   1166             }))
   1167             .await
   1168             .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT);
   1169     }
   1170 
   1171     // Non recurrent accept one then bounce
   1172     prepared_transfer
   1173         .post("/registration")
   1174         .json(&json!(req + {
   1175             "account_pub": acc_pub1,
   1176             "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()),
   1177         }))
   1178         .await
   1179         .assert_ok_json::<RegistrationResponse>();
   1180     register(&auth_pub1)
   1181         .await
   1182         .assert_ok_json::<TransferResponse>();
   1183     check_in(&[Reserve(acc_pub1.clone())]).await;
   1184     register(&auth_pub1)
   1185         .await
   1186         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1187 
   1188     // Again without using mapping
   1189     let acc_pub2 = EddsaPublicKey::rand();
   1190     prepared_transfer
   1191         .post("/registration")
   1192         .json(&json!(req + {
   1193             "account_pub": acc_pub2,
   1194             "authorization_sig": eddsa_sign(&key_pair1, acc_pub2.as_ref()),
   1195         }))
   1196         .await
   1197         .assert_ok_json::<RegistrationResponse>();
   1198     wire_gateway
   1199         .post("/admin/add-incoming")
   1200         .json(json!({
   1201             "amount": amount,
   1202             "reserve_pub": acc_pub2,
   1203             "debit_account": debit_acount,
   1204         }))
   1205         .await
   1206         .assert_ok();
   1207     register(&auth_pub1)
   1208         .await
   1209         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1210     check_in(&[Reserve(acc_pub1.clone()), Reserve(acc_pub2.clone())]).await;
   1211 
   1212     // Recurrent accept one and delay others
   1213     let acc_pub3 = EddsaPublicKey::rand();
   1214     prepared_transfer
   1215         .post("/registration")
   1216         .json(&json!(req + {
   1217             "account_pub": acc_pub3,
   1218             "authorization_sig": eddsa_sign(&key_pair1, acc_pub3.as_ref()),
   1219             "recurrent": true
   1220         }))
   1221         .await
   1222         .assert_ok_json::<RegistrationResponse>();
   1223     for _ in 0..5 {
   1224         register(&auth_pub1)
   1225             .await
   1226             .assert_ok_json::<TransferResponse>();
   1227     }
   1228     check_in(&[
   1229         Reserve(acc_pub1.clone()),
   1230         Reserve(acc_pub2.clone()),
   1231         Reserve(acc_pub3.clone()),
   1232         Pending,
   1233         Pending,
   1234         Pending,
   1235         Pending,
   1236     ])
   1237     .await;
   1238 
   1239     // Complete pending on recurrent update
   1240     let acc_pub4 = EddsaPublicKey::rand();
   1241     prepared_transfer
   1242         .post("/registration")
   1243         .json(&json!(req + {
   1244             "type": "kyc",
   1245             "account_pub": acc_pub4,
   1246             "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()),
   1247             "recurrent": true
   1248         }))
   1249         .await
   1250         .assert_ok_json::<RegistrationResponse>();
   1251     prepared_transfer
   1252         .post("/registration")
   1253         .json(&json!(req + {
   1254             "account_pub": acc_pub4,
   1255             "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()),
   1256             "recurrent": true
   1257         }))
   1258         .await
   1259         .assert_ok_json::<RegistrationResponse>();
   1260     check_in(&[
   1261         Reserve(acc_pub1.clone()),
   1262         Reserve(acc_pub2.clone()),
   1263         Reserve(acc_pub3.clone()),
   1264         Kyc(acc_pub4.clone()),
   1265         Reserve(acc_pub4.clone()),
   1266         Pending,
   1267         Pending,
   1268     ])
   1269     .await;
   1270 
   1271     // Kyc key reuse keep pending ones
   1272     wire_gateway
   1273         .post("/admin/add-kycauth")
   1274         .json(json!({
   1275             "amount": amount,
   1276             "account_pub": acc_pub4,
   1277             "debit_account": debit_acount,
   1278         }))
   1279         .await
   1280         .assert_ok_json::<TransferResponse>();
   1281     check_in(&[
   1282         Reserve(acc_pub1.clone()),
   1283         Reserve(acc_pub2.clone()),
   1284         Reserve(acc_pub3.clone()),
   1285         Kyc(acc_pub4.clone()),
   1286         Reserve(acc_pub4.clone()),
   1287         Pending,
   1288         Pending,
   1289         Kyc(acc_pub4.clone()),
   1290     ])
   1291     .await;
   1292 
   1293     // Switching to non recurrent cancel pending
   1294     let auth_pair = Ed25519KeyPair::generate().unwrap();
   1295     let auth_pub2 = EddsaPublicKey::try_from(auth_pair.public_key().as_ref()).unwrap();
   1296     prepared_transfer
   1297         .post("/registration")
   1298         .json(&json!(req + {
   1299             "account_pub": auth_pub2,
   1300             "authorization_pub": auth_pub2,
   1301             "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()),
   1302             "recurrent": true
   1303         }))
   1304         .await
   1305         .assert_ok_json::<RegistrationResponse>();
   1306     for _ in 0..3 {
   1307         register(&auth_pub2)
   1308             .await
   1309             .assert_ok_json::<TransferResponse>();
   1310     }
   1311     check_in(&[
   1312         Reserve(acc_pub1.clone()),
   1313         Reserve(acc_pub2.clone()),
   1314         Reserve(acc_pub3.clone()),
   1315         Kyc(acc_pub4.clone()),
   1316         Reserve(acc_pub4.clone()),
   1317         Pending,
   1318         Pending,
   1319         Kyc(acc_pub4.clone()),
   1320         Reserve(auth_pub2.clone()),
   1321         Pending,
   1322         Pending,
   1323     ])
   1324     .await;
   1325     prepared_transfer
   1326         .post("/registration")
   1327         .json(&json!(req + {
   1328             "type": "kyc",
   1329             "account_pub": auth_pub2,
   1330             "authorization_pub": auth_pub2,
   1331             "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()),
   1332             "recurrent": false
   1333         }))
   1334         .await
   1335         .assert_ok_json::<RegistrationResponse>();
   1336     check_in(&[
   1337         Reserve(acc_pub1.clone()),
   1338         Reserve(acc_pub2.clone()),
   1339         Reserve(acc_pub3.clone()),
   1340         Kyc(acc_pub4.clone()),
   1341         Reserve(acc_pub4.clone()),
   1342         Pending,
   1343         Pending,
   1344         Kyc(acc_pub4.clone()),
   1345         Reserve(auth_pub2.clone()),
   1346         Bounced,
   1347         Bounced,
   1348     ])
   1349     .await;
   1350 
   1351     // Recurrent reserve simple subject
   1352     let acc_pub5 = EddsaPublicKey::rand();
   1353     prepared_transfer
   1354         .post("/registration")
   1355         .json(&json!(req + {
   1356             "type": "reserve",
   1357             "account_pub": acc_pub5,
   1358             "authorization_pub": auth_pub2,
   1359             "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()),
   1360             "recurrent": true
   1361         }))
   1362         .await
   1363         .assert_ok_json::<RegistrationResponse>();
   1364     wire_gateway
   1365         .post("/admin/add-incoming")
   1366         .json(json!({
   1367             "amount": amount,
   1368             "reserve_pub": acc_pub5,
   1369             "debit_account": debit_acount,
   1370         }))
   1371         .await
   1372         .assert_ok();
   1373     register(&auth_pub2)
   1374         .await
   1375         .assert_ok_json::<TransferResponse>();
   1376     check_in(&[
   1377         Reserve(acc_pub1.clone()),
   1378         Reserve(acc_pub2.clone()),
   1379         Reserve(acc_pub3.clone()),
   1380         Kyc(acc_pub4.clone()),
   1381         Reserve(acc_pub4.clone()),
   1382         Pending,
   1383         Pending,
   1384         Kyc(acc_pub4.clone()),
   1385         Reserve(auth_pub2.clone()),
   1386         Bounced,
   1387         Bounced,
   1388         Reserve(acc_pub5.clone()),
   1389         Pending,
   1390     ])
   1391     .await;
   1392 
   1393     // Recurrent kyc simple subject
   1394     prepared_transfer
   1395         .post("/registration")
   1396         .json(&json!(req + {
   1397             "type": "kyc",
   1398             "account_pub": acc_pub5,
   1399             "authorization_pub": auth_pub2,
   1400             "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()),
   1401             "recurrent": false
   1402         }))
   1403         .await
   1404         .assert_ok_json::<RegistrationResponse>();
   1405     prepared_transfer
   1406         .post("/registration")
   1407         .json(&json!(req + {
   1408             "type": "kyc",
   1409             "account_pub": acc_pub5,
   1410             "authorization_pub": auth_pub2,
   1411             "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()),
   1412             "recurrent": true
   1413         }))
   1414         .await
   1415         .assert_ok_json::<RegistrationResponse>();
   1416     wire_gateway
   1417         .post("/admin/add-kycauth")
   1418         .json(json!({
   1419             "amount": amount,
   1420             "account_pub": acc_pub5,
   1421             "debit_account": debit_acount,
   1422         }))
   1423         .await
   1424         .assert_ok();
   1425     for _ in 0..2 {
   1426         register(&auth_pub2)
   1427             .await
   1428             .assert_ok_json::<TransferResponse>();
   1429     }
   1430     check_in(&[
   1431         Reserve(acc_pub1.clone()),
   1432         Reserve(acc_pub2.clone()),
   1433         Reserve(acc_pub3.clone()),
   1434         Kyc(acc_pub4.clone()),
   1435         Reserve(acc_pub4.clone()),
   1436         Pending,
   1437         Pending,
   1438         Kyc(acc_pub4.clone()),
   1439         Reserve(auth_pub2.clone()),
   1440         Bounced,
   1441         Bounced,
   1442         Reserve(acc_pub5.clone()),
   1443         Bounced,
   1444         Kyc(acc_pub5.clone()),
   1445         Pending,
   1446         Pending,
   1447     ])
   1448     .await;
   1449 
   1450     /* ----- Unregistration ----- */
   1451     let now = Timestamp::now().to_string();
   1452     let un_req = json!({
   1453         "timestamp": now,
   1454         "authorization_pub": auth_pub2,
   1455         "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()),
   1456     });
   1457 
   1458     // Delete
   1459     prepared_transfer
   1460         .post("/unregistration")
   1461         .json(&un_req)
   1462         .await
   1463         .assert_no_content();
   1464 
   1465     // Check bounce pending on deletion
   1466     check_in(&[
   1467         Reserve(acc_pub1.clone()),
   1468         Reserve(acc_pub2.clone()),
   1469         Reserve(acc_pub3.clone()),
   1470         Kyc(acc_pub4.clone()),
   1471         Reserve(acc_pub4.clone()),
   1472         Pending,
   1473         Pending,
   1474         Kyc(acc_pub4.clone()),
   1475         Reserve(auth_pub2.clone()),
   1476         Bounced,
   1477         Bounced,
   1478         Reserve(acc_pub5.clone()),
   1479         Bounced,
   1480         Kyc(acc_pub5.clone()),
   1481         Bounced,
   1482         Bounced,
   1483     ])
   1484     .await;
   1485 
   1486     // Idempotent
   1487     prepared_transfer
   1488         .post("/unregistration")
   1489         .json(&un_req)
   1490         .await
   1491         .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
   1492 
   1493     // Bad signature
   1494     prepared_transfer
   1495         .post("/unregistration")
   1496         .json(&json!(un_req + {
   1497             "authorization_sig": eddsa_sign(&auth_pair, b"lol"),
   1498         }))
   1499         .await
   1500         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1501 
   1502     // Old timestamp
   1503     let now = (Timestamp::now() - SignedDuration::from_mins(10)).to_string();
   1504     prepared_transfer
   1505         .post("/unregistration")
   1506         .json(json!({
   1507             "timestamp": now,
   1508             "authorization_pub": auth_pub2,
   1509             "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()),
   1510         }))
   1511         .await
   1512         .assert_error(ErrorCode::BANK_OLD_TIMESTAMP);
   1513 
   1514     /* ----- API ----- */
   1515 
   1516     let history: Vec<_> = wire_gateway
   1517         .get("/history/incoming?limit=20")
   1518         .await
   1519         .assert_ok_json::<IncomingHistory>()
   1520         .incoming_transactions
   1521         .into_iter()
   1522         .map(|tx| {
   1523             let (acc_pub, auth_pub, auth_sig) = match tx {
   1524                 IncomingBankTransaction::Reserve {
   1525                     reserve_pub,
   1526                     authorization_pub,
   1527                     authorization_sig,
   1528                     ..
   1529                 } => (reserve_pub, authorization_pub, authorization_sig),
   1530                 IncomingBankTransaction::Wad { .. } => unreachable!(),
   1531                 IncomingBankTransaction::Kyc {
   1532                     account_pub,
   1533                     authorization_pub,
   1534                     authorization_sig,
   1535                     ..
   1536                 } => (account_pub, authorization_pub, authorization_sig),
   1537             };
   1538             if let Some(auth_pub) = &auth_pub {
   1539                 assert!(check_eddsa_signature(
   1540                     auth_pub,
   1541                     acc_pub.as_ref(),
   1542                     &auth_sig.unwrap()
   1543                 ));
   1544             } else {
   1545                 assert!(auth_sig.is_none())
   1546             }
   1547             (acc_pub, auth_pub)
   1548         })
   1549         .collect();
   1550     assert_eq!(
   1551         history,
   1552         [
   1553             (acc_pub1.clone(), Some(auth_pub1.clone())),
   1554             (acc_pub2.clone(), None),
   1555             (acc_pub3.clone(), Some(auth_pub1.clone())),
   1556             (acc_pub4.clone(), Some(auth_pub1.clone())),
   1557             (acc_pub4.clone(), Some(auth_pub1.clone())),
   1558             (acc_pub4.clone(), None),
   1559             (auth_pub2.clone(), Some(auth_pub2.clone())),
   1560             (acc_pub5.clone(), None),
   1561             (acc_pub5.clone(), None),
   1562         ]
   1563     )
   1564 }