taler-rust

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

routine.rs (48599B)


      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     register: Tasks<impl AsyncFnMut(usize)>,
    780     ignore: Tasks<impl AsyncFnMut(usize)>,
    781 ) {
    782     let currency = &get_currency(revenue_api).await;
    783     routine_history::<RevenueIncomingHistory>(
    784         &revenue_api.suffix("/history"),
    785         tasks!(register;
    786             {
    787                 wire_gateway
    788                         .post("/admin/add-incoming")
    789                         .json(json!({
    790                             "amount": format!("{currency}:1"),
    791                             "reserve_pub": EddsaPublicKey::rand(),
    792                             "debit_account": debit_acount,
    793                         }))
    794                         .await
    795                         .assert_ok_json::<TransferResponse>();
    796             },
    797             {
    798                 wire_gateway
    799                         .post("/admin/add-kycauth")
    800                         .json(json!({
    801                             "amount": format!("{currency}:2"),
    802                             "account_pub": EddsaPublicKey::rand(),
    803                             "debit_account": debit_acount,
    804                         }))
    805                         .await
    806                         .assert_ok_json::<TransferResponse>();
    807             }
    808         ),
    809         ignore,
    810     )
    811     .await;
    812 }
    813 
    814 /// Test standard behavior of the outgoing history endpoint
    815 pub async fn out_history_routine(
    816     wire_gateway: &Router,
    817     register: Tasks<impl AsyncFnMut(usize)>,
    818     ignore: Tasks<impl AsyncFnMut(usize)>,
    819 ) {
    820     routine_history::<OutgoingHistory>(&wire_gateway.suffix("/history/outgoing"), register, ignore)
    821         .await;
    822 }
    823 
    824 /// Test standard behavior of the incoming history endpoint
    825 pub async fn in_history_routine(
    826     wire_gateway: &Router,
    827     prepared_transfer: &Router,
    828     debit_account: &PaytoURI,
    829     credit_account: &PaytoURI,
    830     register: Tasks<impl AsyncFnMut(usize)>,
    831     ignored: Tasks<impl AsyncFnMut(usize)>,
    832 ) {
    833     let currency = &get_currency(wire_gateway).await;
    834     let mut key = Ed25519KeyPair::generate().unwrap();
    835 
    836     routine_history::<IncomingHistory>(
    837         &wire_gateway.suffix("/history/incoming"),
    838         tasks!(register;
    839             {
    840                 wire_gateway
    841                     .post("/admin/add-incoming")
    842                     .json(json!({
    843                         "amount": format!("{currency}:1"),
    844                         "reserve_pub": EddsaPublicKey::rand(),
    845                         "debit_account": debit_account,
    846                     }))
    847                     .await
    848                     .assert_ok_json::<TransferResponse>();
    849             },
    850             {
    851                 key = Ed25519KeyPair::generate().unwrap();
    852                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    853                 let reserve_pub = EddsaPublicKey::rand();
    854                 let amount = format!("{currency}:2");
    855                 prepared_transfer
    856                     .post("/registration")
    857                     .json(json!({
    858                         "credit_account": credit_account,
    859                         "credit_amount": amount,
    860                         "type": "reserve",
    861                         "alg": "EdDSA",
    862                         "account_pub": reserve_pub,
    863                         "authorization_pub": auth_pub,
    864                         "authorization_sig": eddsa_sign(&key, reserve_pub.as_ref()),
    865                         "recurrent": true
    866                     }))
    867                     .await
    868                     .assert_ok_json::<RegistrationResponse>();
    869                 wire_gateway
    870                     .post("/admin/add-mapped")
    871                     .json(json!({
    872                         "amount": amount,
    873                         "authorization_pub": auth_pub,
    874                         "debit_account": debit_account,
    875                     }))
    876                     .await
    877                     .assert_ok_json::<TransferResponse>();
    878                 wire_gateway
    879                     .post("/admin/add-mapped")
    880                     .json(json!({
    881                         "amount": amount,
    882                         "authorization_pub": auth_pub,
    883                         "debit_account": debit_account,
    884                     }))
    885                     .await
    886                     .assert_ok_json::<TransferResponse>();
    887             },
    888             {
    889                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    890                 let reserve_pub = EddsaPublicKey::rand();
    891                 prepared_transfer
    892                     .post("/registration")
    893                     .json(json!({
    894                         "credit_account": credit_account,
    895                         "credit_amount": format!("{currency}:3"),
    896                         "type": "reserve",
    897                         "alg": "EdDSA",
    898                         "account_pub": reserve_pub,
    899                         "authorization_pub": auth_pub,
    900                         "authorization_sig": eddsa_sign(&key, reserve_pub.as_ref()),
    901                         "recurrent": true
    902                     }))
    903                     .await
    904                     .assert_ok_json::<RegistrationResponse>();
    905             },
    906             {
    907                 wire_gateway
    908                     .post("/admin/add-kycauth")
    909                     .json(json!({
    910                         "amount": format!("{currency}:4"),
    911                         "account_pub": EddsaPublicKey::rand(),
    912                         "debit_account": debit_account,
    913                     }))
    914                     .await
    915                     .assert_ok_json::<TransferResponse>();
    916             },
    917             {
    918                 key = Ed25519KeyPair::generate().unwrap();
    919                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    920                 let account_pub = EddsaPublicKey::rand();
    921                 let amount = format!("{currency}:5");
    922                 prepared_transfer
    923                     .post("/registration")
    924                     .json(json!({
    925                         "credit_account": credit_account,
    926                         "credit_amount": amount,
    927                         "type": "kyc",
    928                         "alg": "EdDSA",
    929                         "account_pub": account_pub,
    930                         "authorization_pub": auth_pub,
    931                         "authorization_sig": eddsa_sign(&key, account_pub.as_ref()),
    932                         "recurrent": true
    933                     }))
    934                     .await
    935                     .assert_ok_json::<RegistrationResponse>();
    936                 wire_gateway
    937                     .post("/admin/add-mapped")
    938                     .json(json!({
    939                         "amount": amount,
    940                         "authorization_pub": auth_pub,
    941                         "debit_account": debit_account,
    942                     }))
    943                     .await
    944                     .assert_ok_json::<TransferResponse>();
    945                 wire_gateway
    946                     .post("/admin/add-mapped")
    947                     .json(json!({
    948                         "amount": amount,
    949                         "authorization_pub": auth_pub,
    950                         "debit_account": debit_account,
    951                     }))
    952                     .await
    953                     .assert_ok_json::<TransferResponse>();
    954             },
    955             {
    956                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    957                 let account_pub = EddsaPublicKey::rand();
    958                 prepared_transfer
    959                     .post("/registration")
    960                     .json(json!({
    961                         "credit_account": credit_account,
    962                         "credit_amount": format!("{currency}:6"),
    963                         "type": "kyc",
    964                         "alg": "EdDSA",
    965                         "account_pub": account_pub,
    966                         "authorization_pub": auth_pub,
    967                         "authorization_sig": eddsa_sign(&key, account_pub.as_ref()),
    968                         "recurrent": true
    969                     }))
    970                     .await
    971                     .assert_ok_json::<RegistrationResponse>();
    972             }
    973         ),
    974         ignored,
    975     )
    976     .await;
    977 }
    978 
    979 /// Test standard behavior of the admin add incoming endpoints
    980 pub async fn admin_add_incoming_routine(
    981     wire_gateway: &Router,
    982     prepared_transfer: &Router,
    983     debit_acount: &PaytoURI,
    984     credit_account: &PaytoURI,
    985 ) {
    986     let currency = &get_currency(wire_gateway).await;
    987     for kind in IncomingType::entries {
    988         add_incoming_routine(
    989             wire_gateway,
    990             prepared_transfer,
    991             currency,
    992             *kind,
    993             debit_acount,
    994             credit_account,
    995         )
    996         .await;
    997     }
    998 }
    999 
   1000 #[derive(Debug, PartialEq, Eq)]
   1001 pub enum Status {
   1002     Simple,
   1003     Pending,
   1004     Bounced,
   1005     Incomplete,
   1006     Reserve(EddsaPublicKey),
   1007     Kyc(EddsaPublicKey),
   1008 }
   1009 
   1010 /// Test standard registration behavior of the registration endpoints
   1011 pub async fn registration_routine<F1: Future<Output = Vec<Status>>>(
   1012     wire_gateway: &Router,
   1013     prepared_transfer: &Router,
   1014     debit_acount: &PaytoURI,
   1015     credit_account: &PaytoURI,
   1016     unknown_account: &PaytoURI,
   1017     mut in_status: impl FnMut() -> F1,
   1018 ) {
   1019     pub use Status::*;
   1020     let mut check_in = async |state: &[Status]| {
   1021         let current = in_status().await;
   1022         assert_eq!(state, current);
   1023     };
   1024 
   1025     let currency = &get_currency(wire_gateway).await;
   1026     let amount = amount(format!("{currency}:42"));
   1027     let key_pair1 = Ed25519KeyPair::generate().unwrap();
   1028     let auth_pub1 = EddsaPublicKey::try_from(key_pair1.public_key().as_ref()).unwrap();
   1029     let req = json!({
   1030         "credit_account": credit_account,
   1031         "type": "reserve",
   1032         "recurrent": false,
   1033         "credit_amount": amount,
   1034         "alg": "EdDSA",
   1035         "account_pub": auth_pub1,
   1036         "authorization_pub": auth_pub1,
   1037         "authorization_sig": eddsa_sign(&key_pair1, auth_pub1.as_ref()),
   1038     });
   1039 
   1040     let register = async |auth_pub: &EddsaPublicKey| {
   1041         wire_gateway
   1042             .post("/admin/add-mapped")
   1043             .json(json!({
   1044                 "amount": format!("{currency}:42"),
   1045                 "authorization_pub": auth_pub,
   1046                 "debit_account": debit_acount,
   1047             }))
   1048             .await
   1049     };
   1050 
   1051     /* ----- Registration ----- */
   1052     let routine = async |ty: TransferType,
   1053                          account_pub: &EddsaPublicKey,
   1054                          recurrent: bool,
   1055                          fmt: IncomingType| {
   1056         let req = json!(req + {
   1057             "type": ty,
   1058             "account_pub": account_pub,
   1059             "authorization_pub": auth_pub1,
   1060             "authorization_sig": eddsa_sign(&key_pair1, account_pub.as_ref()),
   1061             "recurrent": recurrent
   1062         });
   1063         // Valid
   1064         let res = prepared_transfer
   1065             .post("/registration")
   1066             .json(&req)
   1067             .await
   1068             .assert_ok_json::<RegistrationResponse>();
   1069 
   1070         // Idempotent
   1071         assert_eq!(
   1072             res,
   1073             prepared_transfer
   1074                 .post("/registration")
   1075                 .json(&req)
   1076                 .await
   1077                 .assert_ok_json::<RegistrationResponse>()
   1078         );
   1079 
   1080         assert!(!res.subjects.is_empty());
   1081 
   1082         for sub in res.subjects {
   1083             if let TransferSubject::Simple { subject, .. } = sub {
   1084                 assert_eq!(subject, fmt_in_subject(fmt, &auth_pub1).to_string());
   1085             };
   1086         }
   1087     };
   1088     for ty in [TransferType::reserve, TransferType::kyc] {
   1089         routine(ty, &auth_pub1, false, ty.into()).await;
   1090         routine(ty, &auth_pub1, true, IncomingType::map).await;
   1091     }
   1092 
   1093     let acc_pub1 = EddsaPublicKey::rand();
   1094     for ty in [TransferType::reserve, TransferType::kyc] {
   1095         routine(ty, &acc_pub1, false, IncomingType::map).await;
   1096         routine(ty, &acc_pub1, true, IncomingType::map).await;
   1097     }
   1098 
   1099     // Bad signature
   1100     prepared_transfer
   1101         .post("/registration")
   1102         .json(&json!(req + { "authorization_sig": eddsa_sign(&key_pair1, b"lol")}))
   1103         .await
   1104         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1105 
   1106     // Unknown account
   1107     prepared_transfer
   1108         .post("/registration")
   1109         .json(&json!(req + { "credit_account": unknown_account }))
   1110         .await
   1111         .assert_error(ErrorCode::BANK_UNKNOWN_CREDITOR);
   1112 
   1113     // Unsupported payto kind
   1114     prepared_transfer
   1115         .post("/registration")
   1116         .json(&json!(req + { "credit_account": UNKNOWN }))
   1117         .await
   1118         .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
   1119 
   1120     // Malformed payto
   1121     prepared_transfer
   1122         .post("/registration")
   1123         .json(&json!(req + {"credit_account": "http://email@test.com" }))
   1124         .await
   1125         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
   1126 
   1127     // Reserve pub reuse
   1128     prepared_transfer
   1129         .post("/registration")
   1130         .json(&json!(req + {
   1131             "account_pub": acc_pub1,
   1132             "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()),
   1133         }))
   1134         .await
   1135         .assert_ok_json::<RegistrationResponse>();
   1136     {
   1137         let key_pair = Ed25519KeyPair::generate().unwrap();
   1138         let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
   1139         prepared_transfer
   1140             .post("/registration")
   1141             .json(&json!(req + {
   1142                 "account_pub": acc_pub1,
   1143                 "authorization_pub": auth_pub,
   1144                 "authorization_sig": eddsa_sign(&key_pair, acc_pub1.as_ref()),
   1145             }))
   1146             .await
   1147             .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT);
   1148     }
   1149 
   1150     // Non recurrent accept one then bounce
   1151     prepared_transfer
   1152         .post("/registration")
   1153         .json(&json!(req + {
   1154             "account_pub": acc_pub1,
   1155             "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()),
   1156         }))
   1157         .await
   1158         .assert_ok_json::<RegistrationResponse>();
   1159     register(&auth_pub1)
   1160         .await
   1161         .assert_ok_json::<TransferResponse>();
   1162     check_in(&[Reserve(acc_pub1.clone())]).await;
   1163     register(&auth_pub1)
   1164         .await
   1165         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1166 
   1167     // Again without using mapping
   1168     let acc_pub2 = EddsaPublicKey::rand();
   1169     prepared_transfer
   1170         .post("/registration")
   1171         .json(&json!(req + {
   1172             "account_pub": acc_pub2,
   1173             "authorization_sig": eddsa_sign(&key_pair1, acc_pub2.as_ref()),
   1174         }))
   1175         .await
   1176         .assert_ok_json::<RegistrationResponse>();
   1177     wire_gateway
   1178         .post("/admin/add-incoming")
   1179         .json(json!({
   1180             "amount": amount,
   1181             "reserve_pub": acc_pub2,
   1182             "debit_account": debit_acount,
   1183         }))
   1184         .await
   1185         .assert_ok();
   1186     register(&auth_pub1)
   1187         .await
   1188         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1189     check_in(&[Reserve(acc_pub1.clone()), Reserve(acc_pub2.clone())]).await;
   1190 
   1191     // Recurrent accept one and delay others
   1192     let acc_pub3 = EddsaPublicKey::rand();
   1193     prepared_transfer
   1194         .post("/registration")
   1195         .json(&json!(req + {
   1196             "account_pub": acc_pub3,
   1197             "authorization_sig": eddsa_sign(&key_pair1, acc_pub3.as_ref()),
   1198             "recurrent": true
   1199         }))
   1200         .await
   1201         .assert_ok_json::<RegistrationResponse>();
   1202     for _ in 0..5 {
   1203         register(&auth_pub1)
   1204             .await
   1205             .assert_ok_json::<TransferResponse>();
   1206     }
   1207     check_in(&[
   1208         Reserve(acc_pub1.clone()),
   1209         Reserve(acc_pub2.clone()),
   1210         Reserve(acc_pub3.clone()),
   1211         Pending,
   1212         Pending,
   1213         Pending,
   1214         Pending,
   1215     ])
   1216     .await;
   1217 
   1218     // Complete pending on recurrent update
   1219     let acc_pub4 = EddsaPublicKey::rand();
   1220     prepared_transfer
   1221         .post("/registration")
   1222         .json(&json!(req + {
   1223             "type": "kyc",
   1224             "account_pub": acc_pub4,
   1225             "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()),
   1226             "recurrent": true
   1227         }))
   1228         .await
   1229         .assert_ok_json::<RegistrationResponse>();
   1230     prepared_transfer
   1231         .post("/registration")
   1232         .json(&json!(req + {
   1233             "account_pub": acc_pub4,
   1234             "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()),
   1235             "recurrent": true
   1236         }))
   1237         .await
   1238         .assert_ok_json::<RegistrationResponse>();
   1239     check_in(&[
   1240         Reserve(acc_pub1.clone()),
   1241         Reserve(acc_pub2.clone()),
   1242         Reserve(acc_pub3.clone()),
   1243         Kyc(acc_pub4.clone()),
   1244         Reserve(acc_pub4.clone()),
   1245         Pending,
   1246         Pending,
   1247     ])
   1248     .await;
   1249 
   1250     // Kyc key reuse keep pending ones
   1251     wire_gateway
   1252         .post("/admin/add-kycauth")
   1253         .json(json!({
   1254             "amount": amount,
   1255             "account_pub": acc_pub4,
   1256             "debit_account": debit_acount,
   1257         }))
   1258         .await
   1259         .assert_ok_json::<TransferResponse>();
   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         Kyc(acc_pub4.clone()),
   1269     ])
   1270     .await;
   1271 
   1272     // Switching to non recurrent cancel pending
   1273     let auth_pair = Ed25519KeyPair::generate().unwrap();
   1274     let auth_pub2 = EddsaPublicKey::try_from(auth_pair.public_key().as_ref()).unwrap();
   1275     prepared_transfer
   1276         .post("/registration")
   1277         .json(&json!(req + {
   1278             "account_pub": auth_pub2,
   1279             "authorization_pub": auth_pub2,
   1280             "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()),
   1281             "recurrent": true
   1282         }))
   1283         .await
   1284         .assert_ok_json::<RegistrationResponse>();
   1285     for _ in 0..3 {
   1286         register(&auth_pub2)
   1287             .await
   1288             .assert_ok_json::<TransferResponse>();
   1289     }
   1290     check_in(&[
   1291         Reserve(acc_pub1.clone()),
   1292         Reserve(acc_pub2.clone()),
   1293         Reserve(acc_pub3.clone()),
   1294         Kyc(acc_pub4.clone()),
   1295         Reserve(acc_pub4.clone()),
   1296         Pending,
   1297         Pending,
   1298         Kyc(acc_pub4.clone()),
   1299         Reserve(auth_pub2.clone()),
   1300         Pending,
   1301         Pending,
   1302     ])
   1303     .await;
   1304     prepared_transfer
   1305         .post("/registration")
   1306         .json(&json!(req + {
   1307             "type": "kyc",
   1308             "account_pub": auth_pub2,
   1309             "authorization_pub": auth_pub2,
   1310             "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()),
   1311             "recurrent": false
   1312         }))
   1313         .await
   1314         .assert_ok_json::<RegistrationResponse>();
   1315     check_in(&[
   1316         Reserve(acc_pub1.clone()),
   1317         Reserve(acc_pub2.clone()),
   1318         Reserve(acc_pub3.clone()),
   1319         Kyc(acc_pub4.clone()),
   1320         Reserve(acc_pub4.clone()),
   1321         Pending,
   1322         Pending,
   1323         Kyc(acc_pub4.clone()),
   1324         Reserve(auth_pub2.clone()),
   1325         Bounced,
   1326         Bounced,
   1327     ])
   1328     .await;
   1329 
   1330     // Recurrent reserve simple subject
   1331     let acc_pub5 = EddsaPublicKey::rand();
   1332     prepared_transfer
   1333         .post("/registration")
   1334         .json(&json!(req + {
   1335             "type": "reserve",
   1336             "account_pub": acc_pub5,
   1337             "authorization_pub": auth_pub2,
   1338             "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()),
   1339             "recurrent": true
   1340         }))
   1341         .await
   1342         .assert_ok_json::<RegistrationResponse>();
   1343     wire_gateway
   1344         .post("/admin/add-incoming")
   1345         .json(json!({
   1346             "amount": amount,
   1347             "reserve_pub": acc_pub5,
   1348             "debit_account": debit_acount,
   1349         }))
   1350         .await
   1351         .assert_ok();
   1352     register(&auth_pub2)
   1353         .await
   1354         .assert_ok_json::<TransferResponse>();
   1355     check_in(&[
   1356         Reserve(acc_pub1.clone()),
   1357         Reserve(acc_pub2.clone()),
   1358         Reserve(acc_pub3.clone()),
   1359         Kyc(acc_pub4.clone()),
   1360         Reserve(acc_pub4.clone()),
   1361         Pending,
   1362         Pending,
   1363         Kyc(acc_pub4.clone()),
   1364         Reserve(auth_pub2.clone()),
   1365         Bounced,
   1366         Bounced,
   1367         Reserve(acc_pub5.clone()),
   1368         Pending,
   1369     ])
   1370     .await;
   1371 
   1372     // Recurrent kyc simple subject
   1373     prepared_transfer
   1374         .post("/registration")
   1375         .json(&json!(req + {
   1376             "type": "kyc",
   1377             "account_pub": acc_pub5,
   1378             "authorization_pub": auth_pub2,
   1379             "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()),
   1380             "recurrent": false
   1381         }))
   1382         .await
   1383         .assert_ok_json::<RegistrationResponse>();
   1384     prepared_transfer
   1385         .post("/registration")
   1386         .json(&json!(req + {
   1387             "type": "kyc",
   1388             "account_pub": acc_pub5,
   1389             "authorization_pub": auth_pub2,
   1390             "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()),
   1391             "recurrent": true
   1392         }))
   1393         .await
   1394         .assert_ok_json::<RegistrationResponse>();
   1395     wire_gateway
   1396         .post("/admin/add-kycauth")
   1397         .json(json!({
   1398             "amount": amount,
   1399             "account_pub": acc_pub5,
   1400             "debit_account": debit_acount,
   1401         }))
   1402         .await
   1403         .assert_ok();
   1404     for _ in 0..2 {
   1405         register(&auth_pub2)
   1406             .await
   1407             .assert_ok_json::<TransferResponse>();
   1408     }
   1409     check_in(&[
   1410         Reserve(acc_pub1.clone()),
   1411         Reserve(acc_pub2.clone()),
   1412         Reserve(acc_pub3.clone()),
   1413         Kyc(acc_pub4.clone()),
   1414         Reserve(acc_pub4.clone()),
   1415         Pending,
   1416         Pending,
   1417         Kyc(acc_pub4.clone()),
   1418         Reserve(auth_pub2.clone()),
   1419         Bounced,
   1420         Bounced,
   1421         Reserve(acc_pub5.clone()),
   1422         Bounced,
   1423         Kyc(acc_pub5.clone()),
   1424         Pending,
   1425         Pending,
   1426     ])
   1427     .await;
   1428 
   1429     /* ----- Unregistration ----- */
   1430     let now = Timestamp::now().to_string();
   1431     let un_req = json!({
   1432         "timestamp": now,
   1433         "authorization_pub": auth_pub2,
   1434         "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()),
   1435     });
   1436 
   1437     // Delete
   1438     prepared_transfer
   1439         .post("/unregistration")
   1440         .json(&un_req)
   1441         .await
   1442         .assert_no_content();
   1443 
   1444     // Check bounce pending on deletion
   1445     check_in(&[
   1446         Reserve(acc_pub1.clone()),
   1447         Reserve(acc_pub2.clone()),
   1448         Reserve(acc_pub3.clone()),
   1449         Kyc(acc_pub4.clone()),
   1450         Reserve(acc_pub4.clone()),
   1451         Pending,
   1452         Pending,
   1453         Kyc(acc_pub4.clone()),
   1454         Reserve(auth_pub2.clone()),
   1455         Bounced,
   1456         Bounced,
   1457         Reserve(acc_pub5.clone()),
   1458         Bounced,
   1459         Kyc(acc_pub5.clone()),
   1460         Bounced,
   1461         Bounced,
   1462     ])
   1463     .await;
   1464 
   1465     // Idempotent
   1466     prepared_transfer
   1467         .post("/unregistration")
   1468         .json(&un_req)
   1469         .await
   1470         .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
   1471 
   1472     // Bad signature
   1473     prepared_transfer
   1474         .post("/unregistration")
   1475         .json(&json!(un_req + {
   1476             "authorization_sig": eddsa_sign(&auth_pair, b"lol"),
   1477         }))
   1478         .await
   1479         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1480 
   1481     // Old timestamp
   1482     let now = (Timestamp::now() - SignedDuration::from_mins(10)).to_string();
   1483     prepared_transfer
   1484         .post("/unregistration")
   1485         .json(json!({
   1486             "timestamp": now,
   1487             "authorization_pub": auth_pub2,
   1488             "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()),
   1489         }))
   1490         .await
   1491         .assert_error(ErrorCode::BANK_OLD_TIMESTAMP);
   1492 
   1493     /* ----- API ----- */
   1494 
   1495     let history: Vec<_> = wire_gateway
   1496         .get("/history/incoming?limit=20")
   1497         .await
   1498         .assert_ok_json::<IncomingHistory>()
   1499         .incoming_transactions
   1500         .into_iter()
   1501         .map(|tx| {
   1502             let (acc_pub, auth_pub, auth_sig) = match tx {
   1503                 IncomingBankTransaction::Reserve {
   1504                     reserve_pub,
   1505                     authorization_pub,
   1506                     authorization_sig,
   1507                     ..
   1508                 } => (reserve_pub, authorization_pub, authorization_sig),
   1509                 IncomingBankTransaction::Wad { .. } => unreachable!(),
   1510                 IncomingBankTransaction::Kyc {
   1511                     account_pub,
   1512                     authorization_pub,
   1513                     authorization_sig,
   1514                     ..
   1515                 } => (account_pub, authorization_pub, authorization_sig),
   1516             };
   1517             if let Some(auth_pub) = &auth_pub {
   1518                 assert!(check_eddsa_signature(
   1519                     auth_pub,
   1520                     acc_pub.as_ref(),
   1521                     &auth_sig.unwrap()
   1522                 ));
   1523             } else {
   1524                 assert!(auth_sig.is_none())
   1525             }
   1526             (acc_pub, auth_pub)
   1527         })
   1528         .collect();
   1529     assert_eq!(
   1530         history,
   1531         [
   1532             (acc_pub1.clone(), Some(auth_pub1.clone())),
   1533             (acc_pub2.clone(), None),
   1534             (acc_pub3.clone(), Some(auth_pub1.clone())),
   1535             (acc_pub4.clone(), Some(auth_pub1.clone())),
   1536             (acc_pub4.clone(), Some(auth_pub1.clone())),
   1537             (acc_pub4.clone(), None),
   1538             (auth_pub2.clone(), Some(auth_pub2.clone())),
   1539             (acc_pub5.clone(), None),
   1540             (acc_pub5.clone(), None),
   1541         ]
   1542     )
   1543 }