diff --git a/Cargo.lock b/Cargo.lock index 9732e53..11882e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3200,12 +3200,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token-ids" +version = "0.1.0" + [[package]] name = "token-methods" version = "0.1.0" dependencies = [ "risc0-build", "risc0-zkvm", + "token-ids", "token_core", ] diff --git a/Cargo.toml b/Cargo.toml index 7021e21..795e17d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "token/ids", "token/core", "token", "token/methods", diff --git a/ata/core/src/lib.rs b/ata/core/src/lib.rs index 4ea46bf..05dcac7 100644 --- a/ata/core/src/lib.rs +++ b/ata/core/src/lib.rs @@ -15,7 +15,7 @@ pub enum Instruction { /// - Token definition account /// - Associated token account (default/uninitialized, or already initialized) /// - /// `token_program_id` is derived from `token_definition.account.program_owner`. + /// The downstream token program is the canonical token program wired by the ATA guest. Create { ata_program_id: ProgramId }, /// Transfer tokens FROM owner's ATA to a recipient token holding account. @@ -26,7 +26,7 @@ pub enum Instruction { /// - Sender ATA (owner's token holding) /// - Recipient token holding (must be initialized) /// - /// `token_program_id` is derived from `sender_ata.account.program_owner`. + /// The downstream token program is the canonical token program wired by the ATA guest. Transfer { ata_program_id: ProgramId, amount: u128, @@ -40,7 +40,7 @@ pub enum Instruction { /// - Owner's ATA (the holding to burn from) /// - Token definition account /// - /// `token_program_id` is derived from `holder_ata.account.program_owner`. + /// The downstream token program is the canonical token program wired by the ATA guest. Burn { ata_program_id: ProgramId, amount: u128, diff --git a/ata/methods/guest/Cargo.lock b/ata/methods/guest/Cargo.lock index 8c55a8a..fb1ea9f 100644 --- a/ata/methods/guest/Cargo.lock +++ b/ata/methods/guest/Cargo.lock @@ -281,6 +281,7 @@ dependencies = [ "risc0-zkvm", "serde", "spel-framework", + "token-ids", "token_core", ] @@ -3145,6 +3146,10 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token-ids" +version = "0.1.0" + [[package]] name = "token_core" version = "0.1.0" diff --git a/ata/methods/guest/Cargo.toml b/ata/methods/guest/Cargo.toml index 22e8f42..b4abd09 100644 --- a/ata/methods/guest/Cargo.toml +++ b/ata/methods/guest/Cargo.toml @@ -15,6 +15,7 @@ nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.gi risc0-zkvm = { version = "=3.0.5", default-features = false } ata_core = { path = "../../core" } ata_program = { path = "../..", package = "ata_program" } +token-ids = { path = "../../../token/ids" } token_core = { path = "../../../token/core" } serde = { version = "1.0", features = ["derive"] } borsh = "1.5" diff --git a/ata/methods/guest/src/bin/ata.rs b/ata/methods/guest/src/bin/ata.rs index ad8a950..0d35e9b 100644 --- a/ata/methods/guest/src/bin/ata.rs +++ b/ata/methods/guest/src/bin/ata.rs @@ -24,6 +24,7 @@ mod ata { token_definition, ata_account, ata_program_id, + token_ids::TOKEN_ID, ); Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) } @@ -44,6 +45,7 @@ mod ata { sender_ata, recipient, ata_program_id, + token_ids::TOKEN_ID, amount, ); Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) @@ -64,6 +66,7 @@ mod ata { holder_ata, token_definition, ata_program_id, + token_ids::TOKEN_ID, amount, ); Ok(SpelOutput::with_chained_calls(post_states, chained_calls)) diff --git a/ata/src/burn.rs b/ata/src/burn.rs index 4940fde..77278ca 100644 --- a/ata/src/burn.rs +++ b/ata/src/burn.rs @@ -2,20 +2,24 @@ use nssa_core::{ account::AccountWithMetadata, program::{AccountPostState, ChainedCall, ProgramId}, }; -use token_core::TokenHolding; pub fn burn_from_associated_token_account( owner: AccountWithMetadata, holder_ata: AccountWithMetadata, token_definition: AccountWithMetadata, ata_program_id: ProgramId, + token_program_id: ProgramId, amount: u128, ) -> (Vec, Vec) { - let token_program_id = holder_ata.account.program_owner; assert!(owner.is_authorized, "Owner authorization is missing"); - let definition_id = TokenHolding::try_from(&holder_ata.account.data) - .expect("Holder ATA must hold a valid token") - .definition_id(); + let _definition = + crate::validation::canonical_token_definition(&token_definition, token_program_id); + let definition_id = + crate::validation::canonical_ata_holding(&holder_ata, token_program_id).definition_id(); + assert_eq!( + definition_id, token_definition.account_id, + "Holder ATA token definition mismatch" + ); let seed = ata_core::verify_ata_and_get_seed(&holder_ata, &owner, definition_id, ata_program_id); diff --git a/ata/src/create.rs b/ata/src/create.rs index 060f0c7..e2a8c9c 100644 --- a/ata/src/create.rs +++ b/ata/src/create.rs @@ -8,12 +8,14 @@ pub fn create_associated_token_account( token_definition: AccountWithMetadata, ata_account: AccountWithMetadata, ata_program_id: ProgramId, + token_program_id: ProgramId, ) -> (Vec, Vec) { // No explicit owner authorization check is needed here: ATA creation is idempotent, so the // call itself may proceed without `owner.is_authorized`. If the owner account is still // default, the returned post-state will still carry `Claim::Authorized` so the runtime can // claim that owner account when needed. - let token_program_id = token_definition.account.program_owner; + let _definition = + crate::validation::canonical_token_definition(&token_definition, token_program_id); let seed = ata_core::verify_ata_and_get_seed( &ata_account, &owner, @@ -23,6 +25,12 @@ pub fn create_associated_token_account( // Idempotent: already initialized → no-op if ata_account.account != Account::default() { + let holding = crate::validation::canonical_ata_holding(&ata_account, token_program_id); + assert_eq!( + holding.definition_id(), + token_definition.account_id, + "Existing ATA token definition mismatch" + ); return ( vec![ AccountPostState::new_claimed_if_default(owner.account.clone(), Claim::Authorized), diff --git a/ata/src/lib.rs b/ata/src/lib.rs index 13740f0..9b10a70 100644 --- a/ata/src/lib.rs +++ b/ata/src/lib.rs @@ -5,6 +5,7 @@ pub use ata_core as core; pub mod burn; pub mod create; pub mod transfer; +mod validation; #[cfg(test)] mod tests; diff --git a/ata/src/tests.rs b/ata/src/tests.rs index 07a2185..c59d65c 100644 --- a/ata/src/tests.rs +++ b/ata/src/tests.rs @@ -1,12 +1,13 @@ use ata_core::{compute_ata_seed, get_associated_token_account_id}; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data}, - program::{ChainedCall, Claim}, + program::{ChainedCall, Claim, ProgramId}, }; use token_core::{TokenDefinition, TokenHolding}; -const ATA_PROGRAM_ID: nssa_core::program::ProgramId = [1u32; 8]; -const TOKEN_PROGRAM_ID: nssa_core::program::ProgramId = [2u32; 8]; +const ATA_PROGRAM_ID: ProgramId = [1u32; 8]; +const TOKEN_PROGRAM_ID: ProgramId = [2u32; 8]; +const FOREIGN_TOKEN_PROGRAM_ID: ProgramId = [3u32; 8]; fn owner_id() -> AccountId { AccountId::new([0x01u8; 32]) @@ -16,6 +17,10 @@ fn definition_id() -> AccountId { AccountId::new([0x02u8; 32]) } +fn other_definition_id() -> AccountId { + AccountId::new([0x03u8; 32]) +} + fn ata_id() -> AccountId { get_associated_token_account_id( &ATA_PROGRAM_ID, @@ -23,6 +28,10 @@ fn ata_id() -> AccountId { ) } +fn recipient_id() -> AccountId { + AccountId::new([0x04u8; 32]) +} + fn owner_account() -> AccountWithMetadata { AccountWithMetadata { account: Account::default(), @@ -48,6 +57,12 @@ fn definition_account() -> AccountWithMetadata { } } +fn foreign_owned_definition_account() -> AccountWithMetadata { + let mut account = definition_account(); + account.account.program_owner = FOREIGN_TOKEN_PROGRAM_ID; + account +} + fn uninitialized_ata_account() -> AccountWithMetadata { AccountWithMetadata { account: Account::default(), @@ -56,13 +71,13 @@ fn uninitialized_ata_account() -> AccountWithMetadata { } } -fn initialized_ata_account() -> AccountWithMetadata { +fn initialized_ata_account_for_definition(definition_id: AccountId) -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0, data: Data::from(&TokenHolding::Fungible { - definition_id: definition_id(), + definition_id, balance: 100, }), nonce: nssa_core::account::Nonce(0), @@ -72,6 +87,45 @@ fn initialized_ata_account() -> AccountWithMetadata { } } +fn initialized_ata_account() -> AccountWithMetadata { + initialized_ata_account_for_definition(definition_id()) +} + +fn foreign_owned_initialized_ata_account() -> AccountWithMetadata { + let mut account = initialized_ata_account(); + account.account.program_owner = FOREIGN_TOKEN_PROGRAM_ID; + account +} + +fn malformed_initialized_ata_account() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0, + data: Data::try_from(vec![0xFF]).expect("test data fits"), + nonce: nssa_core::account::Nonce(0), + }, + is_authorized: false, + account_id: ata_id(), + } +} + +fn recipient_account() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: definition_id(), + balance: 0, + }), + nonce: nssa_core::account::Nonce(0), + }, + is_authorized: false, + account_id: recipient_id(), + } +} + #[test] fn create_emits_chained_call_for_uninitialized_ata() { let (post_states, chained_calls) = crate::create::create_associated_token_account( @@ -79,6 +133,7 @@ fn create_emits_chained_call_for_uninitialized_ata() { definition_account(), uninitialized_ata_account(), ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, ); assert_eq!(post_states.len(), 3); @@ -103,6 +158,7 @@ fn create_is_idempotent_for_initialized_ata() { definition_account(), initialized_ata_account(), ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, ); assert_eq!(post_states.len(), 3); @@ -112,6 +168,106 @@ fn create_is_idempotent_for_initialized_ata() { ); } +#[test] +#[should_panic(expected = "Token definition must be owned by token program")] +fn create_rejects_foreign_owned_token_definition() { + crate::create::create_associated_token_account( + owner_account(), + foreign_owned_definition_account(), + uninitialized_ata_account(), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + ); +} + +#[test] +#[should_panic(expected = "ATA account must hold a valid token")] +fn create_rejects_malformed_initialized_ata() { + crate::create::create_associated_token_account( + owner_account(), + definition_account(), + malformed_initialized_ata_account(), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + ); +} + +#[test] +#[should_panic(expected = "ATA account must be owned by token program")] +fn create_rejects_foreign_owned_initialized_ata() { + crate::create::create_associated_token_account( + owner_account(), + definition_account(), + foreign_owned_initialized_ata_account(), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + ); +} + +#[test] +#[should_panic(expected = "Existing ATA token definition mismatch")] +fn create_rejects_initialized_ata_for_different_definition() { + crate::create::create_associated_token_account( + owner_account(), + definition_account(), + initialized_ata_account_for_definition(other_definition_id()), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + ); +} + +#[test] +#[should_panic(expected = "ATA account must be owned by token program")] +fn transfer_rejects_foreign_owned_sender_ata() { + crate::transfer::transfer_from_associated_token_account( + owner_account(), + foreign_owned_initialized_ata_account(), + recipient_account(), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + 10, + ); +} + +#[test] +#[should_panic(expected = "ATA account must be owned by token program")] +fn burn_rejects_foreign_owned_holder_ata() { + crate::burn::burn_from_associated_token_account( + owner_account(), + foreign_owned_initialized_ata_account(), + definition_account(), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + 10, + ); +} + +#[test] +#[should_panic(expected = "Token definition must be owned by token program")] +fn burn_rejects_foreign_owned_token_definition() { + crate::burn::burn_from_associated_token_account( + owner_account(), + initialized_ata_account(), + foreign_owned_definition_account(), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + 10, + ); +} + +#[test] +#[should_panic(expected = "Holder ATA token definition mismatch")] +fn burn_rejects_holder_definition_mismatch() { + crate::burn::burn_from_associated_token_account( + owner_account(), + initialized_ata_account_for_definition(other_definition_id()), + definition_account(), + ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, + 10, + ); +} + #[test] #[should_panic(expected = "ATA account ID does not match expected derivation")] fn create_panics_on_wrong_ata_address() { @@ -126,6 +282,7 @@ fn create_panics_on_wrong_ata_address() { definition_account(), wrong_ata, ATA_PROGRAM_ID, + TOKEN_PROGRAM_ID, ); } diff --git a/ata/src/transfer.rs b/ata/src/transfer.rs index c3198fa..e9683f0 100644 --- a/ata/src/transfer.rs +++ b/ata/src/transfer.rs @@ -2,20 +2,18 @@ use nssa_core::{ account::AccountWithMetadata, program::{AccountPostState, ChainedCall, ProgramId}, }; -use token_core::TokenHolding; pub fn transfer_from_associated_token_account( owner: AccountWithMetadata, sender_ata: AccountWithMetadata, recipient: AccountWithMetadata, ata_program_id: ProgramId, + token_program_id: ProgramId, amount: u128, ) -> (Vec, Vec) { - let token_program_id = sender_ata.account.program_owner; assert!(owner.is_authorized, "Owner authorization is missing"); - let definition_id = TokenHolding::try_from(&sender_ata.account.data) - .expect("Sender ATA must hold a valid token") - .definition_id(); + let definition_id = + crate::validation::canonical_ata_holding(&sender_ata, token_program_id).definition_id(); let sender_seed = ata_core::verify_ata_and_get_seed(&sender_ata, &owner, definition_id, ata_program_id); diff --git a/ata/src/validation.rs b/ata/src/validation.rs new file mode 100644 index 0000000..983e78a --- /dev/null +++ b/ata/src/validation.rs @@ -0,0 +1,25 @@ +use nssa_core::{account::AccountWithMetadata, program::ProgramId}; +use token_core::{TokenDefinition, TokenHolding}; + +pub fn canonical_token_definition( + token_definition: &AccountWithMetadata, + token_program_id: ProgramId, +) -> TokenDefinition { + assert_eq!( + token_definition.account.program_owner, token_program_id, + "Token definition must be owned by token program" + ); + TokenDefinition::try_from(&token_definition.account.data) + .expect("Token definition account must be valid") +} + +pub fn canonical_ata_holding( + ata_account: &AccountWithMetadata, + token_program_id: ProgramId, +) -> TokenHolding { + assert_eq!( + ata_account.account.program_owner, token_program_id, + "ATA account must be owned by token program" + ); + TokenHolding::try_from(&ata_account.account.data).expect("ATA account must hold a valid token") +} diff --git a/integration_tests/tests/ata.rs b/integration_tests/tests/ata.rs index dd2eef7..c2ebc31 100644 --- a/integration_tests/tests/ata.rs +++ b/integration_tests/tests/ata.rs @@ -41,6 +41,10 @@ impl Ids { token_methods::TOKEN_ID } + fn foreign_token_program() -> nssa_core::program::ProgramId { + [0xfeed_u32; 8] + } + fn ata_program() -> nssa_core::program::ProgramId { ata_methods::ATA_ID } @@ -49,6 +53,10 @@ impl Ids { AccountId::from(&PublicKey::new_from_private_key(&Keys::def_key())) } + fn other_token_definition() -> AccountId { + AccountId::new([0x99; 32]) + } + fn owner() -> AccountId { AccountId::from(&PublicKey::new_from_private_key(&Keys::owner_key())) } @@ -82,6 +90,25 @@ impl Accounts { } } + fn token_definition_foreign_owner() -> Account { + let mut account = Self::token_definition_init(); + account.program_owner = Ids::foreign_token_program(); + account + } + + fn other_token_definition_init() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Silver"), + total_supply: 500_000_u128, + metadata_id: None, + }), + nonce: Nonce(0), + } + } + fn owner_ata_init() -> Account { Account { program_owner: Ids::token_program(), @@ -94,6 +121,21 @@ impl Accounts { } } + fn owner_ata_foreign_owner() -> Account { + let mut account = Self::owner_ata_init(); + account.program_owner = Ids::foreign_token_program(); + account + } + + fn owner_ata_malformed() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::try_from(vec![0xFF]).expect("test data fits"), + nonce: Nonce(0), + } + } + fn recipient_ata_init() -> Account { Account { program_owner: Ids::token_program(), @@ -175,6 +217,42 @@ fn ata_create() { ); } +#[test] +fn ata_create_rejects_foreign_owned_token_definition() { + let mut state = V03State::new_with_genesis_accounts(&[], &[], 0); + deploy_programs(&mut state); + state.force_insert_account( + Ids::token_definition(), + Accounts::token_definition_foreign_owner(), + ); + + let instruction = ata_core::Instruction::Create { + ata_program_id: Ids::ata_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::token_definition()), + Accounts::token_definition_foreign_owner() + ); + assert_eq!( + state.get_account_by_id(Ids::owner_ata()), + Account::default() + ); +} + #[test] fn ata_create_is_idempotent() { let mut state = state_for_ata_tests(); @@ -211,6 +289,36 @@ fn ata_create_is_idempotent() { ); } +#[test] +fn ata_create_rejects_malformed_existing_ata_occupant() { + let mut state = V03State::new_with_genesis_accounts(&[], &[], 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_malformed()); + + let instruction = ata_core::Instruction::Create { + ata_program_id: Ids::ata_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()), + Accounts::owner_ata_malformed() + ); +} + #[test] fn ata_transfer() { let mut state = state_for_ata_tests_with_precreated_recipient_ata(); @@ -260,6 +368,39 @@ fn ata_transfer() { ); } +#[test] +fn ata_transfer_rejects_foreign_owned_sender_ata() { + let mut state = state_for_ata_tests_with_precreated_recipient_ata(); + state.force_insert_account(Ids::owner_ata(), Accounts::owner_ata_foreign_owner()); + + let instruction = ata_core::Instruction::Transfer { + ata_program_id: Ids::ata_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); + assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err()); + + assert_eq!( + state.get_account_by_id(Ids::owner_ata()), + Accounts::owner_ata_foreign_owner() + ); + assert_eq!( + state.get_account_by_id(Ids::recipient_ata()), + Accounts::recipient_ata_init() + ); +} + #[test] fn ata_burn() { let mut state = state_for_ata_tests(); @@ -310,6 +451,82 @@ fn ata_burn() { ); } +#[test] +fn ata_burn_rejects_foreign_owned_token_definition() { + let mut state = state_for_ata_tests(); + state.force_insert_account( + Ids::token_definition(), + Accounts::token_definition_foreign_owner(), + ); + + let instruction = ata_core::Instruction::Burn { + ata_program_id: Ids::ata_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); + 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::token_definition()), + Accounts::token_definition_foreign_owner() + ); +} + +#[test] +fn ata_burn_rejects_definition_mismatch() { + let mut state = state_for_ata_tests(); + state.force_insert_account( + Ids::other_token_definition(), + Accounts::other_token_definition_init(), + ); + + let instruction = ata_core::Instruction::Burn { + ata_program_id: Ids::ata_program(), + amount: 300_000_u128, + }; + + let message = public_transaction::Message::try_new( + Ids::ata_program(), + vec![ + Ids::owner(), + Ids::owner_ata(), + Ids::other_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); + 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::other_token_definition()), + Accounts::other_token_definition_init() + ); +} + #[test] fn ata_create_from_private_owner() { let mut state = V03State::new_with_genesis_accounts(&[], &[], 0); diff --git a/token/ids/Cargo.toml b/token/ids/Cargo.toml new file mode 100644 index 0000000..5ea6188 --- /dev/null +++ b/token/ids/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "token-ids" +version = "0.1.0" +edition = "2021" diff --git a/token/ids/src/lib.rs b/token/ids/src/lib.rs new file mode 100644 index 0000000..44f7186 --- /dev/null +++ b/token/ids/src/lib.rs @@ -0,0 +1,3 @@ +pub const TOKEN_ID: [u32; 8] = [ + 437848389, 692033004, 1799570421, 2462683779, 3620810328, 646372816, 1484372518, 2025744330, +]; diff --git a/token/methods/Cargo.toml b/token/methods/Cargo.toml index a4897fb..b4c629c 100644 --- a/token/methods/Cargo.toml +++ b/token/methods/Cargo.toml @@ -10,5 +10,8 @@ risc0-build = "=3.0.5" risc0-zkvm = { version = "=3.0.5", features = ["std"] } token_core = { path = "../core" } +[dev-dependencies] +token-ids = { path = "../ids" } + [package.metadata.risc0] methods = ["guest"] diff --git a/token/methods/src/lib.rs b/token/methods/src/lib.rs index 1bdb308..dcc0a06 100644 --- a/token/methods/src/lib.rs +++ b/token/methods/src/lib.rs @@ -1 +1,9 @@ include!(concat!(env!("OUT_DIR"), "/methods.rs")); + +#[cfg(test)] +mod tests { + #[test] + fn token_ids_match_generated_method_id() { + assert_eq!(token_ids::TOKEN_ID, super::TOKEN_ID); + } +}