fixed token name validation

This commit is contained in:
viraladmin 2026-06-23 11:24:35 -06:00
parent bdca5ed1ff
commit e044f15e76
17 changed files with 175 additions and 59 deletions

View File

@ -1,4 +1,5 @@
use blockchain::blocks::burn::{BurnTransaction, UnsignedBurnTransaction}; use blockchain::blocks::burn::{BurnTransaction, UnsignedBurnTransaction};
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
use blockchain::common::network_paths_and_settings::block_extension_and_paths; use blockchain::common::network_paths_and_settings::block_extension_and_paths;
@ -9,13 +10,6 @@ use blockchain::File;
use blockchain::Utc; use blockchain::Utc;
use blockchain::{create_dir_all, AsyncWriteExt}; use blockchain::{create_dir_all, AsyncWriteExt};
// Pad the asset name so it matches the 15-byte on-chain asset format.
fn pad_to_width(input: &str, width: usize) -> String {
let mut result = String::with_capacity(width);
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
result
}
fn display_fee(value: u64) -> f64 { fn display_fee(value: u64) -> f64 {
// Fees are stored as atomic units and displayed as whole-coin decimals for the CLI. // Fees are stored as atomic units and displayed as whole-coin decimals for the CLI.
value as f64 / 100_000_000.0 value as f64 / 100_000_000.0
@ -29,7 +23,10 @@ async fn main() {
// Burnable assets are tokens or NFTs, never the base coin for this transaction type. // Burnable assets are tokens or NFTs, never the base coin for this transaction type.
let coin_name = prompt_visible("Please enter the token or NFT name you wish to burn: ").await; let coin_name = prompt_visible("Please enter the token or NFT name you wish to burn: ").await;
let coin = pad_to_width(&coin_name.trim().to_lowercase(), 15); let Some(coin) = padded_normalized_asset_input(coin_name.trim()) else {
println!("Burn asset must normalize to 3-15 alphanumeric characters.");
return;
};
if coin.trim().to_lowercase() == block_extension_and_paths().1.trim().to_lowercase() { if coin.trim().to_lowercase() == block_extension_and_paths().1.trim().to_lowercase() {
println!("Base coin cannot be burned with this transaction type."); println!("Base coin cannot be burned with this transaction type.");

View File

@ -1,4 +1,5 @@
use blockchain::blocks::issue_token::{IssueTokenTransaction, UnsignedIssueTokenTransaction}; use blockchain::blocks::issue_token::{IssueTokenTransaction, UnsignedIssueTokenTransaction};
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
use blockchain::common::types::ISSUE_TOKEN_FEE; use blockchain::common::types::ISSUE_TOKEN_FEE;
@ -8,13 +9,6 @@ use blockchain::File;
use blockchain::Utc; use blockchain::Utc;
use blockchain::{create_dir_all, AsyncWriteExt}; use blockchain::{create_dir_all, AsyncWriteExt};
// Pad the ticker so it matches the fixed-width 15-byte on-chain format.
fn pad_to_width(input: &str, width: usize) -> String {
let mut result = String::with_capacity(width);
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
result
}
fn display_fee(value: u64) -> f64 { fn display_fee(value: u64) -> f64 {
// Fees are stored as atomic units and displayed as whole-coin decimals for the CLI. // Fees are stored as atomic units and displayed as whole-coin decimals for the CLI.
value as f64 / 100_000_000.0 value as f64 / 100_000_000.0
@ -29,7 +23,10 @@ async fn main() {
// The ticker is stored as a fixed-width 15-byte asset field. // The ticker is stored as a fixed-width 15-byte asset field.
let ticker_name = let ticker_name =
prompt_visible("Please enter the ticker / token name you wish to issue more of: ").await; prompt_visible("Please enter the ticker / token name you wish to issue more of: ").await;
let ticker = pad_to_width(&ticker_name.trim().to_lowercase(), 15); let Some(ticker) = padded_normalized_asset_input(ticker_name.trim()) else {
println!("Ticker must normalize to 3-15 alphanumeric characters.");
return;
};
// Token amounts are entered as whole tokens and stored as atomic units. // Token amounts are entered as whole tokens and stored as atomic units.
let number_string = let number_string =

View File

@ -1,5 +1,7 @@
use blockchain::blocks::loans::UnsignedLoanContractTransaction; use blockchain::blocks::loans::UnsignedLoanContractTransaction;
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
use blockchain::common::types::LENDER_FEE; use blockchain::common::types::LENDER_FEE;
use blockchain::json; use blockchain::json;
@ -9,11 +11,13 @@ use blockchain::File;
use blockchain::{create_dir_all, AsyncWriteExt}; use blockchain::{create_dir_all, AsyncWriteExt};
use blockchain::{Local, LocalResult, NaiveDate, NaiveTime, TimeZone}; use blockchain::{Local, LocalResult, NaiveDate, NaiveTime, TimeZone};
fn pad_to_width(input: &str, width: usize) -> String { fn normalize_asset_or_base(input: &str) -> Option<String> {
// Asset names are fixed-width fields in the loan transaction bytes. let base_coin = block_extension_and_paths().1;
let mut result = String::with_capacity(width); if input.trim().eq_ignore_ascii_case(base_coin.trim()) {
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}")); Some(base_coin)
result } else {
padded_normalized_asset_input(input.trim())
}
} }
fn display_fee(value: u64) -> f64 { fn display_fee(value: u64) -> f64 {
@ -63,8 +67,10 @@ async fn main() {
// Coin/token and collateral names are padded to fixed-width transaction fields. // Coin/token and collateral names are padded to fixed-width transaction fields.
let loan_coin_name = prompt_visible("What coin or token will you be lending out? ").await; let loan_coin_name = prompt_visible("What coin or token will you be lending out? ").await;
let loan_coin_name = loan_coin_name.trim().to_lowercase(); let Some(loan_coin) = normalize_asset_or_base(&loan_coin_name) else {
let loan_coin = pad_to_width(&loan_coin_name, 15); println!("Loan asset must be the base coin or normalize to 3-15 alphanumeric characters.");
return;
};
let loan_amount_string = prompt_visible("How many coins or tokens will you be lending? ").await; let loan_amount_string = prompt_visible("How many coins or tokens will you be lending? ").await;
let loan_amount_f64: f64 = loan_amount_string let loan_amount_f64: f64 = loan_amount_string
@ -123,8 +129,10 @@ async fn main() {
let collateral_name = let collateral_name =
prompt_visible("What collateral coin, token, or NFT will you be accepting for this loan? ") prompt_visible("What collateral coin, token, or NFT will you be accepting for this loan? ")
.await; .await;
let collateral_name = collateral_name.trim().to_lowercase(); let Some(collateral) = padded_normalized_asset_input(collateral_name.trim()) else {
let collateral = pad_to_width(&collateral_name, 15); println!("Collateral asset must normalize to 3-15 alphanumeric characters.");
return;
};
let collateral_amount_string = prompt_visible("How many collateral coins or tokens will you be accepting for this loan (enter 1 for NFT collateral)? ").await; let collateral_amount_string = prompt_visible("How many collateral coins or tokens will you be accepting for this loan (enter 1 for NFT collateral)? ").await;
let collateral_amount_f64: f64 = collateral_amount_string let collateral_amount_f64: f64 = collateral_amount_string

View File

@ -1,4 +1,5 @@
use blockchain::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction}; use blockchain::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction};
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
use blockchain::common::types::CREATE_NFT_FEE; use blockchain::common::types::CREATE_NFT_FEE;
@ -31,7 +32,10 @@ async fn main() {
// get user input ticker // get user input ticker
let nft_ticker_name = prompt_visible("Please enter the name for your NFT. This can be 3 to 15 characters and must be 100% unique. First come first serve: ").await; let nft_ticker_name = prompt_visible("Please enter the name for your NFT. This can be 3 to 15 characters and must be 100% unique. First come first serve: ").await;
let nft_name = pad_to_width(nft_ticker_name.trim(), 15); let Some(nft_name) = padded_normalized_asset_input(nft_ticker_name.trim()) else {
println!("NFT name must normalize to 3-15 alphanumeric characters.");
return;
};
// get the padded CID string // get the padded CID string
let ipfs_hash = prompt_visible("Enter your IPFS CID for your NFT. This will be stored in a fixed 100 character field, so shorter CIDs are padded with spaces: ").await; let ipfs_hash = prompt_visible("Enter your IPFS CID for your NFT. This will be stored in a fixed 100 character field, so shorter CIDs are padded with spaces: ").await;

View File

@ -1,5 +1,7 @@
use blockchain::blocks::swap::UnsignedSwapTransaction; use blockchain::blocks::swap::UnsignedSwapTransaction;
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
use blockchain::common::types::SWAP_FEE; use blockchain::common::types::SWAP_FEE;
use blockchain::json; use blockchain::json;
@ -10,11 +12,13 @@ use blockchain::File;
use blockchain::Utc; use blockchain::Utc;
use blockchain::{create_dir_all, AsyncWriteExt}; use blockchain::{create_dir_all, AsyncWriteExt};
// pad the coin to ensure 15 characters fn normalize_asset_or_base(input: &str) -> Option<String> {
fn pad_to_width(input: &str, width: usize) -> String { let base_coin = block_extension_and_paths().1;
let mut result = String::with_capacity(width); // Pre-allocate string with capacity if input.trim().eq_ignore_ascii_case(base_coin.trim()) {
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}")); Some(base_coin)
result } else {
padded_normalized_asset_input(input.trim())
}
} }
fn display_fee(value: u64) -> f64 { fn display_fee(value: u64) -> f64 {
@ -30,8 +34,10 @@ async fn main() {
// get user input tocken1 // get user input tocken1
let ticker1_name = let ticker1_name =
prompt_visible("Please enter the coin or token you wish to send during the swap: ").await; prompt_visible("Please enter the coin or token you wish to send during the swap: ").await;
let ticker1_name = ticker1_name.trim().to_lowercase(); let Some(ticker1) = normalize_asset_or_base(&ticker1_name) else {
let ticker1 = pad_to_width(&ticker1_name, 15); println!("Swap asset must be the base coin or normalize to 3-15 alphanumeric characters.");
return;
};
let nft_series1_string = prompt_visible("Please enter the NFT series number for the asset you are sending, or 0 for coins/tokens/1of1 NFTs: ").await; let nft_series1_string = prompt_visible("Please enter the NFT series number for the asset you are sending, or 0 for coins/tokens/1of1 NFTs: ").await;
let nft_series1: u32 = nft_series1_string let nft_series1: u32 = nft_series1_string
@ -52,8 +58,10 @@ async fn main() {
let ticker2_name = let ticker2_name =
prompt_visible("Please enter the coin or token you wish to receive during the swap: ") prompt_visible("Please enter the coin or token you wish to receive during the swap: ")
.await; .await;
let ticker2_name = ticker2_name.trim().to_lowercase(); let Some(ticker2) = normalize_asset_or_base(&ticker2_name) else {
let ticker2 = pad_to_width(&ticker2_name, 15); println!("Swap asset must be the base coin or normalize to 3-15 alphanumeric characters.");
return;
};
let nft_series2_string = prompt_visible("Please enter the NFT series number for the asset you expect to receive, or 0 for coins/tokens/1of1 NFTs: ").await; let nft_series2_string = prompt_visible("Please enter the NFT series number for the asset you expect to receive, or 0 for coins/tokens/1of1 NFTs: ").await;
let nft_series2: u32 = nft_series2_string let nft_series2: u32 = nft_series2_string

View File

@ -1,4 +1,5 @@
use blockchain::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction}; use blockchain::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction};
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
use blockchain::common::types::CREATE_TOKEN_FEE; use blockchain::common::types::CREATE_TOKEN_FEE;
@ -8,13 +9,6 @@ use blockchain::File;
use blockchain::Utc; use blockchain::Utc;
use blockchain::{create_dir_all, AsyncWriteExt}; use blockchain::{create_dir_all, AsyncWriteExt};
// pad the coin to ensure 15 characters
fn pad_to_width(input: &str, width: usize) -> String {
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
result
}
fn display_fee(value: u64) -> f64 { fn display_fee(value: u64) -> f64 {
value as f64 / 100_000_000.0 value as f64 / 100_000_000.0
} }
@ -27,7 +21,10 @@ async fn main() {
// get user input ticker // get user input ticker
let ticker_name = prompt_visible("Please enter the ticker / token name you wish to create. This can be 3 to 15 characters and must be 100% unique. First come first serve: ").await; let ticker_name = prompt_visible("Please enter the ticker / token name you wish to create. This can be 3 to 15 characters and must be 100% unique. First come first serve: ").await;
let ticker = pad_to_width(ticker_name.trim(), 15); let Some(ticker) = padded_normalized_asset_input(ticker_name.trim()) else {
println!("Ticker must normalize to 3-15 alphanumeric characters.");
return;
};
// get user input value // get user input value
let number_string = prompt_visible( let number_string = prompt_visible(

View File

@ -1,4 +1,5 @@
use blockchain::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction}; use blockchain::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction};
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
use blockchain::common::network_paths_and_settings::block_extension_and_paths; use blockchain::common::network_paths_and_settings::block_extension_and_paths;
@ -11,11 +12,13 @@ use blockchain::File;
use blockchain::Utc; use blockchain::Utc;
use blockchain::{create_dir_all, AsyncWriteExt}; use blockchain::{create_dir_all, AsyncWriteExt};
// pad the coin to ensure 15 characters fn normalize_asset_or_base(input: &str) -> Option<String> {
fn pad_to_width(input: &str, width: usize) -> String { let base_coin = block_extension_and_paths().1;
let mut result = String::with_capacity(width); // Pre-allocate string with capacity if input.trim().eq_ignore_ascii_case(base_coin.trim()) {
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}")); Some(base_coin)
result } else {
padded_normalized_asset_input(input.trim())
}
} }
fn display_fee(value: u64) -> f64 { fn display_fee(value: u64) -> f64 {
@ -42,9 +45,11 @@ async fn main() {
} else { } else {
prompt_visible("Please enter the coin or token you wish to transfer: ").await prompt_visible("Please enter the coin or token you wish to transfer: ").await
}; };
let coin = pad_to_width(&coin_name, 15); let Some(coin) = normalize_asset_or_base(&coin_name) else {
let is_base_coin = println!("Transfer asset must be the base coin or normalize to 3-15 alphanumeric characters.");
coin.trim().to_lowercase() == block_extension_and_paths().1.trim().to_lowercase(); return;
};
let is_base_coin = coin.trim().eq_ignore_ascii_case(block_extension_and_paths().1.trim());
let value_string = if args.len() > 2 { let value_string = if args.len() > 2 {
args[2].clone() args[2].clone()

View File

@ -1,4 +1,5 @@
use blockchain::blocks::swap::UnsignedSwapTransaction; use blockchain::blocks::swap::UnsignedSwapTransaction;
use blockchain::common::asset_names::padded_normalized_asset_input;
use blockchain::common::cli_prompts::{ use blockchain::common::cli_prompts::{
ask_yes_no_question, prompt_hidden_nonempty, prompt_wallet_path, ask_yes_no_question, prompt_hidden_nonempty, prompt_wallet_path,
}; };
@ -12,11 +13,12 @@ use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
use blockchain::wallets::structures::Wallet; use blockchain::wallets::structures::Wallet;
use blockchain::Value; use blockchain::Value;
// padd the coin to ensure 15 characters fn normalize_asset_or_base(input: &str, base_coin: &str) -> Option<String> {
fn pad_to_width(input: &str, width: usize) -> String { if input.trim().eq_ignore_ascii_case(base_coin.trim()) {
let mut result = String::with_capacity(width); // Pre-allocate string with capacity Some(base_coin.to_string())
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}")); } else {
result padded_normalized_asset_input(input.trim())
}
} }
#[tokio::main] #[tokio::main]
@ -162,8 +164,14 @@ async fn main() {
return; return;
} }
let padded_token_name1 = pad_to_width(&token_name1, 15); let Some(padded_token_name1) = normalize_asset_or_base(&token_name1, &network_coin) else {
let padded_token_name2 = pad_to_width(&token_name2, 15); println!("Ticker1 must be the base coin or normalize to 3-15 alphanumeric characters.");
return;
};
let Some(padded_token_name2) = normalize_asset_or_base(&token_name2, &network_coin) else {
println!("Ticker2 must be the base coin or normalize to 3-15 alphanumeric characters.");
return;
};
let unsigned_swap = UnsignedSwapTransaction::new( let unsigned_swap = UnsignedSwapTransaction::new(
txtype, txtype,

38
src/common/asset_names.rs Normal file
View File

@ -0,0 +1,38 @@
use crate::common::types::COIN_LENGTH;
pub fn normalize_asset_identifier(input: &str) -> String {
input
.chars()
.filter(|character| character.is_ascii_alphanumeric())
.flat_map(char::to_lowercase)
.collect()
}
pub fn padded_normalized_asset_identifier(input: &str) -> Option<String> {
let normalized = normalize_asset_identifier(input);
if normalized.len() < 3 || normalized.len() > COIN_LENGTH {
return None;
}
Some(format!("{normalized:<COIN_LENGTH$}"))
}
pub fn padded_normalized_asset_input(input: &str) -> Option<String> {
if !input
.chars()
.all(|character| character.is_ascii_alphanumeric() || character.is_ascii_whitespace())
{
return None;
}
padded_normalized_asset_identifier(input)
}
pub fn is_canonical_padded_asset_identifier(input: &str) -> bool {
padded_normalized_asset_identifier(input).is_some_and(|canonical| canonical == input)
}
pub fn is_canonical_padded_asset_or_base(input: &str, base_coin: &str) -> bool {
input.trim().eq_ignore_ascii_case(base_coin.trim())
|| is_canonical_padded_asset_identifier(input)
}

View File

@ -1,4 +1,5 @@
pub mod binary_conversions; pub mod binary_conversions;
pub mod asset_names;
pub mod check_genesis; pub mod check_genesis;
pub mod cli_prompts; pub mod cli_prompts;
pub mod network_paths_and_settings; pub mod network_paths_and_settings;

View File

@ -1,4 +1,5 @@
use crate::blocks::burn::BurnTransaction; use crate::blocks::burn::BurnTransaction;
use crate::common::asset_names::is_canonical_padded_asset_identifier;
use crate::common::nft_assets::nft_asset_name; use crate::common::nft_assets::nft_asset_name;
use crate::common::types::{BURN_FEE, COIN_LENGTH}; use crate::common::types::{BURN_FEE, COIN_LENGTH};
use crate::encode; use crate::encode;
@ -51,6 +52,9 @@ impl BurnTransaction {
if self.unsigned_burn.coin.len() != COIN_LENGTH { if self.unsigned_burn.coin.len() != COIN_LENGTH {
return Err("Coin length is invalid.".to_string()); return Err("Coin length is invalid.".to_string());
} }
if !is_canonical_padded_asset_identifier(&self.unsigned_burn.coin) {
return Err("Burn asset must be a canonical alphanumeric asset id.".to_string());
}
// The base coin can never be destroyed through the burn path. // The base coin can never be destroyed through the burn path.
if self.unsigned_burn.coin.trim().to_lowercase() == BASECOIN.trim().to_lowercase() { if self.unsigned_burn.coin.trim().to_lowercase() == BASECOIN.trim().to_lowercase() {

View File

@ -1,4 +1,5 @@
use crate::blocks::nft::CreateNftTransaction; use crate::blocks::nft::CreateNftTransaction;
use crate::common::asset_names::is_canonical_padded_asset_identifier;
use crate::common::nft_assets::nft_asset_name; use crate::common::nft_assets::nft_asset_name;
use crate::common::types::{COIN_LENGTH, CREATE_NFT_FEE}; use crate::common::types::{COIN_LENGTH, CREATE_NFT_FEE};
use crate::encode; use crate::encode;
@ -27,6 +28,11 @@ impl CreateNftTransaction {
) )
.map_err(|s| s.to_string())?; .map_err(|s| s.to_string())?;
} }
if !is_canonical_padded_asset_identifier(&self.unsigned_create_nft.nft_name) {
return Err(
"NFT name must normalize to 3-15 lowercase alphanumeric characters.".to_string(),
);
}
if is_reserved_base_coin_ticker(&self.unsigned_create_nft.nft_name) { if is_reserved_base_coin_ticker(&self.unsigned_create_nft.nft_name) {
return Err("NFT name is reserved for the base coin.".to_string()); return Err("NFT name is reserved for the base coin.".to_string());

View File

@ -1,4 +1,5 @@
use crate::blocks::token::CreateTokenTransaction; use crate::blocks::token::CreateTokenTransaction;
use crate::common::asset_names::is_canonical_padded_asset_identifier;
use crate::common::types::{COIN_LENGTH, CREATE_TOKEN_FEE}; use crate::common::types::{COIN_LENGTH, CREATE_TOKEN_FEE};
use crate::encode; use crate::encode;
use crate::records::memory::mempool::BASECOIN; use crate::records::memory::mempool::BASECOIN;
@ -27,6 +28,11 @@ impl CreateTokenTransaction {
return Err("Ticker length must be 15 characters. Consider padding with empty spaces") return Err("Ticker length must be 15 characters. Consider padding with empty spaces")
.map_err(|s| s.to_string())?; .map_err(|s| s.to_string())?;
} }
if !is_canonical_padded_asset_identifier(&self.unsigned_create_token.ticker) {
return Err(
"Ticker must normalize to 3-15 lowercase alphanumeric characters.".to_string(),
);
}
if is_reserved_base_coin_ticker(&self.unsigned_create_token.ticker) { if is_reserved_base_coin_ticker(&self.unsigned_create_token.ticker) {
return Err("Ticker is reserved for the base coin.".to_string()); return Err("Ticker is reserved for the base coin.".to_string());

View File

@ -1,5 +1,6 @@
use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::issue_token::IssueTokenTransaction;
use crate::blocks::token::CreateTokenTransaction; use crate::blocks::token::CreateTokenTransaction;
use crate::common::asset_names::is_canonical_padded_asset_identifier;
use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::network_paths_and_settings::block_extension_and_paths;
use crate::common::types::{COIN_LENGTH, ISSUE_TOKEN_FEE}; use crate::common::types::{COIN_LENGTH, ISSUE_TOKEN_FEE};
use crate::records::memory::mempool::BASECOIN; use crate::records::memory::mempool::BASECOIN;
@ -88,6 +89,11 @@ impl IssueTokenTransaction {
.to_string(), .to_string(),
); );
} }
if !is_canonical_padded_asset_identifier(ticker) {
return Err(
"Ticker must normalize to 3-15 lowercase alphanumeric characters.".to_string(),
);
}
// Check reserved keywords not used // Check reserved keywords not used
if reserved_base_coins() if reserved_base_coins()

View File

@ -1,4 +1,7 @@
use crate::blocks::loans::LoanContractTransaction; use crate::blocks::loans::LoanContractTransaction;
use crate::common::asset_names::{
is_canonical_padded_asset_identifier, is_canonical_padded_asset_or_base,
};
use crate::common::types::{COIN_LENGTH, LENDER_FEE}; use crate::common::types::{COIN_LENGTH, LENDER_FEE};
use crate::encode; use crate::encode;
use crate::records::memory::mempool::BASECOIN; use crate::records::memory::mempool::BASECOIN;
@ -91,6 +94,14 @@ impl LoanContractTransaction {
) )
.map_err(|s| s.to_string())?; .map_err(|s| s.to_string())?;
} }
if !is_canonical_padded_asset_or_base(&loan_coin, &BASECOIN)
|| !is_canonical_padded_asset_identifier(&collateral)
{
return Err(
"Loan assets must use the base coin or canonical alphanumeric asset ids."
.to_string(),
);
}
// Loaned assets must already exist as either the network base coin // Loaned assets must already exist as either the network base coin
// or a previously created token. // or a previously created token.

View File

@ -1,4 +1,5 @@
use crate::blocks::swap::SwapTransaction; use crate::blocks::swap::SwapTransaction;
use crate::common::asset_names::is_canonical_padded_asset_or_base;
use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::network_paths_and_settings::block_extension_and_paths;
use crate::common::nft_assets::nft_asset_name; use crate::common::nft_assets::nft_asset_name;
use crate::common::types::{COIN_LENGTH, SWAP_FEE}; use crate::common::types::{COIN_LENGTH, SWAP_FEE};
@ -75,6 +76,18 @@ impl SwapTransaction {
"Ticker lengths are invalid. Consider padding with empty spaces".to_string(), "Ticker lengths are invalid. Consider padding with empty spaces".to_string(),
); );
} }
let network_base_coin_padded = block_extension_and_paths().1;
if !is_canonical_padded_asset_or_base(&self.unsigned_swap.ticker1, &network_base_coin_padded)
|| !is_canonical_padded_asset_or_base(
&self.unsigned_swap.ticker2,
&network_base_coin_padded,
)
{
return Err(
"Swap assets must be the base coin or canonical alphanumeric asset ids."
.to_string(),
);
}
// Build concrete asset names for NFT series swaps before checking // Build concrete asset names for NFT series swaps before checking
// balances and registry existence. // balances and registry existence.
@ -83,7 +96,7 @@ impl SwapTransaction {
// The base coin name comes from the active network settings so // The base coin name comes from the active network settings so
// mainnet and testnet validate against their own ticker. // mainnet and testnet validate against their own ticker.
let network_base_coin = block_extension_and_paths().1.trim().to_lowercase(); let network_base_coin = network_base_coin_padded.trim().to_lowercase();
// Numbered NFT swaps are single-item transfers, while non-series // Numbered NFT swaps are single-item transfers, while non-series
// assets must already exist as a token, NFT, or base coin. // assets must already exist as a token, NFT, or base coin.

View File

@ -1,4 +1,5 @@
use crate::blocks::transfer::TransferTransaction; use crate::blocks::transfer::TransferTransaction;
use crate::common::asset_names::is_canonical_padded_asset_or_base;
use crate::common::nft_assets::nft_asset_name; use crate::common::nft_assets::nft_asset_name;
use crate::common::types::{minimum_transfer_fee, COIN_LENGTH}; use crate::common::types::{minimum_transfer_fee, COIN_LENGTH};
use crate::encode; use crate::encode;
@ -138,6 +139,12 @@ impl TransferTransaction {
if self.unsigned_transfer.coin.len() != COIN_LENGTH { if self.unsigned_transfer.coin.len() != COIN_LENGTH {
return Err("Coin length is invalid.".to_string()); return Err("Coin length is invalid.".to_string());
} }
if !is_canonical_padded_asset_or_base(&self.unsigned_transfer.coin, &BASECOIN) {
return Err(
"Transfer asset must be the base coin or a canonical alphanumeric asset id."
.to_string(),
);
}
// Saved-chain duplicates are rejected by txid even if the mempool // Saved-chain duplicates are rejected by txid even if the mempool
// did not already contain the transaction. // did not already contain the transaction.