diff --git a/Cargo.lock b/Cargo.lock index 9e6788b4..58f8ef64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2666,6 +2666,14 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "faucet_core" +version = "0.1.0" +dependencies = [ + "nssa_core", + "serde", +] + [[package]] name = "ferroid" version = "2.0.0" @@ -3938,6 +3946,7 @@ dependencies = [ "bytesize", "common", "env_logger", + "faucet_core", "futures", "hex", "indexer_ffi", @@ -6322,6 +6331,7 @@ dependencies = [ "borsh", "clock_core", "env_logger", + "faucet_core", "hex", "hex-literal 1.1.0", "k256", @@ -7057,6 +7067,7 @@ dependencies = [ "ata_program", "authenticated_transfer_core", "clock_core", + "faucet_core", "nssa_core", "risc0-zkvm", "serde", @@ -8421,6 +8432,7 @@ dependencies = [ "bytesize", "chrono", "common", + "faucet_core", "futures", "humantime-serde", "log", diff --git a/Cargo.toml b/Cargo.toml index 974f73ce..724f53e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "programs/associated_token_account/core", "programs/associated_token_account", "programs/authenticated_transfer/core", + "programs/faucet/core", "programs/vault/core", "sequencer/core", "sequencer/service", @@ -68,6 +69,7 @@ amm_program = { path = "programs/amm" } ata_core = { path = "programs/associated_token_account/core" } ata_program = { path = "programs/associated_token_account" } authenticated_transfer_core = { path = "programs/authenticated_transfer/core" } +faucet_core = { path = "programs/faucet/core" } vault_core = { path = "programs/vault/core" } test_program_methods = { path = "test_program_methods" } testnet_initial_state = { path = "testnet_initial_state" } diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 06d149eb..5d45f561 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin index 1598a7fd..6e9350e8 100644 Binary files a/artifacts/program_methods/associated_token_account.bin and b/artifacts/program_methods/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index 3191ac44..107c53d6 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/clock.bin b/artifacts/program_methods/clock.bin index b477d939..f225da54 100644 Binary files a/artifacts/program_methods/clock.bin and b/artifacts/program_methods/clock.bin differ diff --git a/artifacts/program_methods/faucet.bin b/artifacts/program_methods/faucet.bin new file mode 100644 index 00000000..b4043034 Binary files /dev/null and b/artifacts/program_methods/faucet.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index 87943ad0..797a3a31 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index b44b4395..87c7007f 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 580489f7..6b3ec34c 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index f9682d3a..7ddb9d77 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/program_methods/vault.bin b/artifacts/program_methods/vault.bin index 3f50fa9f..08b858f8 100644 Binary files a/artifacts/program_methods/vault.bin and b/artifacts/program_methods/vault.bin differ diff --git a/common/src/transaction.rs b/common/src/transaction.rs index f5ea6488..6175f1a1 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -68,7 +68,8 @@ impl NSSATransaction { /// Validates the transaction against the current state and returns the resulting diff /// without applying it. Rejects transactions that modify clock system accounts and - /// rejects unsafe modifications of the system faucet account. + /// rejects unsafe modifications of the system faucet account. Also rejects direct + /// invocation of the faucet program for user-submitted transactions. /// /// This check is required for all user transactions. Only sequencer transaction may bypass this /// check. @@ -78,6 +79,14 @@ impl NSSATransaction { block_id: BlockId, timestamp: Timestamp, ) -> Result { + if let Self::Public(tx) = self + && tx.message().program_id == nssa::program::Program::faucet().id() + { + return Err(nssa::error::NssaError::InvalidInput( + "Transaction invokes restricted faucet program".into(), + )); + } + let diff = match self { Self::Public(tx) => { ValidatedStateDiff::from_public_transaction(tx, state, block_id, timestamp) @@ -102,36 +111,6 @@ impl NSSATransaction { )); } - let faucet_account_id = nssa::SYSTEM_FAUCET_ACCOUNT_ID; - if let Some(post_faucet) = public_diff.get(&faucet_account_id) { - let pre_faucet = state.get_account_by_id(faucet_account_id); - - let nssa::Account { - program_owner: post_program_owner, - data: post_data, - nonce: post_nonce, - balance: post_balance, - } = post_faucet; - - let nssa::Account { - program_owner: pre_program_owner, - data: pre_data, - nonce: pre_nonce, - balance: pre_balance, - } = pre_faucet; - - let faucet_change_is_allowed = *post_program_owner == pre_program_owner - && *post_data == pre_data - && *post_nonce == pre_nonce - && *post_balance >= pre_balance; - - if !faucet_change_is_allowed { - return Err(nssa::error::NssaError::InvalidInput( - "Transaction modifies system faucet account".into(), - )); - } - } - Ok(diff) } diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 288137ea..536f30bc 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -21,6 +21,7 @@ serde_json.workspace = true token_core.workspace = true ata_core.workspace = true vault_core.workspace = true +faucet_core.workspace = true indexer_service_rpc = { workspace = true, features = ["client"] } sequencer_service_rpc = { workspace = true, features = ["client"] } jsonrpsee = { workspace = true, features = ["ws-client"] } diff --git a/integration_tests/tests/auth_transfer/public.rs b/integration_tests/tests/auth_transfer/public.rs index 80c9d575..54713f67 100644 --- a/integration_tests/tests/auth_transfer/public.rs +++ b/integration_tests/tests/auth_transfer/public.rs @@ -4,7 +4,7 @@ use anyhow::Result; use common::transaction::NSSATransaction; use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention}; use log::info; -use nssa::{SYSTEM_FAUCET_ACCOUNT_ID, program::Program, public_transaction}; +use nssa::{program::Program, public_transaction, system_faucet_account_id}; use sequencer_service_rpc::RpcClient as _; use tokio::test; use wallet::{ @@ -349,6 +349,7 @@ async fn successful_transfer_using_to_label() -> Result<()> { #[test] async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { let ctx = TestContext::new().await?; + let faucet_account_id = system_faucet_account_id(); let recipient = ctx.existing_public_accounts()[0]; let recipient_balance_before = ctx @@ -357,13 +358,13 @@ async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { .await?; let faucet_balance_before = ctx .sequencer_client() - .get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID) + .get_account_balance(faucet_account_id) .await?; let amount = 1_u128; let message = public_transaction::Message::try_new( Program::authenticated_transfer_program().id(), - vec![SYSTEM_FAUCET_ACCOUNT_ID, recipient], + vec![faucet_account_id, recipient], vec![], authenticated_transfer_core::Instruction::Transfer { amount }, )?; @@ -385,7 +386,7 @@ async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { .await?; let faucet_balance_after = ctx .sequencer_client() - .get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID) + .get_account_balance(faucet_account_id) .await?; let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?; @@ -399,18 +400,19 @@ async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { #[test] async fn can_transfer_funds_to_system_faucet_account() -> Result<()> { let mut ctx = TestContext::new().await?; + let faucet_account_id = system_faucet_account_id(); let sender = ctx.existing_public_accounts()[0]; let sender_balance_before = ctx.sequencer_client().get_account_balance(sender).await?; let faucet_balance_before = ctx .sequencer_client() - .get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID) + .get_account_balance(faucet_account_id) .await?; let amount = 100_u128; let command = Command::AuthTransfer(AuthTransferSubcommand::Send { from: public_mention(sender), - to: Some(public_mention(SYSTEM_FAUCET_ACCOUNT_ID)), + to: Some(public_mention(faucet_account_id)), to_npk: None, to_vpk: None, to_identifier: Some(0), @@ -424,7 +426,7 @@ async fn can_transfer_funds_to_system_faucet_account() -> Result<()> { let sender_balance_after = ctx.sequencer_client().get_account_balance(sender).await?; let faucet_balance_after = ctx .sequencer_client() - .get_account_balance(SYSTEM_FAUCET_ACCOUNT_ID) + .get_account_balance(faucet_account_id) .await?; assert_eq!(sender_balance_after, sender_balance_before - amount); @@ -432,3 +434,61 @@ async fn can_transfer_funds_to_system_faucet_account() -> Result<()> { Ok(()) } + +#[test] +async fn cannot_execute_faucet_program() -> Result<()> { + let ctx = TestContext::new().await?; + let faucet_account_id = system_faucet_account_id(); + + let recipient = ctx.existing_public_accounts()[0]; + let vault_program_id = Program::vault().id(); + let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient); + + let recipient_balance_before = ctx + .sequencer_client() + .get_account_balance(recipient) + .await?; + let faucet_balance_before = ctx + .sequencer_client() + .get_account_balance(faucet_account_id) + .await?; + + let amount = 1_u128; + let message = public_transaction::Message::try_new( + Program::faucet().id(), + vec![faucet_account_id, recipient_vault_id], + vec![], + faucet_core::Instruction::Transfer { + vault_program_id, + recipient_id: recipient, + amount, + }, + )?; + let tx = nssa::PublicTransaction::new( + message, + nssa::public_transaction::WitnessSet::from_raw_parts(vec![]), + ); + let tx_hash = ctx + .sequencer_client() + .send_transaction(NSSATransaction::Public(tx)) + .await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let recipient_balance_after = ctx + .sequencer_client() + .get_account_balance(recipient) + .await?; + let faucet_balance_after = ctx + .sequencer_client() + .get_account_balance(faucet_account_id) + .await?; + let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?; + + assert_eq!(recipient_balance_after, recipient_balance_before); + assert_eq!(faucet_balance_after, faucet_balance_before); + assert!(tx_on_chain.is_none()); + + Ok(()) +} diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index d2141999..20889e11 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] nssa_core = { workspace = true, features = ["host"] } clock_core.workspace = true +faucet_core.workspace = true anyhow.workspace = true thiserror.workspace = true diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index f8b9cd0f..5998e803 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -4,7 +4,7 @@ )] pub use nssa_core::{ - GENESIS_BLOCK_ID, SYSTEM_FAUCET_ACCOUNT_ID, SharedSecretKey, + GENESIS_BLOCK_ID, SharedSecretKey, account::{Account, AccountId, Data}, encryption::EphemeralPublicKey, program::ProgramId, @@ -18,7 +18,7 @@ pub use public_transaction::PublicTransaction; pub use signature::{PrivateKey, PublicKey, Signature}; pub use state::{ CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, - CLOCK_PROGRAM_ACCOUNT_IDS, V03State, + CLOCK_PROGRAM_ACCOUNT_IDS, V03State, system_faucet_account_id, }; pub use validated_state_diff::ValidatedStateDiff; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 07a85469..1aff3bc9 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -10,8 +10,8 @@ use crate::{ error::NssaError, program_methods::{ AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID, - AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, PINATA_ELF, - PINATA_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID, + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, FAUCET_ELF, + FAUCET_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID, }, }; @@ -156,6 +156,14 @@ impl Program { elf: VAULT_ELF.to_vec(), } } + + #[must_use] + pub fn faucet() -> Self { + Self { + id: FAUCET_ID, + elf: FAUCET_ELF.to_vec(), + } + } } // TODO: Testnet only. Refactor to prevent compilation on mainnet. @@ -186,8 +194,9 @@ mod tests { program::Program, program_methods::{ AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID, - AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, PINATA_ELF, - PINATA_ID, PINATA_TOKEN_ELF, PINATA_TOKEN_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID, + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, FAUCET_ELF, + FAUCET_ID, PINATA_ELF, PINATA_ID, PINATA_TOKEN_ELF, PINATA_TOKEN_ID, TOKEN_ELF, + TOKEN_ID, VAULT_ELF, VAULT_ID, }, }; @@ -501,6 +510,7 @@ mod tests { let auth_transfer_program = Program::authenticated_transfer_program(); let token_program = Program::token(); let vault_program = Program::vault(); + let faucet_program = Program::faucet(); let pinata_program = Program::pinata(); assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID); @@ -509,6 +519,8 @@ mod tests { assert_eq!(token_program.elf, TOKEN_ELF); assert_eq!(vault_program.id, VAULT_ID); assert_eq!(vault_program.elf, VAULT_ELF); + assert_eq!(faucet_program.id, FAUCET_ID); + assert_eq!(faucet_program.elf, FAUCET_ELF); assert_eq!(pinata_program.id, PINATA_ID); assert_eq!(pinata_program.elf, PINATA_ELF); } @@ -520,6 +532,7 @@ mod tests { (AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID), (ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID), (CLOCK_ELF, CLOCK_ID), + (FAUCET_ELF, FAUCET_ID), (PINATA_ELF, PINATA_ID), (PINATA_TOKEN_ELF, PINATA_TOKEN_ID), (TOKEN_ELF, TOKEN_ID), diff --git a/nssa/src/state.rs b/nssa/src/state.rs index a2c7cbea..e9f2058f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -8,7 +8,7 @@ pub use clock_core::{ }; use nssa_core::{ BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier, - SYSTEM_FAUCET_ACCOUNT_ID, Timestamp, + Timestamp, account::{Account, AccountId, Nonce}, program::ProgramId, }; @@ -124,9 +124,10 @@ pub struct V03State { impl Default for V03State { fn default() -> Self { + let faucet_account_id = system_faucet_account_id(); let faucet_account = system_faucet_account(); let mut public_state = HashMap::new(); - public_state.insert(SYSTEM_FAUCET_ACCOUNT_ID, faucet_account); + public_state.insert(faucet_account_id, faucet_account); Self { public_state, @@ -148,6 +149,7 @@ impl V03State { initial_private_accounts: Vec<(Commitment, Nullifier)>, genesis_timestamp: nssa_core::Timestamp, ) -> Self { + let faucet_account_id = system_faucet_account_id(); let authenticated_transfer_program = Program::authenticated_transfer_program(); let mut public_state: HashMap<_, _> = initial_data .iter() @@ -162,7 +164,7 @@ impl V03State { }) .collect(); let faucet_account = system_faucet_account(); - public_state.insert(SYSTEM_FAUCET_ACCOUNT_ID, faucet_account); + public_state.insert(faucet_account_id, faucet_account); let mut commitment_set = CommitmentSet::with_capacity(32); commitment_set.extend(&[DUMMY_COMMITMENT]); @@ -187,6 +189,7 @@ impl V03State { this.insert_program(Program::amm()); this.insert_program(Program::ata()); this.insert_program(Program::vault()); + this.insert_program(Program::faucet()); this } @@ -381,6 +384,11 @@ fn system_faucet_account() -> Account { } } +#[must_use] +pub fn system_faucet_account_id() -> AccountId { + faucet_core::compute_faucet_account_id(Program::faucet().id()) +} + #[cfg(test)] pub mod tests { #![expect( @@ -404,7 +412,7 @@ pub mod tests { }; use crate::{ - PublicKey, PublicTransaction, SYSTEM_FAUCET_ACCOUNT_ID, V03State, + PublicKey, PublicTransaction, V03State, error::{InvalidProgramBehaviorError, NssaError}, execute_and_prove, privacy_preserving_transaction::{ @@ -420,6 +428,7 @@ pub mod tests { CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, CLOCK_PROGRAM_ACCOUNT_IDS, MAX_NUMBER_CHAINED_CALLS, system_faucet_account, }, + system_faucet_account_id, }; impl V03State { @@ -612,7 +621,7 @@ pub mod tests { ..Account::default() }, ); - this.insert(SYSTEM_FAUCET_ACCOUNT_ID, system_faucet_account()); + this.insert(system_faucet_account_id(), system_faucet_account()); for account_id in CLOCK_PROGRAM_ACCOUNT_IDS { this.insert( account_id, @@ -636,6 +645,7 @@ pub mod tests { this.insert(Program::amm().id(), Program::amm()); this.insert(Program::ata().id(), Program::ata()); this.insert(Program::vault().id(), Program::vault()); + this.insert(Program::faucet().id(), Program::faucet()); this }; diff --git a/program_methods/guest/Cargo.toml b/program_methods/guest/Cargo.toml index 98938d1f..136fb0b8 100644 --- a/program_methods/guest/Cargo.toml +++ b/program_methods/guest/Cargo.toml @@ -17,6 +17,7 @@ amm_core.workspace = true amm_program.workspace = true ata_core.workspace = true ata_program.workspace = true +faucet_core.workspace = true vault_core.workspace = true risc0-zkvm.workspace = true serde = { workspace = true, default-features = false } diff --git a/program_methods/guest/src/bin/authenticated_transfer.rs b/program_methods/guest/src/bin/authenticated_transfer.rs index bdb91ac5..0c8040d9 100644 --- a/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/program_methods/guest/src/bin/authenticated_transfer.rs @@ -1,6 +1,5 @@ use authenticated_transfer_core::Instruction; use nssa_core::{ - SYSTEM_FAUCET_ACCOUNT_ID, account::{Account, AccountWithMetadata}, program::{ AccountPostState, Claim, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs, @@ -26,13 +25,8 @@ fn transfer( recipient: AccountWithMetadata, balance_to_move: u128, ) -> Vec { - // Continue only if the sender has authorized this operation - // or it's the system faucet account which is allowed without authorization as it may be used - // only by sequencer. - assert!( - sender.is_authorized || sender.account_id == SYSTEM_FAUCET_ACCOUNT_ID, - "Sender must be authorized" - ); + // Continue only if the sender has authorized this operation. + assert!(sender.is_authorized, "Sender must be authorized"); // Create accounts post states, with updated balances let sender_post = { diff --git a/program_methods/guest/src/bin/faucet.rs b/program_methods/guest/src/bin/faucet.rs new file mode 100644 index 00000000..e56330cd --- /dev/null +++ b/program_methods/guest/src/bin/faucet.rs @@ -0,0 +1,71 @@ +use faucet_core::Instruction; +use nssa_core::program::{ + AccountPostState, ChainedCall, ProgramInput, ProgramOutput, read_nssa_inputs, +}; + +fn unchanged_post_states( + pre_states: &[nssa_core::account::AccountWithMetadata], +) -> Vec { + pre_states + .iter() + .map(|pre_state| AccountPostState::new(pre_state.account.clone())) + .collect() +} + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_nssa_inputs::(); + + let pre_states_clone = pre_states.clone(); + let post_states = unchanged_post_states(&pre_states_clone); + + let chained_calls = match instruction { + Instruction::Transfer { + vault_program_id, + recipient_id, + amount, + } => { + let [faucet, recipient_vault] = pre_states + .try_into() + .expect("Transfer requires exactly 2 accounts"); + + assert_eq!( + faucet.account_id, + faucet_core::compute_faucet_account_id(self_program_id), + "First account must be faucet PDA" + ); + + let mut faucet_for_vault = faucet; + faucet_for_vault.is_authorized = true; + + vec![ + ChainedCall::new( + vault_program_id, + vec![faucet_for_vault, recipient_vault], + &vault_core::Instruction::Transfer { + recipient_id, + amount, + }, + ) + .with_pda_seeds(vec![faucet_core::compute_faucet_seed()]), + ] + } + }; + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + pre_states_clone, + post_states, + ) + .with_chained_calls(chained_calls) + .write(); +} diff --git a/programs/faucet/core/Cargo.toml b/programs/faucet/core/Cargo.toml new file mode 100644 index 00000000..aa8826ea --- /dev/null +++ b/programs/faucet/core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "faucet_core" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +nssa_core.workspace = true +serde = { workspace = true, default-features = false } diff --git a/programs/faucet/core/src/lib.rs b/programs/faucet/core/src/lib.rs new file mode 100644 index 00000000..da9861e6 --- /dev/null +++ b/programs/faucet/core/src/lib.rs @@ -0,0 +1,29 @@ +pub use nssa_core::program::PdaSeed; +use nssa_core::{account::AccountId, program::ProgramId}; +use serde::{Deserialize, Serialize}; + +const FAUCET_SEED_DOMAIN_SEPARATOR: [u8; 32] = *b"/LEZ/v0.3/FaucetSeed/0000000000/"; + +#[derive(Serialize, Deserialize)] +pub enum Instruction { + /// Transfers native tokens from system faucet to recipient's vault. + /// + /// Required accounts (2): + /// - Faucet PDA account + /// - Recipient vault PDA account + Transfer { + vault_program_id: ProgramId, + recipient_id: AccountId, + amount: u128, + }, +} + +#[must_use] +pub const fn compute_faucet_seed() -> PdaSeed { + PdaSeed::new(FAUCET_SEED_DOMAIN_SEPARATOR) +} + +#[must_use] +pub fn compute_faucet_account_id(faucet_program_id: ProgramId) -> AccountId { + AccountId::for_public_pda(&faucet_program_id, &compute_faucet_seed()) +} diff --git a/sequencer/core/Cargo.toml b/sequencer/core/Cargo.toml index 9729d209..cab54e56 100644 --- a/sequencer/core/Cargo.toml +++ b/sequencer/core/Cargo.toml @@ -15,6 +15,7 @@ storage.workspace = true mempool.workspace = true logos-blockchain-zone-sdk.workspace = true testnet_initial_state.workspace = true +faucet_core.workspace = true vault_core.workspace = true thiserror.workspace = true diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index c103adfc..53894935 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -383,14 +383,16 @@ fn build_supply_account_genesis_transaction( account_id: &AccountId, balance: u128, ) -> PublicTransaction { + let faucet_program_id = Program::faucet().id(); let vault_program_id = Program::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, *account_id); let message = Message::try_new( - vault_program_id, - vec![nssa::SYSTEM_FAUCET_ACCOUNT_ID, recipient_vault_id], + faucet_program_id, + vec![nssa::system_faucet_account_id(), recipient_vault_id], vec![], - vault_core::Instruction::Transfer { + faucet_core::Instruction::Transfer { + vault_program_id, recipient_id: *account_id, amount: balance, },