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