mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-26 22:51:14 +00:00
fix(ata): lock down ATA::Transfer recipient contract
Enforce at the ATA layer that the recipient token holding is already initialized, owned by the same token program as the sender ATA, decodes to a valid `TokenHolding`, and points at the same token definition as the sender. Align the core instruction doc and guest wrapper doc with that contract, and cover the boundary with unit tests (default, foreign-owned, malformed, mismatched-definition recipients, plus the missing-owner-auth and happy paths) and end-to-end integration tests (default and mismatched-definition recipients). Without this, the downstream `token::Transfer` default-recipient `Claim::Authorized` path was reachable through ATA, so integrators had to reverse-engineer recipient semantics from token/runtime internals.
This commit is contained in:
committed by
r4bbit
parent
0b078b2dde
commit
f8cbcc6956
@@ -255,6 +255,85 @@ fn ata_transfer() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_transfer_rejects_default_recipient() {
|
||||
let mut state = state_for_ata_tests();
|
||||
|
||||
let instruction = ata_core::Instruction::Transfer {
|
||||
ata_program_id: Ids::ata_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 {
|
||||
ata_program_id: Ids::ata_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();
|
||||
|
||||
Reference in New Issue
Block a user