Merge pull request #211 from logos-blockchain/Pravdyvy/private-definition-token

Private TokenDefinition support
This commit is contained in:
Pravdyvy 2025-12-10 10:03:42 +02:00 committed by GitHub
commit 81393925d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 507 additions and 111 deletions

View File

@ -445,11 +445,11 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
}
/// This test creates a new private token using the token program. After creating the token, the
/// test executes a private token transfer to a new account. All accounts are owned except
/// definition.
/// test executes a private token transfer to a new account. All accounts are private owned
/// except definition which is public.
#[nssa_integration_test]
pub async fn test_success_token_program_private_owned() {
info!("########## test_success_token_program_private_owned ##########");
pub async fn test_success_token_program_private_owned_supply() {
info!("########## test_success_token_program_private_owned_supply ##########");
let wallet_config = fetch_config().await.unwrap();
// Create new account for the token definition (public)
@ -606,6 +606,189 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
}
/// This test creates a new private token using the token program. All accounts are private
/// owned except supply which is public.
#[nssa_integration_test]
pub async fn test_success_token_program_private_owned_definition() {
info!("########## test_success_token_program_private_owned_definition ##########");
let wallet_config = fetch_config().await.unwrap();
// Create new account for the token definition (private)
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new account for the token supply holder (public)
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Public {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: make_private_account_input_from_str(
&definition_account_id.to_string(),
),
supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
let new_commitment1 = wallet_storage
.get_private_account_commitment(&definition_account_id)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await);
// Check the status of the token definition account is the expected after the execution
let supply_acc = seq_client
.get_account(supply_account_id.to_string())
.await
.unwrap()
.account;
assert_eq!(supply_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!(
supply_acc.data,
vec![
1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239,
84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
}
/// This test creates a new private token using the token program. All accounts are private
/// owned.
#[nssa_integration_test]
pub async fn test_success_token_program_private_owned_definition_and_supply() {
info!(
"########## test_success_token_program_private_owned_definition_and_supply ##########"
);
let wallet_config = fetch_config().await.unwrap();
// Create new account for the token definition (private)
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new account for the token supply holder (private)
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New(
NewSubcommand::Private {
cci: ChainIndex::root(),
},
)))
.await
.unwrap()
else {
panic!("invalid subcommand return value");
};
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: make_private_account_input_from_str(
&definition_account_id.to_string(),
),
supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(Command::Token(subcommand))
.await
.unwrap();
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let wallet_config = fetch_config().await.unwrap();
let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
let new_commitment1 = wallet_storage
.get_private_account_commitment(&definition_account_id)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await);
let new_commitment2 = wallet_storage
.get_private_account_commitment(&supply_account_id)
.unwrap();
assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await);
let definition_acc = wallet_storage
.get_account_private(&definition_account_id)
.unwrap();
let supply_acc = wallet_storage
.get_account_private(&supply_account_id)
.unwrap();
assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!(
definition_acc.data,
vec![
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
assert_eq!(supply_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!(
supply_acc.data,
vec![
1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239,
84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
}
/// This test creates a new private token using the token program. After creating the token, the
/// test executes a private token transfer to a new account.
#[nssa_integration_test]

View File

@ -14,8 +14,6 @@ use crate::{
#[derive(Subcommand, Debug, Clone)]
pub enum TokenProgramAgnosticSubcommand {
/// Produce a new token
///
/// Currently the only supported privacy options is for public definition
New {
/// definition_account_id - valid 32 byte base58 string with privacy prefix
#[arg(long)]
@ -72,8 +70,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
let underlying_subcommand = match (definition_addr_privacy, supply_addr_privacy) {
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::CreateNewToken {
TokenProgramSubcommand::Create(
CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp {
definition_account_id,
supply_account_id,
name,
@ -82,8 +80,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
)
}
(AccountPrivacyKind::Public, AccountPrivacyKind::Private) => {
TokenProgramSubcommand::Private(
TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned {
TokenProgramSubcommand::Create(
CreateNewTokenProgramSubcommand::NewPublicDefPrivateSupp {
definition_account_id,
supply_account_id,
name,
@ -92,14 +90,24 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
)
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Private) => {
// ToDo: maybe implement this one. It is not immediately clear why
// definition should be private.
anyhow::bail!("Unavailable privacy pairing")
TokenProgramSubcommand::Create(
CreateNewTokenProgramSubcommand::NewPrivateDefPrivateSupp {
definition_account_id,
supply_account_id,
name,
total_supply,
},
)
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Public) => {
// ToDo: Probably valid. If definition is not public, but supply is it is
// very suspicious.
anyhow::bail!("Unavailable privacy pairing")
TokenProgramSubcommand::Create(
CreateNewTokenProgramSubcommand::NewPrivateDefPublicSupp {
definition_account_id,
supply_account_id,
name,
total_supply,
},
)
}
};
@ -202,6 +210,9 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
/// Represents generic CLI subcommand for a wallet working with token_program
#[derive(Subcommand, Debug, Clone)]
pub enum TokenProgramSubcommand {
/// Creation of new token
#[command(subcommand)]
Create(CreateNewTokenProgramSubcommand),
/// Public execution
#[command(subcommand)]
Public(TokenProgramSubcommandPublic),
@ -219,17 +230,6 @@ pub enum TokenProgramSubcommand {
/// Represents generic public CLI subcommand for a wallet working with token_program
#[derive(Subcommand, Debug, Clone)]
pub enum TokenProgramSubcommandPublic {
// Create a new token using the token program
CreateNewToken {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
supply_account_id: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
// Transfer tokens using the token program
TransferToken {
#[arg(short, long)]
@ -244,17 +244,6 @@ pub enum TokenProgramSubcommandPublic {
/// Represents generic private CLI subcommand for a wallet working with token_program
#[derive(Subcommand, Debug, Clone)]
pub enum TokenProgramSubcommandPrivate {
// Create a new token using the token program
CreateNewTokenPrivateOwned {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
supply_account_id: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
// Transfer tokens using the token program
TransferTokenPrivateOwned {
#[arg(short, long)]
@ -320,35 +309,69 @@ pub enum TokenProgramSubcommandShielded {
},
}
/// Represents generic initialization subcommand for a wallet working with token_program
#[derive(Subcommand, Debug, Clone)]
pub enum CreateNewTokenProgramSubcommand {
/// Create a new token using the token program
///
/// Definition - public, supply - public
NewPublicDefPublicSupp {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
supply_account_id: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
/// Create a new token using the token program
///
/// Definition - public, supply - private
NewPublicDefPrivateSupp {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
supply_account_id: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
/// Create a new token using the token program
///
/// Definition - private, supply - public
NewPrivateDefPublicSupp {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
supply_account_id: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
/// Create a new token using the token program
///
/// Definition - private, supply - private
NewPrivateDefPrivateSupp {
#[arg(short, long)]
definition_account_id: String,
#[arg(short, long)]
supply_account_id: String,
#[arg(short, long)]
name: String,
#[arg(short, long)]
total_supply: u128,
},
}
impl WalletSubcommand for TokenProgramSubcommandPublic {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
TokenProgramSubcommandPublic::CreateNewToken {
definition_account_id,
supply_account_id,
name,
total_supply,
} => {
let name = name.as_bytes();
if name.len() > 6 {
// TODO: return error
panic!();
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
Token(wallet_core)
.send_new_definition(
definition_account_id.parse().unwrap(),
supply_account_id.parse().unwrap(),
name_bytes,
total_supply,
)
.await?;
Ok(SubcommandReturnValue::Empty)
}
TokenProgramSubcommandPublic::TransferToken {
sender_account_id,
recipient_account_id,
@ -373,54 +396,6 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned {
definition_account_id,
supply_account_id,
name,
total_supply,
} => {
let name = name.as_bytes();
if name.len() > 6 {
// TODO: return error
panic!("Name length mismatch");
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
let (res, secret_supply) = Token(wallet_core)
.send_new_definition_private_owned(
definition_account_id,
supply_account_id,
name_bytes,
total_supply,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![(secret_supply, supply_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
TokenProgramSubcommandPrivate::TransferTokenPrivateOwned {
sender_account_id,
recipient_account_id,
@ -657,12 +632,195 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
}
}
impl WalletSubcommand for CreateNewTokenProgramSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
CreateNewTokenProgramSubcommand::NewPrivateDefPrivateSupp {
definition_account_id,
supply_account_id,
name,
total_supply,
} => {
let name = name.as_bytes();
if name.len() > 6 {
// TODO: return error
panic!("Name length mismatch");
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
let (res, [secret_definition, secret_supply]) = Token(wallet_core)
.send_new_definition_private_owned_definiton_and_supply(
definition_account_id,
supply_account_id,
name_bytes,
total_supply,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![
(secret_definition, definition_account_id),
(secret_supply, supply_account_id),
];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
CreateNewTokenProgramSubcommand::NewPrivateDefPublicSupp {
definition_account_id,
supply_account_id,
name,
total_supply,
} => {
let name = name.as_bytes();
if name.len() > 6 {
// TODO: return error
panic!("Name length mismatch");
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
let (res, secret_definition) = Token(wallet_core)
.send_new_definition_private_owned_definiton(
definition_account_id,
supply_account_id,
name_bytes,
total_supply,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![(secret_definition, definition_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
CreateNewTokenProgramSubcommand::NewPublicDefPrivateSupp {
definition_account_id,
supply_account_id,
name,
total_supply,
} => {
let name = name.as_bytes();
if name.len() > 6 {
// TODO: return error
panic!("Name length mismatch");
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
let definition_account_id: AccountId = definition_account_id.parse().unwrap();
let supply_account_id: AccountId = supply_account_id.parse().unwrap();
let (res, secret_supply) = Token(wallet_core)
.send_new_definition_private_owned_supply(
definition_account_id,
supply_account_id,
name_bytes,
total_supply,
)
.await?;
println!("Results of tx send are {res:#?}");
let tx_hash = res.tx_hash;
let transfer_tx = wallet_core
.poll_native_token_transfer(tx_hash.clone())
.await?;
if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx {
let acc_decode_data = vec![(secret_supply, supply_account_id)];
wallet_core.decode_insert_privacy_preserving_transaction_results(
tx,
&acc_decode_data,
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp {
definition_account_id,
supply_account_id,
name,
total_supply,
} => {
let name = name.as_bytes();
if name.len() > 6 {
// TODO: return error
panic!();
}
let mut name_bytes = [0; 6];
name_bytes[..name.len()].copy_from_slice(name);
Token(wallet_core)
.send_new_definition(
definition_account_id.parse().unwrap(),
supply_account_id.parse().unwrap(),
name_bytes,
total_supply,
)
.await?;
Ok(SubcommandReturnValue::Empty)
}
}
}
}
impl WalletSubcommand for TokenProgramSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
TokenProgramSubcommand::Create(creation_subcommand) => {
creation_subcommand.handle_subcommand(wallet_core).await
}
TokenProgramSubcommand::Private(private_subcommand) => {
private_subcommand.handle_subcommand(wallet_core).await
}

View File

@ -38,7 +38,7 @@ impl Token<'_> {
Ok(self.0.sequencer_client.send_tx_public(tx).await?)
}
pub async fn send_new_definition_private_owned(
pub async fn send_new_definition_private_owned_supply(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
@ -61,11 +61,66 @@ impl Token<'_> {
let first = secrets
.into_iter()
.next()
.expect("expected recipient's secret");
.expect("expected supply's secret");
(resp, first)
})
}
pub async fn send_new_definition_private_owned_definiton(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
name: [u8; 6],
total_supply: u128,
) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_definition(name, total_supply);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::Public(supply_account_id),
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let first = secrets
.into_iter()
.next()
.expect("expected definition's secret");
(resp, first)
})
}
pub async fn send_new_definition_private_owned_definiton_and_supply(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
name: [u8; 6],
total_supply: u128,
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
let (instruction_data, program) = token_program_preparation_definition(name, total_supply);
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(supply_account_id),
],
&instruction_data,
&program,
)
.await
.map(|(resp, secrets)| {
let mut iter = secrets.into_iter();
let first = iter.next().expect("expected definition's secret");
let second = iter.next().expect("expected supply's secret");
(resp, [first, second])
})
}
pub async fn send_transfer_transaction(
&self,
sender_account_id: AccountId,