mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-26 06:31:19 +00:00
refactor: move programs into programs and UIs into apps
This refactors the repository structure as it has grown over time.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "integration_tests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nssa = { workspace = true }
|
||||
nssa_core = { workspace = true, features = ["host"] }
|
||||
amm_core = { workspace = true }
|
||||
token_core = { workspace = true }
|
||||
ata_core = { workspace = true }
|
||||
stablecoin_core = { workspace = true }
|
||||
token-methods = { path = "../token/methods" }
|
||||
amm-methods = { path = "../amm/methods" }
|
||||
ata-methods = { path = "../ata/methods" }
|
||||
stablecoin-methods = { path = "../stablecoin/methods" }
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,587 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ata_core::{compute_ata_seed, get_associated_token_account_id};
|
||||
use nssa::{
|
||||
execute_and_prove,
|
||||
privacy_preserving_transaction::{
|
||||
circuit::ProgramWithDependencies, Message, PrivacyPreservingTransaction, WitnessSet,
|
||||
},
|
||||
program::Program,
|
||||
program_deployment_transaction::{self, ProgramDeploymentTransaction},
|
||||
public_transaction, EphemeralPublicKey, PrivateKey, PublicKey, PublicTransaction,
|
||||
SharedSecretKey, V03State,
|
||||
};
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, AccountWithMetadata, Data, Nonce},
|
||||
encryption::{Scalar, ViewingPublicKey},
|
||||
NullifierPublicKey, NullifierSecretKey,
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
struct Keys;
|
||||
struct Ids;
|
||||
struct Accounts;
|
||||
|
||||
impl Keys {
|
||||
fn def_key() -> PrivateKey {
|
||||
PrivateKey::try_new([10; 32]).expect("valid private key")
|
||||
}
|
||||
|
||||
fn owner_key() -> PrivateKey {
|
||||
PrivateKey::try_new([11; 32]).expect("valid private key")
|
||||
}
|
||||
|
||||
fn recipient_key() -> PrivateKey {
|
||||
PrivateKey::try_new([12; 32]).expect("valid private key")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ids {
|
||||
fn token_program() -> nssa_core::program::ProgramId {
|
||||
token_methods::TOKEN_ID
|
||||
}
|
||||
|
||||
fn ata_program() -> nssa_core::program::ProgramId {
|
||||
ata_methods::ATA_ID
|
||||
}
|
||||
|
||||
fn token_definition() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::def_key()))
|
||||
}
|
||||
|
||||
fn owner() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::owner_key()))
|
||||
}
|
||||
|
||||
fn recipient() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::recipient_key()))
|
||||
}
|
||||
|
||||
fn owner_ata() -> AccountId {
|
||||
let seed = compute_ata_seed(
|
||||
Self::token_program(),
|
||||
Self::owner(),
|
||||
Self::token_definition(),
|
||||
);
|
||||
get_associated_token_account_id(&Self::ata_program(), &seed)
|
||||
}
|
||||
|
||||
fn recipient_ata() -> AccountId {
|
||||
let seed = compute_ata_seed(
|
||||
Self::token_program(),
|
||||
Self::recipient(),
|
||||
Self::token_definition(),
|
||||
);
|
||||
get_associated_token_account_id(&Self::ata_program(), &seed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Accounts {
|
||||
fn token_definition_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn owner_ata_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 1_000_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn recipient_ata_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 0_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn foreign_owned_token_definition() -> Account {
|
||||
Account {
|
||||
program_owner: [99; 8],
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Foreign Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy_programs(state: &mut V03State) {
|
||||
let token_message =
|
||||
program_deployment_transaction::Message::new(token_methods::TOKEN_ELF.to_vec());
|
||||
state
|
||||
.transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new(
|
||||
token_message,
|
||||
))
|
||||
.expect("token program deployment must succeed");
|
||||
|
||||
let ata_message = program_deployment_transaction::Message::new(ata_methods::ATA_ELF.to_vec());
|
||||
state
|
||||
.transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new(
|
||||
ata_message,
|
||||
))
|
||||
.expect("ata program deployment must succeed");
|
||||
}
|
||||
|
||||
fn state_for_ata_tests() -> V03State {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
|
||||
state.force_insert_account(Ids::owner_ata(), Accounts::owner_ata_init());
|
||||
state
|
||||
}
|
||||
|
||||
fn state_for_ata_tests_with_precreated_recipient_ata() -> V03State {
|
||||
let mut state = state_for_ata_tests();
|
||||
state.force_insert_account(Ids::recipient_ata(), Accounts::recipient_ata_init());
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_create() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
|
||||
|
||||
let instruction = ata_core::Instruction::Create {
|
||||
token_program_id: Ids::token_program(),
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::owner_ata()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 0_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_create_is_idempotent() {
|
||||
let mut state = state_for_ata_tests();
|
||||
|
||||
let instruction = ata_core::Instruction::Create {
|
||||
token_program_id: Ids::token_program(),
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
// Already initialized — should remain unchanged
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::owner_ata()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 1_000_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_create_rejects_definition_owned_by_unexpected_token_program() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(
|
||||
Ids::token_definition(),
|
||||
Accounts::foreign_owned_token_definition(),
|
||||
);
|
||||
|
||||
let instruction = ata_core::Instruction::Create {
|
||||
token_program_id: Ids::token_program(),
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::owner_ata()),
|
||||
Account::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_create_rejects_existing_ata_owned_by_unexpected_token_program() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
|
||||
|
||||
let mut foreign_ata = Accounts::owner_ata_init();
|
||||
foreign_ata.program_owner = [99; 8];
|
||||
state.force_insert_account(Ids::owner_ata(), foreign_ata.clone());
|
||||
|
||||
let instruction = ata_core::Instruction::Create {
|
||||
token_program_id: Ids::token_program(),
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
assert_eq!(state.get_account_by_id(Ids::owner_ata()), foreign_ata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_create_rejects_existing_ata_with_mismatched_definition() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
|
||||
|
||||
let mut mismatched_ata = Accounts::owner_ata_init();
|
||||
mismatched_ata.data = Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::recipient(),
|
||||
balance: 1_000_000_u128,
|
||||
});
|
||||
state.force_insert_account(Ids::owner_ata(), mismatched_ata.clone());
|
||||
|
||||
let instruction = ata_core::Instruction::Create {
|
||||
token_program_id: Ids::token_program(),
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
assert_eq!(state.get_account_by_id(Ids::owner_ata()), mismatched_ata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_transfer() {
|
||||
let mut state = state_for_ata_tests_with_precreated_recipient_ata();
|
||||
|
||||
let instruction = ata_core::Instruction::Transfer {
|
||||
token_program_id: Ids::token_program(),
|
||||
amount: 400_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::owner_ata(), Ids::recipient_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::owner_ata()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 600_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient_ata()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 400_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_transfer_rejects_default_recipient() {
|
||||
let mut state = state_for_ata_tests();
|
||||
|
||||
let instruction = ata_core::Instruction::Transfer {
|
||||
token_program_id: Ids::token_program(),
|
||||
amount: 1_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::owner_ata(), Ids::recipient_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::owner_ata()),
|
||||
Accounts::owner_ata_init()
|
||||
);
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient_ata()),
|
||||
Account::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_transfer_rejects_mismatched_definition_recipient() {
|
||||
let mut state = state_for_ata_tests_with_precreated_recipient_ata();
|
||||
|
||||
// Replace the recipient ATA with a token holding pointing at a different definition.
|
||||
let foreign_definition_id = AccountId::from(&PublicKey::new_from_private_key(
|
||||
&PrivateKey::try_new([42; 32]).expect("valid private key"),
|
||||
));
|
||||
let mismatched_recipient = Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: foreign_definition_id,
|
||||
balance: 0_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
};
|
||||
state.force_insert_account(Ids::recipient_ata(), mismatched_recipient.clone());
|
||||
|
||||
let instruction = ata_core::Instruction::Transfer {
|
||||
token_program_id: Ids::token_program(),
|
||||
amount: 1_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::owner_ata(), Ids::recipient_ata()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::owner_ata()),
|
||||
Accounts::owner_ata_init()
|
||||
);
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient_ata()),
|
||||
mismatched_recipient
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_burn() {
|
||||
let mut state = state_for_ata_tests();
|
||||
|
||||
let instruction = ata_core::Instruction::Burn {
|
||||
token_program_id: Ids::token_program(),
|
||||
amount: 300_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::ata_program(),
|
||||
vec![Ids::owner(), Ids::owner_ata(), Ids::token_definition()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::owner_ata()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 700_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 700_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_create_from_private_owner() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
|
||||
|
||||
// Private owner key material
|
||||
let owner_nsk: NullifierSecretKey = [13u8; 32];
|
||||
let owner_npk = NullifierPublicKey::from(&owner_nsk);
|
||||
let owner_vsk: Scalar = [31u8; 32];
|
||||
let owner_vpk = ViewingPublicKey::from_scalar(owner_vsk);
|
||||
let owner_id = AccountId::from(&owner_npk);
|
||||
|
||||
// ATA derived from the private owner
|
||||
let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition());
|
||||
let owner_ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed);
|
||||
|
||||
// Pre-states: private uninitialized owner (mask=2), public token definition (mask=0), public
|
||||
// uninitialized ATA (mask=0)
|
||||
let owner_pre = AccountWithMetadata::new(Account::default(), false, owner_id);
|
||||
let def_pre = AccountWithMetadata::new(
|
||||
Accounts::token_definition_init(),
|
||||
false,
|
||||
Ids::token_definition(),
|
||||
);
|
||||
let ata_pre = AccountWithMetadata::new(Account::default(), false, owner_ata_id);
|
||||
|
||||
let instruction = ata_core::Instruction::Create {
|
||||
token_program_id: Ids::token_program(),
|
||||
};
|
||||
let instruction_data = Program::serialize_instruction(instruction).unwrap();
|
||||
|
||||
// Ephemeral key for encrypting the private owner's post-state
|
||||
let esk: Scalar = [3u8; 32];
|
||||
let shared_secret = SharedSecretKey::new(&esk, &owner_vpk);
|
||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||
|
||||
let ata_program = Program::new(ata_methods::ATA_ELF.to_vec()).unwrap();
|
||||
let token_program = Program::new(token_methods::TOKEN_ELF.to_vec()).unwrap();
|
||||
let program_with_deps = ProgramWithDependencies::new(
|
||||
ata_program,
|
||||
HashMap::from([(Ids::token_program(), token_program)]),
|
||||
);
|
||||
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![owner_pre, def_pre, ata_pre],
|
||||
instruction_data,
|
||||
// owner=new private (2), token_definition=public (0), ata=public (0)
|
||||
vec![2, 0, 0],
|
||||
vec![(owner_npk, shared_secret)],
|
||||
vec![], // no NSKs: new private accounts don't require one
|
||||
vec![None], // no membership proof: owner is being created, not spending
|
||||
&program_with_deps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![Ids::token_definition(), owner_ata_id],
|
||||
vec![],
|
||||
vec![(owner_npk, owner_vpk, epk)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
state
|
||||
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(owner_ata_id),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 0_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
use nssa::{
|
||||
program_deployment_transaction::{self, ProgramDeploymentTransaction},
|
||||
public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State,
|
||||
};
|
||||
use nssa_core::account::{Account, AccountId, Data, Nonce};
|
||||
use stablecoin_core::{compute_position_pda, compute_position_vault_pda, Position};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
struct Keys;
|
||||
struct Ids;
|
||||
struct Balances;
|
||||
struct Accounts;
|
||||
|
||||
impl Keys {
|
||||
fn owner() -> PrivateKey {
|
||||
PrivateKey::try_new([41; 32]).expect("valid private key")
|
||||
}
|
||||
|
||||
fn user_holding() -> PrivateKey {
|
||||
PrivateKey::try_new([42; 32]).expect("valid private key")
|
||||
}
|
||||
|
||||
fn user_stablecoin_holding() -> PrivateKey {
|
||||
PrivateKey::try_new([43; 32]).expect("valid private key")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ids {
|
||||
fn token_program() -> nssa_core::program::ProgramId {
|
||||
token_methods::TOKEN_ID
|
||||
}
|
||||
|
||||
fn stablecoin_program() -> nssa_core::program::ProgramId {
|
||||
stablecoin_methods::STABLECOIN_ID
|
||||
}
|
||||
|
||||
fn collateral_definition() -> AccountId {
|
||||
AccountId::new([5; 32])
|
||||
}
|
||||
|
||||
fn owner() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::owner()))
|
||||
}
|
||||
|
||||
fn user_holding() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::user_holding()))
|
||||
}
|
||||
|
||||
fn stablecoin_definition() -> AccountId {
|
||||
AccountId::new([6; 32])
|
||||
}
|
||||
|
||||
fn user_stablecoin_holding() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(
|
||||
&Keys::user_stablecoin_holding(),
|
||||
))
|
||||
}
|
||||
|
||||
fn position() -> AccountId {
|
||||
compute_position_pda(
|
||||
Self::stablecoin_program(),
|
||||
Self::owner(),
|
||||
Self::collateral_definition(),
|
||||
)
|
||||
}
|
||||
|
||||
fn vault() -> AccountId {
|
||||
compute_position_vault_pda(Self::stablecoin_program(), Self::position())
|
||||
}
|
||||
}
|
||||
|
||||
impl Balances {
|
||||
fn user_holding_init() -> u128 {
|
||||
1_000_000
|
||||
}
|
||||
|
||||
fn collateral_deposit() -> u128 {
|
||||
500_000
|
||||
}
|
||||
|
||||
fn collateral_withdraw() -> u128 {
|
||||
200_000
|
||||
}
|
||||
|
||||
fn stablecoin_supply_init() -> u128 {
|
||||
1_000
|
||||
}
|
||||
|
||||
fn user_stablecoin_holding_init() -> u128 {
|
||||
1_000
|
||||
}
|
||||
|
||||
fn initial_debt() -> u128 {
|
||||
300
|
||||
}
|
||||
|
||||
fn debt_repay_amount() -> u128 {
|
||||
100
|
||||
}
|
||||
}
|
||||
|
||||
impl Accounts {
|
||||
fn collateral_definition_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: Balances::user_holding_init(),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn user_holding_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::collateral_definition(),
|
||||
balance: Balances::user_holding_init(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn stablecoin_definition_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("DAI"),
|
||||
total_supply: Balances::stablecoin_supply_init(),
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn user_stablecoin_holding_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::stablecoin_definition(),
|
||||
balance: Balances::user_stablecoin_holding_init(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn position_with_debt_init() -> Account {
|
||||
Account {
|
||||
program_owner: stablecoin_methods::STABLECOIN_ID,
|
||||
balance: 0_u128,
|
||||
data: Data::from(&Position {
|
||||
collateral_vault_id: Ids::vault(),
|
||||
collateral_definition_id: Ids::collateral_definition(),
|
||||
collateral_amount: Balances::collateral_deposit(),
|
||||
debt_amount: Balances::initial_debt(),
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy_programs(state: &mut V03State) {
|
||||
let token_message =
|
||||
program_deployment_transaction::Message::new(token_methods::TOKEN_ELF.to_vec());
|
||||
state
|
||||
.transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new(
|
||||
token_message,
|
||||
))
|
||||
.expect("token program deployment must succeed");
|
||||
|
||||
let stablecoin_message =
|
||||
program_deployment_transaction::Message::new(stablecoin_methods::STABLECOIN_ELF.to_vec());
|
||||
state
|
||||
.transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new(
|
||||
stablecoin_message,
|
||||
))
|
||||
.expect("stablecoin program deployment must succeed");
|
||||
}
|
||||
|
||||
fn state_for_stablecoin_tests() -> V03State {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(
|
||||
Ids::collateral_definition(),
|
||||
Accounts::collateral_definition_init(),
|
||||
);
|
||||
state.force_insert_account(Ids::user_holding(), Accounts::user_holding_init());
|
||||
state
|
||||
}
|
||||
|
||||
fn current_nonce(state: &V03State, account_id: AccountId) -> Nonce {
|
||||
state.get_account_by_id(account_id).nonce
|
||||
}
|
||||
|
||||
fn state_for_stablecoin_repay_tests() -> V03State {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_programs(&mut state);
|
||||
state.force_insert_account(
|
||||
Ids::collateral_definition(),
|
||||
Accounts::collateral_definition_init(),
|
||||
);
|
||||
state.force_insert_account(
|
||||
Ids::stablecoin_definition(),
|
||||
Accounts::stablecoin_definition_init(),
|
||||
);
|
||||
state.force_insert_account(Ids::position(), Accounts::position_with_debt_init());
|
||||
state.force_insert_account(
|
||||
Ids::user_stablecoin_holding(),
|
||||
Accounts::user_stablecoin_holding_init(),
|
||||
);
|
||||
state
|
||||
}
|
||||
|
||||
fn assert_position(state: &V03State, expected_collateral: u128) {
|
||||
let position =
|
||||
Position::try_from(&state.get_account_by_id(Ids::position()).data).expect("valid Position");
|
||||
assert_eq!(position.collateral_amount, expected_collateral);
|
||||
assert_eq!(position.debt_amount, 0);
|
||||
assert_eq!(position.collateral_vault_id, Ids::vault());
|
||||
assert_eq!(
|
||||
position.collateral_definition_id,
|
||||
Ids::collateral_definition()
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_fungible_balance(state: &V03State, account_id: AccountId, expected_balance: u128) {
|
||||
let holding = TokenHolding::try_from(&state.get_account_by_id(account_id).data)
|
||||
.expect("valid TokenHolding");
|
||||
match holding {
|
||||
TokenHolding::Fungible {
|
||||
definition_id,
|
||||
balance,
|
||||
} => {
|
||||
assert_eq!(definition_id, Ids::collateral_definition());
|
||||
assert_eq!(balance, expected_balance);
|
||||
}
|
||||
TokenHolding::NftMaster { .. } | TokenHolding::NftPrintedCopy { .. } => {
|
||||
panic!("expected Fungible holding")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stablecoin_open_position_then_withdraw_collateral() {
|
||||
let mut state = state_for_stablecoin_tests();
|
||||
|
||||
// Open the position: deposit collateral from the user's holding into a fresh vault.
|
||||
let open = stablecoin_core::Instruction::OpenPosition {
|
||||
collateral_amount: Balances::collateral_deposit(),
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::stablecoin_program(),
|
||||
vec![
|
||||
Ids::owner(),
|
||||
Ids::position(),
|
||||
Ids::vault(),
|
||||
Ids::user_holding(),
|
||||
Ids::collateral_definition(),
|
||||
],
|
||||
vec![
|
||||
current_nonce(&state, Ids::owner()),
|
||||
current_nonce(&state, Ids::user_holding()),
|
||||
],
|
||||
open,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::owner(), &Keys::user_holding()],
|
||||
);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state
|
||||
.transition_from_public_transaction(&tx, 0, 0)
|
||||
.expect("open_position must succeed");
|
||||
|
||||
assert_position(&state, Balances::collateral_deposit());
|
||||
assert_fungible_balance(&state, Ids::vault(), Balances::collateral_deposit());
|
||||
assert_fungible_balance(
|
||||
&state,
|
||||
Ids::user_holding(),
|
||||
Balances::user_holding_init() - Balances::collateral_deposit(),
|
||||
);
|
||||
|
||||
// Withdraw part of the collateral back to the same user holding.
|
||||
let withdraw = stablecoin_core::Instruction::WithdrawCollateral {
|
||||
amount: Balances::collateral_withdraw(),
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::stablecoin_program(),
|
||||
vec![
|
||||
Ids::owner(),
|
||||
Ids::position(),
|
||||
Ids::vault(),
|
||||
Ids::user_holding(),
|
||||
],
|
||||
vec![current_nonce(&state, Ids::owner())],
|
||||
withdraw,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner()]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state
|
||||
.transition_from_public_transaction(&tx, 0, 0)
|
||||
.expect("withdraw_collateral must succeed");
|
||||
|
||||
assert_position(
|
||||
&state,
|
||||
Balances::collateral_deposit() - Balances::collateral_withdraw(),
|
||||
);
|
||||
assert_fungible_balance(
|
||||
&state,
|
||||
Ids::vault(),
|
||||
Balances::collateral_deposit() - Balances::collateral_withdraw(),
|
||||
);
|
||||
assert_fungible_balance(
|
||||
&state,
|
||||
Ids::user_holding(),
|
||||
Balances::user_holding_init() - Balances::collateral_deposit()
|
||||
+ Balances::collateral_withdraw(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stablecoin_repay_debt_burns_stablecoins_and_decreases_debt() {
|
||||
let mut state = state_for_stablecoin_repay_tests();
|
||||
|
||||
let repay = stablecoin_core::Instruction::RepayDebt {
|
||||
amount: Balances::debt_repay_amount(),
|
||||
};
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::stablecoin_program(),
|
||||
vec![
|
||||
Ids::owner(),
|
||||
Ids::position(),
|
||||
Ids::stablecoin_definition(),
|
||||
Ids::user_stablecoin_holding(),
|
||||
],
|
||||
vec![
|
||||
current_nonce(&state, Ids::owner()),
|
||||
current_nonce(&state, Ids::user_stablecoin_holding()),
|
||||
],
|
||||
repay,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::owner(), &Keys::user_stablecoin_holding()],
|
||||
);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state
|
||||
.transition_from_public_transaction(&tx, 0, 0)
|
||||
.expect("repay_debt must succeed");
|
||||
|
||||
// Position debt decreased; collateral untouched.
|
||||
let position =
|
||||
Position::try_from(&state.get_account_by_id(Ids::position()).data).expect("valid Position");
|
||||
assert_eq!(
|
||||
position.debt_amount,
|
||||
Balances::initial_debt() - Balances::debt_repay_amount()
|
||||
);
|
||||
assert_eq!(position.collateral_amount, Balances::collateral_deposit());
|
||||
|
||||
// Stablecoin total supply decreased by the burn amount.
|
||||
let definition =
|
||||
TokenDefinition::try_from(&state.get_account_by_id(Ids::stablecoin_definition()).data)
|
||||
.expect("valid TokenDefinition");
|
||||
match definition {
|
||||
TokenDefinition::Fungible { total_supply, .. } => {
|
||||
assert_eq!(
|
||||
total_supply,
|
||||
Balances::stablecoin_supply_init() - Balances::debt_repay_amount()
|
||||
);
|
||||
}
|
||||
TokenDefinition::NonFungible { .. } => panic!("expected Fungible definition"),
|
||||
}
|
||||
|
||||
// User stablecoin holding decreased by the burn amount.
|
||||
let holding =
|
||||
TokenHolding::try_from(&state.get_account_by_id(Ids::user_stablecoin_holding()).data)
|
||||
.expect("valid TokenHolding");
|
||||
match holding {
|
||||
TokenHolding::Fungible { balance, .. } => {
|
||||
assert_eq!(
|
||||
balance,
|
||||
Balances::user_stablecoin_holding_init() - Balances::debt_repay_amount()
|
||||
);
|
||||
}
|
||||
TokenHolding::NftMaster { .. } | TokenHolding::NftPrintedCopy { .. } => {
|
||||
panic!("expected Fungible holding")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,906 @@
|
||||
use nssa::{
|
||||
execute_and_prove,
|
||||
privacy_preserving_transaction::{Message, WitnessSet},
|
||||
program::Program,
|
||||
program_deployment_transaction::{self, ProgramDeploymentTransaction},
|
||||
public_transaction, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction,
|
||||
SharedSecretKey, V03State,
|
||||
};
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, AccountWithMetadata, Data, Nonce},
|
||||
encryption::{EphemeralPublicKey, ViewingPublicKey},
|
||||
Commitment, NullifierPublicKey, NullifierSecretKey,
|
||||
};
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
struct Keys;
|
||||
struct Ids;
|
||||
struct Accounts;
|
||||
|
||||
impl Keys {
|
||||
fn def_key() -> PrivateKey {
|
||||
PrivateKey::try_new([10; 32]).expect("valid private key")
|
||||
}
|
||||
|
||||
fn holder_key() -> PrivateKey {
|
||||
PrivateKey::try_new([11; 32]).expect("valid private key")
|
||||
}
|
||||
|
||||
fn recipient_key() -> PrivateKey {
|
||||
PrivateKey::try_new([12; 32]).expect("valid private key")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ids {
|
||||
fn token_program() -> nssa_core::program::ProgramId {
|
||||
token_methods::TOKEN_ID
|
||||
}
|
||||
|
||||
fn foreign_token_program() -> nssa_core::program::ProgramId {
|
||||
[0xfeed_u32; 8]
|
||||
}
|
||||
|
||||
fn token_definition() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::def_key()))
|
||||
}
|
||||
|
||||
fn holder() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::holder_key()))
|
||||
}
|
||||
|
||||
fn recipient() -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(&Keys::recipient_key()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Accounts {
|
||||
fn token_definition_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn token_definition_foreign_owner() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::foreign_token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn holder_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 1_000_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn recipient_init() -> Account {
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 0_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy_token(state: &mut V03State) {
|
||||
let message = program_deployment_transaction::Message::new(token_methods::TOKEN_ELF.to_vec());
|
||||
let tx = ProgramDeploymentTransaction::new(message);
|
||||
state
|
||||
.transition_from_program_deployment_transaction(&tx)
|
||||
.expect("token program deployment must succeed");
|
||||
}
|
||||
|
||||
fn state_for_token_tests() -> V03State {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_token(&mut state);
|
||||
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
|
||||
state.force_insert_account(Ids::holder(), Accounts::holder_init());
|
||||
state.force_insert_account(Ids::recipient(), Accounts::recipient_init());
|
||||
state
|
||||
}
|
||||
|
||||
fn state_for_token_tests_without_recipient() -> V03State {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_token(&mut state);
|
||||
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
|
||||
state.force_insert_account(Ids::holder(), Accounts::holder_init());
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_new_fungible_definition() {
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
deploy_token(&mut state);
|
||||
|
||||
let instruction = token_core::Instruction::NewFungibleDefinition {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::holder()],
|
||||
vec![Nonce(0), Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::def_key(), &Keys::holder_key()],
|
||||
);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_000_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::holder()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 1_000_000_u128,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_initialize_account_succeeds_for_canonical_definition() {
|
||||
let mut state = state_for_token_tests_without_recipient();
|
||||
|
||||
let instruction = token_core::Instruction::InitializeAccount;
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::recipient_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Accounts::token_definition_init()
|
||||
);
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 0_u128,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_initialize_account_rejects_foreign_owned_definition() {
|
||||
let mut state = state_for_token_tests_without_recipient();
|
||||
state.force_insert_account(
|
||||
Ids::token_definition(),
|
||||
Accounts::token_definition_foreign_owner(),
|
||||
);
|
||||
|
||||
let instruction = token_core::Instruction::InitializeAccount;
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
public_transaction::WitnessSet::for_message(&message, &[&Keys::recipient_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Accounts::token_definition_foreign_owner()
|
||||
);
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_transfer() {
|
||||
let mut state = state_for_token_tests();
|
||||
|
||||
let instruction = token_core::Instruction::Transfer {
|
||||
amount_to_transfer: 500_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::holder(), Ids::recipient()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::holder_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::holder()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 500_000_u128,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 500_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_transfer_fresh_public_recipient_requires_authorization() {
|
||||
let mut state = state_for_token_tests_without_recipient();
|
||||
|
||||
let instruction = token_core::Instruction::Transfer {
|
||||
amount_to_transfer: 500_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::holder(), Ids::recipient()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::holder_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::holder()),
|
||||
Accounts::holder_init()
|
||||
);
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_transfer_fresh_authorized_public_recipient() {
|
||||
let mut state = state_for_token_tests_without_recipient();
|
||||
|
||||
let instruction = token_core::Instruction::Transfer {
|
||||
amount_to_transfer: 500_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::holder(), Ids::recipient()],
|
||||
vec![Nonce(0), Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::holder_key(), &Keys::recipient_key()],
|
||||
);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::holder()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 500_000_u128,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 500_000_u128,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_burn() {
|
||||
let mut state = state_for_token_tests();
|
||||
|
||||
let instruction = token_core::Instruction::Burn {
|
||||
amount_to_burn: 200_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::holder()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::holder_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 800_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::holder()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 800_000_u128,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_mint() {
|
||||
let mut state = state_for_token_tests();
|
||||
|
||||
let instruction = token_core::Instruction::Mint {
|
||||
amount_to_mint: 500_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::holder()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_500_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::holder()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 1_500_000_u128,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_mint_rejects_foreign_owned_definition() {
|
||||
let mut state = state_for_token_tests_without_recipient();
|
||||
state.force_insert_account(
|
||||
Ids::token_definition(),
|
||||
Accounts::token_definition_foreign_owner(),
|
||||
);
|
||||
|
||||
let instruction = token_core::Instruction::Mint {
|
||||
amount_to_mint: 500_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0), Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::def_key(), &Keys::recipient_key()],
|
||||
);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Accounts::token_definition_foreign_owner()
|
||||
);
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_mint_fresh_public_recipient_requires_authorization() {
|
||||
let mut state = state_for_token_tests_without_recipient();
|
||||
|
||||
let instruction = token_core::Instruction::Mint {
|
||||
amount_to_mint: 500_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Accounts::token_definition_init()
|
||||
);
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_mint_fresh_authorized_public_recipient() {
|
||||
let mut state = state_for_token_tests_without_recipient();
|
||||
|
||||
let instruction = token_core::Instruction::Mint {
|
||||
amount_to_mint: 500_000_u128,
|
||||
};
|
||||
|
||||
let message = public_transaction::Message::try_new(
|
||||
Ids::token_program(),
|
||||
vec![Ids::token_definition(), Ids::recipient()],
|
||||
vec![Nonce(0), Nonce(0)],
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = public_transaction::WitnessSet::for_message(
|
||||
&message,
|
||||
&[&Keys::def_key(), &Keys::recipient_key()],
|
||||
);
|
||||
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::token_definition()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenDefinition::Fungible {
|
||||
name: String::from("Gold"),
|
||||
total_supply: 1_500_000_u128,
|
||||
metadata_id: None,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::recipient()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0_u128,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 500_000_u128,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
struct PrivateKeys;
|
||||
|
||||
impl PrivateKeys {
|
||||
fn holder_nsk() -> NullifierSecretKey {
|
||||
[42; 32]
|
||||
}
|
||||
|
||||
fn holder_npk() -> NullifierPublicKey {
|
||||
NullifierPublicKey::from(&Self::holder_nsk())
|
||||
}
|
||||
|
||||
fn holder_vsk() -> [u8; 32] {
|
||||
[73; 32]
|
||||
}
|
||||
|
||||
fn holder_vpk() -> ViewingPublicKey {
|
||||
ViewingPublicKey::from_scalar(Self::holder_vsk())
|
||||
}
|
||||
|
||||
fn recipient_nsk() -> NullifierSecretKey {
|
||||
[84; 32]
|
||||
}
|
||||
|
||||
fn recipient_npk() -> NullifierPublicKey {
|
||||
NullifierPublicKey::from(&Self::recipient_nsk())
|
||||
}
|
||||
|
||||
fn recipient_vsk() -> [u8; 32] {
|
||||
[48; 32]
|
||||
}
|
||||
|
||||
fn recipient_vpk() -> ViewingPublicKey {
|
||||
ViewingPublicKey::from_scalar(Self::recipient_vsk())
|
||||
}
|
||||
}
|
||||
|
||||
fn token_program() -> Program {
|
||||
Program::new(token_methods::TOKEN_ELF.to_vec()).expect("valid token ELF")
|
||||
}
|
||||
|
||||
/// Performs a shielded transfer (public → private) of `amount` tokens from
|
||||
/// `Ids::holder()` to a new private account keyed by `PrivateKeys::recipient_*`.
|
||||
/// Returns the resulting private recipient account.
|
||||
#[cfg(test)]
|
||||
fn shielded_token_transfer(amount: u128, state: &mut V03State) -> Account {
|
||||
let sender_id = Ids::holder();
|
||||
let sender_account = state.get_account_by_id(sender_id);
|
||||
let sender_nonce = sender_account.nonce;
|
||||
|
||||
let sender = AccountWithMetadata::new(sender_account, true, sender_id);
|
||||
let recipient =
|
||||
AccountWithMetadata::new(Account::default(), false, &PrivateKeys::recipient_npk());
|
||||
|
||||
let esk = [99u8; 32];
|
||||
let shared_secret = SharedSecretKey::new(&esk, &PrivateKeys::recipient_vpk());
|
||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||
|
||||
let instruction = token_core::Instruction::Transfer {
|
||||
amount_to_transfer: amount,
|
||||
};
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender, recipient],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![0, 2],
|
||||
vec![(PrivateKeys::recipient_npk(), shared_secret)],
|
||||
vec![],
|
||||
vec![None],
|
||||
&token_program().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![sender_id],
|
||||
vec![sender_nonce],
|
||||
vec![(
|
||||
PrivateKeys::recipient_npk(),
|
||||
PrivateKeys::recipient_vpk(),
|
||||
epk,
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::holder_key()]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
state
|
||||
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
|
||||
.unwrap();
|
||||
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: amount,
|
||||
}),
|
||||
nonce: Nonce::private_account_nonce_init(&PrivateKeys::recipient_npk()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_shielded_transfer() {
|
||||
let mut state = state_for_token_tests();
|
||||
let amount = 500_000_u128;
|
||||
|
||||
let recipient_account = shielded_token_transfer(amount, &mut state);
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::holder()),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: 1_000_000 - amount,
|
||||
}),
|
||||
nonce: Nonce(1),
|
||||
}
|
||||
);
|
||||
|
||||
let recipient_commitment = Commitment::new(&PrivateKeys::recipient_npk(), &recipient_account);
|
||||
assert!(state
|
||||
.get_proof_for_commitment(&recipient_commitment)
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_private_transfer() {
|
||||
let mut state = state_for_token_tests();
|
||||
let shielded_amount = 500_000_u128;
|
||||
let transfer_amount = 200_000_u128;
|
||||
|
||||
// Shield tokens into a private account (becomes the sender for the private transfer).
|
||||
let sender_account = shielded_token_transfer(shielded_amount, &mut state);
|
||||
let sender_npk = PrivateKeys::recipient_npk();
|
||||
let sender_nsk = PrivateKeys::recipient_nsk();
|
||||
let sender_vpk = PrivateKeys::recipient_vpk();
|
||||
|
||||
let new_recipient_npk = PrivateKeys::holder_npk();
|
||||
let new_recipient_vpk = PrivateKeys::holder_vpk();
|
||||
|
||||
let sender_commitment = Commitment::new(&sender_npk, &sender_account);
|
||||
|
||||
let esk_1 = [11u8; 32];
|
||||
let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_vpk);
|
||||
let epk_1 = EphemeralPublicKey::from_scalar(esk_1);
|
||||
|
||||
let esk_2 = [22u8; 32];
|
||||
let shared_secret_2 = SharedSecretKey::new(&esk_2, &new_recipient_vpk);
|
||||
let epk_2 = EphemeralPublicKey::from_scalar(esk_2);
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, &sender_npk);
|
||||
let new_recipient_pre = AccountWithMetadata::new(Account::default(), false, &new_recipient_npk);
|
||||
|
||||
let instruction = token_core::Instruction::Transfer {
|
||||
amount_to_transfer: transfer_amount,
|
||||
};
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender_pre, new_recipient_pre],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![1, 2],
|
||||
vec![
|
||||
(sender_npk, shared_secret_1),
|
||||
(new_recipient_npk, shared_secret_2),
|
||||
],
|
||||
vec![sender_nsk],
|
||||
vec![state.get_proof_for_commitment(&sender_commitment), None],
|
||||
&token_program().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(sender_npk, sender_vpk, epk_1),
|
||||
(new_recipient_npk, new_recipient_vpk, epk_2),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
state
|
||||
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
|
||||
.unwrap();
|
||||
|
||||
let sender_nonce_after =
|
||||
Nonce::private_account_nonce_init(&sender_npk).private_account_nonce_increment(&sender_nsk);
|
||||
let new_sender_account = Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: shielded_amount - transfer_amount,
|
||||
}),
|
||||
nonce: sender_nonce_after,
|
||||
};
|
||||
assert!(state
|
||||
.get_proof_for_commitment(&Commitment::new(&sender_npk, &new_sender_account))
|
||||
.is_some());
|
||||
|
||||
let new_recipient_account = Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: transfer_amount,
|
||||
}),
|
||||
nonce: Nonce::private_account_nonce_init(&new_recipient_npk),
|
||||
};
|
||||
assert!(state
|
||||
.get_proof_for_commitment(&Commitment::new(&new_recipient_npk, &new_recipient_account))
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_deshielded_transfer() {
|
||||
let mut state = state_for_token_tests();
|
||||
let shielded_amount = 500_000_u128;
|
||||
let deshield_amount = 300_000_u128;
|
||||
|
||||
// Shield tokens into a private account, then deshield some back to a public account.
|
||||
let sender_account = shielded_token_transfer(shielded_amount, &mut state);
|
||||
let sender_npk = PrivateKeys::recipient_npk();
|
||||
let sender_nsk = PrivateKeys::recipient_nsk();
|
||||
let sender_vpk = PrivateKeys::recipient_vpk();
|
||||
|
||||
let public_recipient_id = Ids::recipient();
|
||||
let sender_commitment = Commitment::new(&sender_npk, &sender_account);
|
||||
|
||||
let esk = [55u8; 32];
|
||||
let shared_secret = SharedSecretKey::new(&esk, &sender_vpk);
|
||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||
|
||||
let public_recipient_pre = AccountWithMetadata::new(
|
||||
state.get_account_by_id(public_recipient_id),
|
||||
false,
|
||||
public_recipient_id,
|
||||
);
|
||||
let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, &sender_npk);
|
||||
|
||||
let instruction = token_core::Instruction::Transfer {
|
||||
amount_to_transfer: deshield_amount,
|
||||
};
|
||||
let (output, proof) = execute_and_prove(
|
||||
vec![sender_pre, public_recipient_pre],
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
vec![1, 0],
|
||||
vec![(sender_npk, shared_secret)],
|
||||
vec![sender_nsk],
|
||||
vec![state.get_proof_for_commitment(&sender_commitment)],
|
||||
&token_program().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![public_recipient_id],
|
||||
vec![],
|
||||
vec![(sender_npk, sender_vpk, epk)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
state
|
||||
.transition_from_privacy_preserving_transaction(&tx, 0, 0)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(public_recipient_id),
|
||||
Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: deshield_amount,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
}
|
||||
);
|
||||
|
||||
let sender_nonce_after =
|
||||
Nonce::private_account_nonce_init(&sender_npk).private_account_nonce_increment(&sender_nsk);
|
||||
let new_sender_account = Account {
|
||||
program_owner: Ids::token_program(),
|
||||
balance: 0,
|
||||
data: Data::from(&TokenHolding::Fungible {
|
||||
definition_id: Ids::token_definition(),
|
||||
balance: shielded_amount - deshield_amount,
|
||||
}),
|
||||
nonce: sender_nonce_after,
|
||||
};
|
||||
assert!(state
|
||||
.get_proof_for_commitment(&Commitment::new(&sender_npk, &new_sender_account))
|
||||
.is_some());
|
||||
}
|
||||
Reference in New Issue
Block a user