@@ -434,3 +434,186 @@ async fn ok(#[case] seed: Seed) {
434434
435435 task. abort ( ) ;
436436}
437+
438+ #[ rstest]
439+ #[ trace]
440+ #[ case( Seed :: from_entropy( ) ) ]
441+ #[ tokio:: test]
442+ async fn mint_tokens ( #[ case] seed : Seed ) {
443+ use chainstate_test_framework:: empty_witness;
444+ use common:: chain:: {
445+ make_token_id,
446+ tokens:: { TokenIssuance , TokenTotalSupply } ,
447+ AccountCommand , AccountNonce , UtxoOutPoint ,
448+ } ;
449+
450+ let listener = tokio:: net:: TcpListener :: bind ( "127.0.0.1:0" ) . await . unwrap ( ) ;
451+ let addr = listener. local_addr ( ) . unwrap ( ) ;
452+
453+ let ( tx, rx) = tokio:: sync:: oneshot:: channel ( ) ;
454+
455+ let task = tokio:: spawn ( async move {
456+ let web_server_state = {
457+ let mut rng = make_seedable_rng ( seed) ;
458+ let chain_config = create_unit_test_config ( ) ;
459+
460+ let chainstate_blocks = {
461+ let mut tf = TestFramework :: builder ( & mut rng)
462+ . with_chain_config ( chain_config. clone ( ) )
463+ . build ( ) ;
464+
465+ let token_issuance_fee =
466+ tf. chainstate . get_chain_config ( ) . fungible_token_issuance_fee ( ) ;
467+
468+ let issuance = test_utils:: nft_utils:: random_token_issuance_v1 (
469+ tf. chain_config ( ) ,
470+ Destination :: AnyoneCanSpend ,
471+ & mut rng,
472+ ) ;
473+ let amount_to_mint = match issuance. total_supply {
474+ TokenTotalSupply :: Fixed ( limit) => {
475+ Amount :: from_atoms ( rng. gen_range ( 1 ..=limit. into_atoms ( ) ) )
476+ }
477+ TokenTotalSupply :: Lockable | TokenTotalSupply :: Unlimited => {
478+ Amount :: from_atoms ( rng. gen_range ( 100 ..1000 ) )
479+ }
480+ } ;
481+ let mint_amount_decimal =
482+ amount_to_mint. into_fixedpoint_str ( issuance. number_of_decimals ) ;
483+
484+ let genesis_outpoint = UtxoOutPoint :: new ( tf. best_block_id ( ) . into ( ) , 0 ) ;
485+ let genesis_coins = chainstate_test_framework:: get_output_value (
486+ tf. chainstate . utxo ( & genesis_outpoint) . unwrap ( ) . unwrap ( ) . output ( ) ,
487+ )
488+ . unwrap ( )
489+ . coin_amount ( )
490+ . unwrap ( ) ;
491+ let coins_after_issue = ( genesis_coins - token_issuance_fee) . unwrap ( ) ;
492+
493+ // Issue token
494+ let tx1 = TransactionBuilder :: new ( )
495+ . add_input ( genesis_outpoint. into ( ) , empty_witness ( & mut rng) )
496+ . add_output ( TxOutput :: Transfer (
497+ OutputValue :: Coin ( coins_after_issue) ,
498+ Destination :: AnyoneCanSpend ,
499+ ) )
500+ . add_output ( TxOutput :: IssueFungibleToken ( Box :: new ( TokenIssuance :: V1 (
501+ issuance,
502+ ) ) ) )
503+ . build ( ) ;
504+ let token_id = make_token_id (
505+ & chain_config,
506+ BlockHeight :: new ( 1 ) ,
507+ tx1. transaction ( ) . inputs ( ) ,
508+ )
509+ . unwrap ( ) ;
510+ let tx1_id = tx1. transaction ( ) . get_id ( ) ;
511+ let block1 = tf. make_block_builder ( ) . add_transaction ( tx1) . build ( & mut rng) ;
512+
513+ tf. process_block ( block1. clone ( ) , chainstate:: BlockSource :: Local ) . unwrap ( ) ;
514+
515+ // Mint tokens
516+ let token_supply_change_fee =
517+ tf. chainstate . get_chain_config ( ) . token_supply_change_fee ( BlockHeight :: zero ( ) ) ;
518+ let coins_after_mint = ( coins_after_issue - token_supply_change_fee) . unwrap ( ) ;
519+
520+ let tx2 = TransactionBuilder :: new ( )
521+ . add_input (
522+ TxInput :: from_command (
523+ AccountNonce :: new ( 0 ) ,
524+ AccountCommand :: MintTokens ( token_id, amount_to_mint) ,
525+ ) ,
526+ empty_witness ( & mut rng) ,
527+ )
528+ . add_input (
529+ TxInput :: from_utxo ( tx1_id. into ( ) , 0 ) ,
530+ empty_witness ( & mut rng) ,
531+ )
532+ . add_output ( TxOutput :: Transfer (
533+ OutputValue :: Coin ( coins_after_mint) ,
534+ Destination :: AnyoneCanSpend ,
535+ ) )
536+ . add_output ( TxOutput :: Transfer (
537+ OutputValue :: TokenV1 ( token_id, amount_to_mint) ,
538+ Destination :: AnyoneCanSpend ,
539+ ) )
540+ . build ( ) ;
541+
542+ let tx2_id = tx2. transaction ( ) . get_id ( ) ;
543+ let block2 = tf. make_block_builder ( ) . add_transaction ( tx2) . build ( & mut rng) ;
544+
545+ tf. process_block ( block2. clone ( ) , chainstate:: BlockSource :: Local ) . unwrap ( ) ;
546+
547+ _ = tx. send ( (
548+ tx2_id. to_hash ( ) . encode_hex :: < String > ( ) ,
549+ mint_amount_decimal,
550+ Address :: new ( & chain_config, token_id) . expect ( "no error" ) . into_string ( ) ,
551+ ) ) ;
552+
553+ vec ! [ block1, block2]
554+ } ;
555+
556+ let storage = {
557+ let mut storage = TransactionalApiServerInMemoryStorage :: new ( & chain_config) ;
558+
559+ let mut db_tx = storage. transaction_rw ( ) . await . unwrap ( ) ;
560+ db_tx. reinitialize_storage ( & chain_config) . await . unwrap ( ) ;
561+ db_tx. commit ( ) . await . unwrap ( ) ;
562+
563+ storage
564+ } ;
565+
566+ let chain_config = Arc :: new ( chain_config) ;
567+ let mut local_node = BlockchainState :: new ( Arc :: clone ( & chain_config) , storage) ;
568+ local_node. scan_genesis ( chain_config. genesis_block ( ) ) . await . unwrap ( ) ;
569+ local_node. scan_blocks ( BlockHeight :: new ( 0 ) , chainstate_blocks) . await . unwrap ( ) ;
570+
571+ ApiServerWebServerState {
572+ db : Arc :: new ( local_node. storage ( ) . clone_storage ( ) . await ) ,
573+ chain_config : Arc :: clone ( & chain_config) ,
574+ rpc : Arc :: new ( DummyRPC { } ) ,
575+ cached_values : Arc :: new ( CachedValues {
576+ feerate_points : RwLock :: new ( ( get_time ( ) , vec ! [ ] ) ) ,
577+ } ) ,
578+ time_getter : Default :: default ( ) ,
579+ }
580+ } ;
581+
582+ web_server ( listener, web_server_state, true ) . await
583+ } ) ;
584+
585+ let ( transaction_id, mint_amount, token_id) = rx. await . unwrap ( ) ;
586+ let url = format ! ( "/api/v2/transaction/{transaction_id}" ) ;
587+
588+ // Given that the listener port is open, this will block until a
589+ // response is made (by the web server, which takes the listener
590+ // over)
591+ let response = reqwest:: get ( format ! ( "http://{}:{}{url}" , addr. ip( ) , addr. port( ) ) )
592+ . await
593+ . unwrap ( ) ;
594+
595+ assert_eq ! ( response. status( ) , 200 ) ;
596+
597+ let body = response. text ( ) . await . unwrap ( ) ;
598+ let body: serde_json:: Value = serde_json:: from_str ( & body) . unwrap ( ) ;
599+ let body = body. as_object ( ) . unwrap ( ) ;
600+
601+ let inputs = body. get ( "inputs" ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
602+ assert_eq ! ( inputs. len( ) , 2 ) ;
603+ let mint_inp = inputs. first ( ) . unwrap ( ) . as_object ( ) . unwrap ( ) . get ( "input" ) . unwrap ( ) ;
604+ assert_eq ! (
605+ mint_inp. as_object( ) . unwrap( ) . get( "command" ) . unwrap( ) . as_str( ) . unwrap( ) ,
606+ "MintTokens"
607+ ) ;
608+ assert_eq ! (
609+ mint_inp. as_object( ) . unwrap( ) . get( "token_id" ) . unwrap( ) . as_str( ) . unwrap( ) ,
610+ token_id,
611+ ) ;
612+ let amount = mint_inp. as_object ( ) . unwrap ( ) . get ( "amount" ) . unwrap ( ) . as_object ( ) . unwrap ( ) ;
613+ assert_eq ! (
614+ amount. get( "decimal" ) . unwrap( ) . as_str( ) . unwrap( ) ,
615+ mint_amount
616+ ) ;
617+
618+ task. abort ( ) ;
619+ }
0 commit comments