Skip to content

Commit

Permalink
Rework check frozen tokens function
Browse files Browse the repository at this point in the history
  • Loading branch information
azarovh committed Sep 19, 2024
1 parent 6e4af35 commit 767222e
Show file tree
Hide file tree
Showing 3 changed files with 380 additions and 62 deletions.
35 changes: 21 additions & 14 deletions chainstate/test-framework/src/random_tx_maker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ fn get_random_token<'a>(
.iter()
.choose(rng)
.and_then(|(token_id, _)| {
tip_view.get_token_data(token_id).unwrap().is_some().then_some(*token_id)
tip_view
.get_token_data(token_id)
.unwrap()
.is_some_and(|data| match data {
tokens_accounting::TokenData::FungibleToken(data) => !data.is_frozen(),
})
.then_some(*token_id)
})
.map(|token_id| {
(
Expand Down Expand Up @@ -257,6 +263,20 @@ impl<'a> RandomTxMaker<'a> {
let orders_db = OrdersAccountingDB::new(self.orders_store);
let mut orders_cache = OrdersAccountingCache::new(&orders_db);

// Select random number of accounts to spend from
let account_inputs = self.select_accounts(rng);

// Spend from accounts first then from utxo because account commands affect the state (like freezing tokens)
let (account_inputs, account_outputs) = self.create_account_spending(
rng,
&mut tokens_cache,
&pos_db,
&mut pos_delta,
&mut orders_cache,
&account_inputs,
key_manager,
);

// Select random number of utxos to spend
let inputs_with_utxos = {
let mut inputs_with_utxos = self.select_utxos(rng);
Expand Down Expand Up @@ -298,19 +318,6 @@ impl<'a> RandomTxMaker<'a> {
inputs_with_utxos,
);

// Select random number of accounts to spend from
let account_inputs = self.select_accounts(rng);

let (account_inputs, account_outputs) = self.create_account_spending(
rng,
&mut tokens_cache,
&pos_db,
&mut pos_delta,
&mut orders_cache,
&account_inputs,
key_manager,
);

let mut inputs: Vec<_> = inputs.into_iter().chain(account_inputs).collect();

outputs.extend(account_outputs);
Expand Down
275 changes: 263 additions & 12 deletions chainstate/test-suite/src/tests/fungible_tokens_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ use common::{
make_token_id, IsTokenFreezable, IsTokenUnfreezable, TokenId, TokenIssuance,
TokenIssuanceV1, TokenTotalSupply,
},
AccountCommand, AccountNonce, AccountType, Block, Destination, GenBlock, OutPointSourceId,
SignedTransaction, Transaction, TxInput, TxOutput, UtxoOutPoint,
AccountCommand, AccountNonce, AccountType, Block, Destination, GenBlock, OrderData,
OutPointSourceId, SignedTransaction, Transaction, TxInput, TxOutput, UtxoOutPoint,
},
primitives::{amount::SignedAmount, Amount, BlockHeight, CoinOrTokenId, Id, Idable},
};
Expand Down Expand Up @@ -4121,9 +4121,7 @@ fn check_freezable_supply(#[case] seed: Seed) {
assert_eq!(
result.unwrap_err(),
ChainstateError::ProcessBlockError(BlockError::StateUpdateFailed(
ConnectTransactionError::TokensAccountingError(
tokens_accounting::Error::CannotLockFrozenToken(token_id)
)
ConnectTransactionError::AttemptToSpendFrozenToken(token_id)
))
);

Expand Down Expand Up @@ -4230,6 +4228,89 @@ fn check_freezable_supply(#[case] seed: Seed) {
))
);

// Try to implicitly burn frozen tokens
let result = tf
.make_block_builder()
.add_transaction(
TransactionBuilder::new()
// token input
.add_input(
TxInput::from_utxo(mint_tx_id.into(), 0),
InputWitness::NoSignature(None),
)
// coin input
.add_input(
TxInput::from_utxo(freeze_tx_id.into(), 0),
InputWitness::NoSignature(None),
)
// coin output
.add_output(TxOutput::Transfer(
OutputValue::Coin(token_supply_change_fee),
Destination::AnyoneCanSpend,
))
.build(),
)
.build_and_process(&mut rng);

assert_eq!(
result.unwrap_err(),
ChainstateError::ProcessBlockError(BlockError::StateUpdateFailed(
ConnectTransactionError::AttemptToSpendFrozenToken(token_id)
))
);

// Try to create an order with frozen token
let order_data = OrderData::new(
Destination::AnyoneCanSpend,
OutputValue::TokenV1(token_id, amount_to_mint),
OutputValue::Coin(Amount::ZERO),
);
let result = tf
.make_block_builder()
.add_transaction(
TransactionBuilder::new()
.add_input(
TxInput::from_utxo(mint_tx_id.into(), 0),
InputWitness::NoSignature(None),
)
.add_output(TxOutput::AnyoneCanTake(Box::new(order_data)))
.build(),
)
.build_and_process(&mut rng);

assert_eq!(
result.unwrap_err(),
ChainstateError::ProcessBlockError(BlockError::StateUpdateFailed(
ConnectTransactionError::AttemptToSpendFrozenToken(token_id)
))
);

// Try to create an order with frozen token
let order_data = OrderData::new(
Destination::AnyoneCanSpend,
OutputValue::Coin(Amount::ZERO),
OutputValue::TokenV1(token_id, amount_to_mint),
);
let result = tf
.make_block_builder()
.add_transaction(
TransactionBuilder::new()
.add_input(
TxInput::from_utxo(mint_tx_id.into(), 0),
InputWitness::NoSignature(None),
)
.add_output(TxOutput::AnyoneCanTake(Box::new(order_data)))
.build(),
)
.build_and_process(&mut rng);

assert_eq!(
result.unwrap_err(),
ChainstateError::ProcessBlockError(BlockError::StateUpdateFailed(
ConnectTransactionError::AttemptToSpendFrozenToken(token_id)
))
);

// Unfreeze the token
let unfreeze_tx = TransactionBuilder::new()
.add_input(
Expand Down Expand Up @@ -4264,7 +4345,181 @@ fn check_freezable_supply(#[case] seed: Seed) {
tokens_accounting::TokenData::FungibleToken(data) => assert!(!data.is_frozen()),
};

// Now all operations are available again. Try mint/unmint/transfer
// Now all operations are available again. Try mint/transfer
tf.make_block_builder()
.add_transaction(
TransactionBuilder::new()
.add_input(
TxInput::from_command(
AccountNonce::new(3),
AccountCommand::MintTokens(token_id, amount_to_mint),
),
InputWitness::NoSignature(None),
)
.add_input(
TxInput::from_utxo(unfreeze_tx_id.into(), 0),
InputWitness::NoSignature(None),
)
.add_output(TxOutput::Transfer(
OutputValue::TokenV1(token_id, amount_to_mint),
Destination::AnyoneCanSpend,
))
.build(),
)
.build_and_process(&mut rng)
.unwrap();
});
}

