use assert_matches::assert_matches;
use hedera::{
Client,
Hbar,
Key,
KeyList,
PrivateKey,
PublicKey,
Status,
TokenCreateTransaction,
TokenDeleteTransaction,
TokenId,
TokenInfoQuery,
TokenKeyValidation,
TokenType,
TokenUpdateTransaction,
};
use time::{
Duration,
OffsetDateTime,
};
use super::FungibleToken;
use crate::account::Account;
use crate::common::{
setup_nonfree,
TestEnvironment,
};
use crate::token::{
CreateFungibleToken,
TokenKeys,
};
#[tokio::test]
async fn basic() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let account = Account::create(Hbar::new(0), &client).await?;
let token = FungibleToken::create(
&client,
&account,
CreateFungibleToken { initial_supply: 0, keys: TokenKeys::ALL_OWNER },
)
.await?;
TokenUpdateTransaction::new()
.token_id(token.id)
.token_name("aaaa")
.token_symbol("A")
.sign(account.key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?;
let info = TokenInfoQuery::new().token_id(token.id).execute(&client).await?;
assert_eq!(info.token_id, token.id);
assert_eq!(info.name, "aaaa");
assert_eq!(info.symbol, "A");
assert_eq!(info.decimals, 3);
assert_eq!(info.treasury_account_id, account.id);
assert_eq!(info.admin_key, Some(Key::Single(account.key.public_key())));
assert_eq!(info.freeze_key, Some(Key::Single(account.key.public_key())));
assert_eq!(info.wipe_key, Some(Key::Single(account.key.public_key())));
assert_eq!(info.kyc_key, Some(Key::Single(account.key.public_key())));
assert_eq!(info.supply_key, Some(Key::Single(account.key.public_key())));
assert_eq!(info.pause_key, Some(Key::Single(account.key.public_key())));
assert_eq!(info.fee_schedule_key, Some(Key::Single(account.key.public_key())));
assert_eq!(info.default_freeze_status, Some(false));
assert_eq!(info.default_kyc_status, Some(false));
token.delete(&client).await?;
account.delete(&client).await?;
Ok(())
}
#[tokio::test]
async fn immutable_token_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let account = Account::create(Hbar::new(0), &client).await?;
let token = FungibleToken::create(
&client,
&account,
CreateFungibleToken { initial_supply: 0, keys: TokenKeys::NONE },
)
.await?;
let res = TokenUpdateTransaction::new()
.token_id(token.id)
.token_name("aaaa")
.token_symbol("A")
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
res,
Err(hedera::Error::ReceiptStatus { status: Status::TokenIsImmutable, .. })
);
Ok(())
}
#[tokio::test]
async fn update_immutable_token_metadata() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let initial_metadata = vec![1];
let updated_metadata = vec![1, 2];
let metadata_key = PrivateKey::generate_ed25519();
let token_id = TokenCreateTransaction::new()
.name("ffff")
.symbol("F")
.token_type(TokenType::FungibleCommon)
.decimals(3)
.initial_supply(100000)
.metadata(initial_metadata.clone())
.treasury_account_id(client.get_operator_account_id().unwrap())
.expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
.admin_key(client.get_operator_public_key().unwrap())
.metadata_key(metadata_key.public_key())
.execute(&client)
.await?
.get_receipt(&client)
.await?
.token_id
.unwrap();
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(&token_info.metadata, &initial_metadata);
assert_eq!(token_info.metadata_key, Some(Key::Single(metadata_key.public_key())));
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata(updated_metadata.clone())
.freeze_with(&client)?
.sign(metadata_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.metadata, updated_metadata);
Ok(())
}
#[tokio::test]
async fn update_keys_with_admin_sig() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let admin_key = PrivateKey::generate_ed25519();
let keys = generate_keys(Some(admin_key));
let token_id = create_token_with_keys(&client, &keys).await?;
let empty_keylist = KeyList::new();
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.admin_key(empty_keylist.clone())
.wipe_key(empty_keylist.clone())
.freeze_key(empty_keylist.clone())
.kyc_key(empty_keylist.clone())
.supply_key(empty_keylist.clone())
.pause_key(empty_keylist.clone())
.fee_schedule_key(empty_keylist.clone())
.metadata_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.admin_key.unwrap().to_owned())
.execute(&client)
.await?
.get_receipt(&client)
.await?;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.admin_key, None);
assert_eq!(token_info.freeze_key, None);
assert_eq!(token_info.wipe_key, None);
assert_eq!(token_info.kyc_key, None);
assert_eq!(token_info.supply_key, None);
assert_eq!(token_info.pause_key, None);
assert_eq!(token_info.fee_schedule_key, None);
assert_eq!(token_info.metadata_key, None);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn remove_keys_with_admin_sig() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let admin_key = PrivateKey::generate_ed25519();
let keys = generate_keys(Some(admin_key));
let token_id = create_token_with_keys(&client, &keys).await?;
let empty_keylist = KeyList::new();
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.admin_key(empty_keylist.clone())
.wipe_key(empty_keylist.clone())
.freeze_key(empty_keylist.clone())
.kyc_key(empty_keylist.clone())
.supply_key(empty_keylist.clone())
.pause_key(empty_keylist.clone())
.fee_schedule_key(empty_keylist.clone())
.metadata_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.admin_key.unwrap())
.execute(&client)
.await?
.get_receipt(&client)
.await?;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.admin_key, None);
assert_eq!(token_info.freeze_key, None);
assert_eq!(token_info.wipe_key, None);
assert_eq!(token_info.kyc_key, None);
assert_eq!(token_info.supply_key, None);
assert_eq!(token_info.pause_key, None);
assert_eq!(token_info.fee_schedule_key, None);
assert_eq!(token_info.metadata_key, None);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn revert_keys_with_admin_sig() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let admin_key = PrivateKey::generate_ed25519();
let keys = generate_keys(Some(admin_key));
let token_id = create_token_with_keys(&client, &keys).await?;
let unusable_key = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(unusable_key)
.freeze_key(unusable_key)
.kyc_key(unusable_key)
.supply_key(unusable_key)
.pause_key(unusable_key)
.fee_schedule_key(unusable_key)
.metadata_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.admin_key.as_ref().unwrap().clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.freeze_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.wipe_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.kyc_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.supply_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.pause_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.fee_schedule_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.metadata_key, Some(Key::Single(unusable_key)));
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(keys.wipe_key.public_key())
.freeze_key(keys.freeze_key.public_key())
.kyc_key(keys.kyc_key.public_key())
.supply_key(keys.supply_key.public_key())
.pause_key(keys.pause_key.public_key())
.fee_schedule_key(keys.fee_schedule_key.public_key())
.metadata_key(keys.metadata_key.public_key())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.admin_key.as_ref().unwrap().clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(
token_info.admin_key,
Some(Key::Single(keys.admin_key.unwrap().clone().public_key()))
);
assert_eq!(token_info.freeze_key, Some(Key::Single(keys.freeze_key.public_key())));
assert_eq!(token_info.wipe_key, Some(Key::Single(keys.wipe_key.public_key())));
assert_eq!(token_info.kyc_key, Some(Key::Single(keys.kyc_key.public_key())));
assert_eq!(token_info.supply_key, Some(Key::Single(keys.supply_key.public_key())));
assert_eq!(token_info.pause_key, Some(Key::Single(keys.pause_key.public_key())));
assert_eq!(token_info.fee_schedule_key, Some(Key::Single(keys.fee_schedule_key.public_key())));
assert_eq!(token_info.metadata_key, Some(Key::Single(keys.metadata_key.public_key())));
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_low_privilege_keys_with_admin_sig() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let admin_key = PrivateKey::generate_ed25519();
let keys = generate_keys(Some(admin_key));
let new_keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
_ = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(new_keys.wipe_key.public_key())
.freeze_key(new_keys.freeze_key.public_key())
.kyc_key(new_keys.kyc_key.public_key())
.supply_key(new_keys.supply_key.public_key())
.pause_key(new_keys.pause_key.public_key())
.fee_schedule_key(new_keys.fee_schedule_key.public_key())
.metadata_key(new_keys.metadata_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.admin_key.unwrap())
.sign(new_keys.wipe_key.clone())
.sign(new_keys.freeze_key.clone())
.sign(new_keys.kyc_key.clone())
.sign(new_keys.supply_key.clone())
.sign(new_keys.pause_key.clone())
.sign(new_keys.fee_schedule_key.clone())
.sign(new_keys.metadata_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.freeze_key, Some(Key::Single(new_keys.freeze_key.public_key())));
assert_eq!(token_info.wipe_key, Some(Key::Single(new_keys.wipe_key.public_key())));
assert_eq!(token_info.kyc_key, Some(Key::Single(new_keys.kyc_key.public_key())));
assert_eq!(token_info.supply_key, Some(Key::Single(new_keys.supply_key.public_key())));
assert_eq!(token_info.pause_key, Some(Key::Single(new_keys.pause_key.public_key())));
assert_eq!(
token_info.fee_schedule_key,
Some(Key::Single(new_keys.fee_schedule_key.public_key()))
);
assert_eq!(token_info.metadata_key, Some(Key::Single(new_keys.metadata_key.public_key())));
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_keys_empty_keylist_without_admin_sig_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let admin_key = PrivateKey::generate_ed25519();
let keys = generate_keys(Some(admin_key));
let token_id = create_token_with_keys(&client, &keys).await?;
let empty_keylist = KeyList::new();
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.kyc_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.pause_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.fee_schedule_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.admin_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_keys_unusable_key_without_admin_sig_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let admin_key = PrivateKey::generate_ed25519();
let keys = generate_keys(Some(admin_key));
let token_id = create_token_with_keys(&client, &keys).await?;
let unusable_key = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.kyc_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.pause_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.fee_schedule_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.admin_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_admin_key_to_usuable_key_fail() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let admin_key = PrivateKey::generate_ed25519();
let supply_key = PrivateKey::generate_ed25519();
let token_id = TokenCreateTransaction::new()
.name("Test NFT")
.symbol("TNFT")
.token_type(TokenType::NonFungibleUnique)
.treasury_account_id(client.get_operator_account_id().unwrap())
.admin_key(admin_key.public_key())
.supply_key(supply_key.public_key())
.expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
.freeze_with(&client)?
.sign(admin_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await?
.token_id
.unwrap();
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.admin_key, Some(Key::Single(admin_key.public_key())));
let unusable_key = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.admin_key(unusable_key.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(admin_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_keys_with_lower_privilege_keys_sigs() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let unusable_key = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let _ = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(unusable_key)
.wipe_key(unusable_key)
.kyc_key(unusable_key)
.supply_key(unusable_key)
.pause_key(unusable_key)
.fee_schedule_key(unusable_key)
.metadata_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.freeze_key.clone())
.sign(keys.wipe_key.clone())
.sign(keys.kyc_key.clone())
.sign(keys.supply_key.clone())
.sign(keys.pause_key.clone())
.sign(keys.fee_schedule_key.clone())
.sign(keys.metadata_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.freeze_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.wipe_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.kyc_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.supply_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.pause_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.fee_schedule_key, Some(Key::Single(unusable_key)));
assert_eq!(token_info.metadata_key, Some(Key::Single(unusable_key)));
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_keys_with_new_and_old_lower_privilege_keys_sigs() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let new_keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let _ = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(new_keys.freeze_key.public_key())
.wipe_key(new_keys.wipe_key.public_key())
.kyc_key(new_keys.kyc_key.public_key())
.supply_key(new_keys.supply_key.public_key())
.pause_key(new_keys.pause_key.public_key())
.fee_schedule_key(new_keys.fee_schedule_key.public_key())
.metadata_key(new_keys.metadata_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.freeze_key.clone())
.sign(keys.kyc_key.clone())
.sign(keys.wipe_key.clone())
.sign(keys.supply_key.clone())
.sign(keys.pause_key.clone())
.sign(keys.fee_schedule_key.clone())
.sign(keys.metadata_key.clone())
.sign(new_keys.freeze_key.clone())
.sign(new_keys.kyc_key.clone())
.sign(new_keys.wipe_key.clone())
.sign(new_keys.supply_key.clone())
.sign(new_keys.pause_key.clone())
.sign(new_keys.fee_schedule_key.clone())
.sign(new_keys.metadata_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.freeze_key, Some(Key::Single(new_keys.freeze_key.public_key())));
assert_eq!(token_info.wipe_key, Some(Key::Single(new_keys.wipe_key.public_key())));
assert_eq!(token_info.kyc_key, Some(Key::Single(new_keys.kyc_key.public_key())));
assert_eq!(token_info.supply_key, Some(Key::Single(new_keys.supply_key.public_key())));
assert_eq!(token_info.pause_key, Some(Key::Single(new_keys.pause_key.public_key())));
assert_eq!(
token_info.fee_schedule_key,
Some(Key::Single(new_keys.fee_schedule_key.public_key()))
);
assert_eq!(token_info.metadata_key, Some(Key::Single(new_keys.metadata_key.public_key())));
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_keys_with_all_old_lower_privilege_keys_sigs() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let new_keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let _ = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(new_keys.freeze_key.public_key())
.wipe_key(new_keys.wipe_key.public_key())
.kyc_key(new_keys.kyc_key.public_key())
.supply_key(new_keys.supply_key.public_key())
.pause_key(new_keys.pause_key.public_key())
.fee_schedule_key(new_keys.fee_schedule_key.public_key())
.metadata_key(new_keys.metadata_key.public_key())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.freeze_key.clone())
.sign(keys.kyc_key.clone())
.sign(keys.wipe_key.clone())
.sign(keys.supply_key.clone())
.sign(keys.pause_key.clone())
.sign(keys.fee_schedule_key.clone())
.sign(keys.metadata_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
let token_info = TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
assert_eq!(token_info.freeze_key, Some(Key::Single(new_keys.freeze_key.public_key())));
assert_eq!(token_info.wipe_key, Some(Key::Single(new_keys.wipe_key.public_key())));
assert_eq!(token_info.kyc_key, Some(Key::Single(new_keys.kyc_key.public_key())));
assert_eq!(token_info.supply_key, Some(Key::Single(new_keys.supply_key.public_key())));
assert_eq!(token_info.pause_key, Some(Key::Single(new_keys.pause_key.public_key())));
assert_eq!(
token_info.fee_schedule_key,
Some(Key::Single(new_keys.fee_schedule_key.public_key()))
);
assert_eq!(token_info.metadata_key, Some(Key::Single(new_keys.metadata_key.public_key())));
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn remove_empty_keylist_keys_lower_privilege_keys_sigs_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let empty_keylist = KeyList::new();
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.wipe_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::TokenIsImmutable,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.kyc_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.kyc_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::TokenIsImmutable,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.freeze_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::TokenIsImmutable,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.pause_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.pause_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::TokenIsImmutable,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.supply_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::TokenIsImmutable,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.fee_schedule_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.fee_schedule_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::TokenIsImmutable,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata_key(empty_keylist.clone())
.key_verification_mode(TokenKeyValidation::NoValidation)
.freeze_with(&client)?
.sign(keys.metadata_key.clone())
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::TokenIsImmutable,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_keys_unusable_key_different_key_sig_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let unusable_key = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.kyc_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.pause_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.fee_schedule_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata_key(unusable_key)
.key_verification_mode(TokenKeyValidation::NoValidation)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_with_unusable_key_with_old_key_sig_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let unusable_key = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.wipe_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.kyc_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.kyc_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.freeze_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.pause_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.pause_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.supply_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.fee_schedule_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.fee_schedule_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.metadata_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_unusable_key_old_new_key_sig_full_validation_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let new_keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let unusable_key = PublicKey::from_str_ed25519(
"0x0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.wipe_key)
.sign(new_keys.wipe_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.kyc_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.kyc_key)
.sign(new_keys.kyc_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.freeze_key)
.sign(new_keys.freeze_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.pause_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.pause_key)
.sign(new_keys.pause_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.supply_key)
.sign(new_keys.supply_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.fee_schedule_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.fee_schedule_key)
.sign(new_keys.fee_schedule_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata_key(unusable_key)
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.metadata_key)
.sign(new_keys.metadata_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
#[tokio::test]
async fn update_keys_old_key_sig_full_validation_fails() -> anyhow::Result<()> {
let Some(TestEnvironment { config: _, client }) = setup_nonfree() else {
return Ok(());
};
let keys = generate_keys(None);
let new_keys = generate_keys(None);
let token_id = create_token_with_keys(&client, &keys).await?;
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.wipe_key(new_keys.wipe_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.wipe_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.kyc_key(new_keys.kyc_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.kyc_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.freeze_key(new_keys.freeze_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.freeze_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.pause_key(new_keys.pause_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.pause_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.supply_key(new_keys.supply_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.supply_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.fee_schedule_key(new_keys.fee_schedule_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.fee_schedule_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
let tx = TokenUpdateTransaction::new()
.token_id(token_id)
.metadata_key(new_keys.metadata_key.public_key())
.key_verification_mode(TokenKeyValidation::FullValidation)
.freeze_with(&client)?
.sign(keys.metadata_key)
.execute(&client)
.await?
.get_receipt(&client)
.await;
assert_matches!(
tx,
Err(hedera::Error::ReceiptStatus {
status: Status::InvalidSignature,
transaction_id: _
})
);
_ = TokenDeleteTransaction::new().token_id(token_id).execute(&client).await?;
Ok(())
}
struct Keys {
admin_key: Option<PrivateKey>,
wipe_key: PrivateKey,
kyc_key: PrivateKey,
freeze_key: PrivateKey,
pause_key: PrivateKey,
supply_key: PrivateKey,
fee_schedule_key: PrivateKey,
metadata_key: PrivateKey,
}
fn generate_keys(admin_key: Option<PrivateKey>) -> Keys {
Keys {
admin_key,
wipe_key: PrivateKey::generate_ed25519(),
kyc_key: PrivateKey::generate_ed25519(),
freeze_key: PrivateKey::generate_ed25519(),
pause_key: PrivateKey::generate_ed25519(),
supply_key: PrivateKey::generate_ed25519(),
fee_schedule_key: PrivateKey::generate_ed25519(),
metadata_key: PrivateKey::generate_ed25519(),
}
}
async fn create_token_with_keys(client: &Client, keys: &Keys) -> anyhow::Result<TokenId> {
let token_id = {
let mut tx = TokenCreateTransaction::new();
tx.name("Test NFT")
.symbol("TNFT")
.token_type(TokenType::NonFungibleUnique)
.expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
.treasury_account_id(client.get_operator_account_id().unwrap())
.freeze_key(keys.freeze_key.public_key())
.supply_key(keys.supply_key.public_key())
.wipe_key(keys.wipe_key.public_key())
.kyc_key(keys.kyc_key.public_key())
.pause_key(keys.pause_key.public_key())
.fee_schedule_key(keys.fee_schedule_key.public_key())
.metadata_key(keys.metadata_key.public_key());
if let Some(admin_key) = &keys.admin_key {
tx.admin_key(admin_key.public_key()).freeze_with(client)?.sign(admin_key.clone());
}
tx.execute(&client).await?.get_receipt(&client).await?.token_id.unwrap()
};
let token_info: hedera::TokenInfo =
TokenInfoQuery::new().token_id(token_id).execute(&client).await?;
if let Some(admin_key) = &keys.admin_key {
assert_eq!(token_info.admin_key, Some(Key::Single(admin_key.public_key())));
} else {
assert_eq!(token_info.admin_key, None);
}
assert_eq!(token_info.freeze_key, Some(Key::Single(keys.freeze_key.public_key())));
assert_eq!(token_info.wipe_key, Some(Key::Single(keys.wipe_key.public_key())));
assert_eq!(token_info.kyc_key, Some(Key::Single(keys.kyc_key.public_key())));
assert_eq!(token_info.supply_key, Some(Key::Single(keys.supply_key.public_key())));
assert_eq!(token_info.pause_key, Some(Key::Single(keys.pause_key.public_key())));
assert_eq!(token_info.fee_schedule_key, Some(Key::Single(keys.fee_schedule_key.public_key())));
assert_eq!(token_info.metadata_key, Some(Key::Single(keys.metadata_key.public_key())));
Ok(token_id)
}