Skip to content

Commit bc1e9ed

Browse files
committed
add test for mint input amount decimals
1 parent ffe241f commit bc1e9ed

File tree

2 files changed

+190
-13
lines changed

2 files changed

+190
-13
lines changed

api-server/stack-test-suite/tests/v2/transaction.rs

+177
Original file line numberDiff line numberDiff line change
@@ -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+
}

api-server/web-server/src/api/json_helpers.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -438,19 +438,19 @@ pub fn tx_to_json(
438438
) -> serde_json::Value {
439439
let token_decimals = &(&additional_info.token_decimals).into();
440440
json!({
441-
"id": tx.get_id().to_hash().encode_hex::<String>(),
442-
"version_byte": tx.version_byte(),
443-
"is_replaceable": tx.is_replaceable(),
444-
"flags": tx.flags(),
445-
"fee": amount_to_json(additional_info.fee, chain_config.coin_decimals()),
446-
"inputs": tx.inputs().iter().zip(additional_info.input_utxos.iter()).map(|(inp, utxo)| json!({
447-
"input": tx_input_to_json(inp, token_decimals, chain_config),
448-
"utxo": utxo.as_ref().map(|txo| txoutput_to_json(txo, chain_config, token_decimals)),
449-
})).collect::<Vec<_>>(),
450-
"outputs": tx.outputs()
451-
.iter()
452-
.map(|out| txoutput_to_json(out, chain_config, token_decimals))
453-
.collect::<Vec<_>>()
441+
"id": tx.get_id().to_hash().encode_hex::<String>(),
442+
"version_byte": tx.version_byte(),
443+
"is_replaceable": tx.is_replaceable(),
444+
"flags": tx.flags(),
445+
"fee": amount_to_json(additional_info.fee, chain_config.coin_decimals()),
446+
"inputs": tx.inputs().iter().zip(additional_info.input_utxos.iter()).map(|(inp, utxo)| json!({
447+
"input": tx_input_to_json(inp, token_decimals, chain_config),
448+
"utxo": utxo.as_ref().map(|txo| txoutput_to_json(txo, chain_config, token_decimals)),
449+
})).collect::<Vec<_>>(),
450+
"outputs": tx.outputs()
451+
.iter()
452+
.map(|out| txoutput_to_json(out, chain_config, token_decimals))
453+
.collect::<Vec<_>>()
454454
})
455455
}
456456

0 commit comments

Comments
 (0)