// Check that if token is frozen/unfrozen by an input command it takes effect only
// after tx is processed. Meaning tx outputs are not aware of state change by inputs in the same tx.
#[rstest]
#[trace]
#[case(Seed::from_entropy())]
fn check_freeze_unfreeze_takes_effect_after_submit(#[case] seed: Seed) {
utils::concurrency::model(move || {
let mut rng = make_seedable_rng(seed);
let mut tf = TestFramework::builder(&mut rng).build();

let token_supply_change_fee =
tf.chainstate.get_chain_config().token_supply_change_fee(BlockHeight::zero());

let (token_id, _, utxo_with_change) = issue_token_from_genesis(
&mut rng,
&mut tf,
TokenTotalSupply::Lockable,
IsTokenFreezable::Yes,
);

// Mint some tokens
let amount_to_mint = Amount::from_atoms(rng.gen_range(1..100_000_000));
let best_block_id = tf.best_block_id();
let (_, mint_tx_id) = mint_tokens_in_block(
&mut rng,
&mut tf,
best_block_id,
utxo_with_change,
token_id,
amount_to_mint,
true,
);

// Freeze the token and transfer at the same tx
let freeze_tx = TransactionBuilder::new()
.add_input(
TxInput::from_command(
AccountNonce::new(1),
AccountCommand::FreezeToken(token_id, IsTokenUnfreezable::Yes),
),
InputWitness::NoSignature(None),
)
.add_input(
TxInput::from_utxo(mint_tx_id.into(), 0),
InputWitness::NoSignature(None),
)
.add_input(
TxInput::from_utxo(mint_tx_id.into(), 1),
InputWitness::NoSignature(None),
)
.add_output(TxOutput::Transfer(
OutputValue::TokenV1(token_id, amount_to_mint),
Destination::AnyoneCanSpend,
))
.add_output(TxOutput::Transfer(
OutputValue::Coin((token_supply_change_fee * 5).unwrap()),
Destination::AnyoneCanSpend,
))
.build();
let freeze_tx_id = freeze_tx.transaction().get_id();
tf.make_block_builder()
.add_transaction(freeze_tx)
.build_and_process(&mut rng)
.unwrap();

// Check result
let actual_token_data = TokensAccountingStorageRead::get_token_data(
&tf.storage.transaction_ro().unwrap(),
&token_id,
)
.unwrap();
match actual_token_data.unwrap() {
tokens_accounting::TokenData::FungibleToken(data) => assert!(data.is_frozen()),
};

// Try unfreeze the token and transfer at the same tx
let result = tf
.make_block_builder()
.add_transaction(
TransactionBuilder::new()
.add_input(
TxInput::from_command(
AccountNonce::new(2),
AccountCommand::UnfreezeToken(token_id),
),
InputWitness::NoSignature(None),
)
.add_input(
TxInput::from_utxo(freeze_tx_id.into(), 0),
InputWitness::NoSignature(None),
)
.add_input(
TxInput::from_utxo(freeze_tx_id.into(), 1),
InputWitness::NoSignature(None),
)
.add_output(TxOutput::Transfer(
OutputValue::Coin((token_supply_change_fee * 3).unwrap()),
Destination::AnyoneCanSpend,
))
.add_output(TxOutput::Transfer(
OutputValue::TokenV1(token_id, amount_to_mint),
Destination::AnyoneCanSpend,
))
.build(),
)
.build_and_process(&mut rng);

assert_eq!(
result.unwrap_err(),
ChainstateError::ProcessBlockError(BlockError::StateUpdateFailed(
ConnectTransactionError::AttemptToSpendFrozenToken(token_id)
))
);

// Unfreeze the token
let unfreeze_tx = TransactionBuilder::new()
.add_input(
TxInput::from_command(
AccountNonce::new(2),
AccountCommand::UnfreezeToken(token_id),
),
InputWitness::NoSignature(None),
)
.add_input(
TxInput::from_utxo(freeze_tx_id.into(), 1),
InputWitness::NoSignature(None),
)
.add_output(TxOutput::Transfer(
OutputValue::Coin((token_supply_change_fee * 3).unwrap()),
Destination::AnyoneCanSpend,
))
.build();
let unfreeze_tx_id = unfreeze_tx.transaction().get_id();
tf.make_block_builder()
.add_transaction(unfreeze_tx)
.build_and_process(&mut rng)
.unwrap();

// Check result
let actual_token_data = TokensAccountingStorageRead::get_token_data(
&tf.storage.transaction_ro().unwrap(),
&token_id,
)
.unwrap();
match actual_token_data.unwrap() {
tokens_accounting::TokenData::FungibleToken(data) => assert!(!data.is_frozen()),
};

// Now all operations are available again. Try mint/transfer
tf.make_block_builder()
.add_transaction(
TransactionBuilder::new()
Expand Down Expand Up @@ -5110,9 +5365,7 @@ fn check_change_authority_for_frozen_token(#[case] seed: Seed) {
assert_eq!(
result.unwrap_err(),
ChainstateError::ProcessBlockError(BlockError::StateUpdateFailed(
ConnectTransactionError::TokensAccountingError(
tokens_accounting::Error::CannotChangeAuthorityForFrozenToken(token_id)
)
ConnectTransactionError::AttemptToSpendFrozenToken(token_id)
))
);

Expand Down Expand Up @@ -5634,9 +5887,7 @@ fn check_change_metadata_for_frozen_token(#[case] seed: Seed) {
assert_eq!(
result.unwrap_err(),
ChainstateError::ProcessBlockError(BlockError::StateUpdateFailed(
ConnectTransactionError::TokensAccountingError(
tokens_accounting::Error::CannotChangeMetadataUriForFrozenToken(token_id)
)
ConnectTransactionError::AttemptToSpendFrozenToken(token_id)
))
);

Expand Down
Loading

0 comments on commit 767222e

Please sign in to comment.