use winterwallet_client::*;
use winterwallet_core::WinternitzKeypair;
#[test]
fn initialize_instruction_layout() {
let payer = solana_address::Address::from([1u8; 32]);
let wallet_pda = solana_address::Address::from([2u8; 32]);
let sig = [0xABu8; SIGNATURE_LEN];
let root = [0xCDu8; 32];
let ix = initialize(&payer, &wallet_pda, &sig, &root);
assert_eq!(ix.program_id, ID);
assert_eq!(ix.accounts.len(), 3);
assert!(ix.accounts[0].is_signer);
assert!(!ix.accounts[1].is_signer);
assert!(!ix.accounts[2].is_signer);
assert_eq!(ix.data.len(), 1 + SIGNATURE_LEN + 32);
assert_eq!(ix.data[0], discriminator::INITIALIZE);
assert_eq!(&ix.data[1..1 + SIGNATURE_LEN], &sig[..]);
assert_eq!(&ix.data[1 + SIGNATURE_LEN..], &root[..]);
}
#[test]
fn withdraw_instruction_layout() {
let wallet_pda = solana_address::Address::from([2u8; 32]);
let receiver = solana_address::Address::from([3u8; 32]);
let lamports: u64 = 1_000_000_000;
let ix = withdraw(&wallet_pda, &receiver, lamports);
assert_eq!(ix.program_id, ID);
assert_eq!(ix.accounts.len(), 2);
assert!(!ix.accounts[0].is_signer); assert!(ix.accounts[0].is_writable);
assert!(!ix.accounts[1].is_signer);
assert_eq!(ix.data.len(), 1 + 8);
assert_eq!(ix.data[0], discriminator::WITHDRAW);
assert_eq!(
u64::from_le_bytes(ix.data[1..9].try_into().unwrap()),
lamports
);
}
#[test]
fn advance_instruction_layout() {
let wallet_pda = solana_address::Address::from([2u8; 32]);
let sig = [0xABu8; SIGNATURE_LEN];
let root = [0xCDu8; 32];
let payload = [0x01, 0x00, 0x00, 0x00];
let accounts = vec![solana_instruction::AccountMeta::new_readonly(
solana_address::Address::from([5u8; 32]),
false,
)];
let ix = advance(&wallet_pda, &accounts, &sig, &root, &payload);
assert_eq!(ix.program_id, ID);
assert_eq!(ix.accounts.len(), 2);
assert_eq!(ix.data.len(), 1 + SIGNATURE_LEN + 32 + payload.len());
assert_eq!(ix.data[0], discriminator::ADVANCE);
}
#[test]
fn encode_advance_withdraw_roundtrip() {
let wallet_pda = solana_address::Address::from([2u8; 32]);
let receiver = solana_address::Address::from([3u8; 32]);
let lamports: u64 = 500_000;
let inner = withdraw(&wallet_pda, &receiver, lamports);
let payload = encode_advance(&[inner]).unwrap();
assert_eq!(payload.data[0], 1); assert_eq!(payload.data[1], 2); let data_len = u16::from_le_bytes([payload.data[2], payload.data[3]]);
assert_eq!(data_len, 9);
assert_eq!(payload.accounts.len(), 3); assert_eq!(payload.accounts[0].pubkey, ID); assert!(!payload.accounts[0].is_signer);
assert_eq!(*payload.accounts[1].pubkey.as_array(), [2u8; 32]); assert!(!payload.accounts[1].is_signer); assert!(payload.accounts[1].is_writable);
assert_eq!(*payload.accounts[2].pubkey.as_array(), [3u8; 32]); assert!(!payload.accounts[2].is_signer);
}
#[test]
fn encode_advance_validates_limits() {
let dummy = winterwallet_client::withdraw(
&solana_address::Address::from([1u8; 32]),
&solana_address::Address::from([2u8; 32]),
100,
);
let many: Vec<_> = (0..256).map(|_| dummy.clone()).collect();
assert!(encode_advance(&many).is_err());
let mut big_ix = dummy.clone();
for i in 0..17 {
big_ix.accounts.push(solana_instruction::AccountMeta::new(
solana_address::Address::from([i as u8; 32]),
false,
));
}
assert!(encode_advance(&[big_ix]).is_err());
}
#[test]
fn advance_plan_keeps_payload_accounts_and_instruction_in_sync() {
let wallet_pda = solana_address::Address::from([2u8; 32]);
let receiver = solana_address::Address::from([3u8; 32]);
let new_root = [4u8; 32];
let sig = [0u8; SIGNATURE_LEN];
let plan =
winterwallet_client::AdvancePlan::withdraw(&wallet_pda, &receiver, 500_000, &new_root)
.unwrap();
let ix = plan.instruction(&sig);
assert_eq!(plan.payload()[0], 1);
assert_eq!(
plan.account_addresses().len(),
plan.passthrough_accounts().len()
);
assert_eq!(ix.accounts[0].pubkey, wallet_pda);
assert!(!ix.accounts[0].is_signer);
}
#[test]
fn advance_plan_estimates_transaction_size_with_compute_budget() {
let payer = solana_address::Address::from([9u8; 32]);
let wallet_pda = solana_address::Address::from([2u8; 32]);
let receiver = solana_address::Address::from([3u8; 32]);
let new_root = [4u8; 32];
let sig = [0u8; SIGNATURE_LEN];
let plan =
winterwallet_client::AdvancePlan::withdraw(&wallet_pda, &receiver, 500_000, &new_root)
.unwrap();
let size = plan.validate_transaction_size(&payer, &sig).unwrap();
assert!(size > 0);
assert!(size <= winterwallet_client::LEGACY_TRANSACTION_SIZE_LIMIT);
}
#[test]
fn wallet_state_machine_requires_persistence_before_send() {
const MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
let wallet_id = wallet_id_from_mnemonic(MNEMONIC).unwrap();
let mut keypair = WinternitzKeypair::from_mnemonic_at(MNEMONIC, 0, 0, 1).unwrap();
let current_root = keypair
.derive::<WINTERNITZ_SCALARS>()
.to_pubkey()
.merklize();
let next_root = WinternitzKeypair::from_mnemonic_at(MNEMONIC, 0, 0, 2)
.unwrap()
.derive::<WINTERNITZ_SCALARS>()
.to_pubkey()
.merklize();
let account = WinterWalletAccount {
id: wallet_id,
root: current_root,
bump: [255],
};
let wallet = WinterWallet::from_account(&account, SigningPosition::new(0, 0, 1));
let receiver = solana_address::Address::from([3u8; 32]);
let unsigned = wallet
.withdraw_plan(&receiver, 500_000, next_root.as_bytes())
.unwrap();
let signed = unsigned.sign(&mut keypair).unwrap();
assert_eq!(keypair.child(), 2);
assert_eq!(signed.next_position().child(), 2);
struct Store {
next_position: Option<SigningPosition>,
}
impl AdvancePersistence for Store {
type Error = core::convert::Infallible;
fn persist_signed_advance(&mut self, advance: &SignedAdvance) -> Result<(), Self::Error> {
self.next_position = Some(advance.next_position());
Ok(())
}
}
struct Sender;
impl AdvanceSender for Sender {
type Error = core::convert::Infallible;
fn send_persisted_advance(
&mut self,
advance: &PersistedAdvance,
) -> Result<String, Self::Error> {
assert_eq!(advance.signed().next_position().child(), 2);
Ok("submitted".to_string())
}
}
let mut store = Store {
next_position: None,
};
let persisted = signed.persist(&mut store).unwrap();
assert_eq!(store.next_position.unwrap().child(), 2);
let mut sender = Sender;
assert_eq!(persisted.send(&mut sender).unwrap(), "submitted");
}
#[test]
fn signing_position_next_handles_child_rollover_and_overflow() {
let rolled = SigningPosition::new(0, 7, u32::MAX).next().unwrap();
assert_eq!(rolled.wallet(), 0);
assert_eq!(rolled.parent(), 8);
assert_eq!(rolled.child(), 0);
assert!(matches!(
SigningPosition::new(0, u32::MAX, u32::MAX).next(),
Err(Error::PositionOverflow)
));
}