diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 5604a04..a5f975b 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -22,6 +22,7 @@ workspace = true [dependencies.sequencer_core] path = "../sequencer_core" +features = ["testnet"] [dependencies.sequencer_runner] path = "../sequencer_runner" diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index bc2c73c..20cb4ef 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -279,21 +279,10 @@ pub async fn test_success_two_transactions() { info!("Second TX Success!"); } -pub async fn test_get_account_wallet_command() { - let command = Command::GetAccount { - addr: ACC_SENDER.to_string(), - }; - +pub async fn test_get_account() { let wallet_config = fetch_config().unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - wallet::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct account"); let account = seq_client .get_account(ACC_SENDER.to_string()) .await @@ -594,6 +583,50 @@ pub async fn test_success_shielded_transfer_to_another_foreign_account() { info!("Success!"); } +pub async fn test_pinata() { + let pinata_addr = "cafe".repeat(16); + let pinata_prize = 150; + let solution = 989106; + let command = Command::ClaimPinata { + pinata_addr: pinata_addr.clone(), + winner_addr: ACC_SENDER.to_string(), + solution, + }; + + let wallet_config = fetch_config().unwrap(); + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + let pinata_balance_pre = seq_client + .get_account_balance(pinata_addr.clone()) + .await + .unwrap() + .balance; + + wallet::execute_subcommand(command).await.unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let pinata_balance_post = seq_client + .get_account_balance(pinata_addr.clone()) + .await + .unwrap() + .balance; + + let winner_balance_post = seq_client + .get_account_balance(ACC_SENDER.to_string()) + .await + .unwrap() + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + assert_eq!(winner_balance_post, 10000 + pinata_prize); + + info!("Success!"); +} + macro_rules! test_cleanup_wrap { ($home_dir:ident, $test_func:ident) => {{ let res = pre_test($home_dir.clone()).await.unwrap(); @@ -627,7 +660,7 @@ pub async fn main_tests_runner() -> Result<()> { test_cleanup_wrap!(home_dir, test_failure); } "test_get_account_wallet_command" => { - test_cleanup_wrap!(home_dir, test_get_account_wallet_command); + test_cleanup_wrap!(home_dir, test_get_account); } "test_success_two_transactions" => { test_cleanup_wrap!(home_dir, test_success_two_transactions); @@ -662,12 +695,14 @@ pub async fn main_tests_runner() -> Result<()> { test_success_shielded_transfer_to_another_foreign_account ); } + "test_pinata" => { + test_cleanup_wrap!(home_dir, test_pinata); + } "all" => { test_cleanup_wrap!(home_dir, test_success_move_to_another_account); test_cleanup_wrap!(home_dir, test_success); test_cleanup_wrap!(home_dir, test_failure); test_cleanup_wrap!(home_dir, test_success_two_transactions); - test_cleanup_wrap!(home_dir, test_get_account_wallet_command); test_cleanup_wrap!( home_dir, test_success_private_transfer_to_another_owned_account @@ -688,6 +723,7 @@ pub async fn main_tests_runner() -> Result<()> { home_dir, test_success_shielded_transfer_to_another_foreign_account ); + test_cleanup_wrap!(home_dir, test_pinata); } _ => { anyhow::bail!("Unknown test name"); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 0cd8e25..4d21e46 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -19,3 +19,6 @@ k256 = "0.13.3" [dev-dependencies] test-program-methods = { path = "test_program_methods" } hex-literal = "1.0.0" + +[features] +default = [] diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 9e7f399..89b84fd 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -34,3 +34,4 @@ fn main() { write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]); } + diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs new file mode 100644 index 0000000..fbea167 --- /dev/null +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -0,0 +1,70 @@ +use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use risc0_zkvm::sha::{Impl, Sha256}; + +const PRIZE: u128 = 150; + +type Instruction = u128; + +struct Challenge { + difficulty: u8, + seed: [u8; 32], +} + +impl Challenge { + fn new(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 33); + let difficulty = bytes[0]; + assert!(difficulty <= 32); + + let mut seed = [0; 32]; + seed.copy_from_slice(&bytes[1..]); + Self { difficulty, seed } + } + + // Checks if the leftmost `self.difficulty` number of bytes of SHA256(self.data || solution) are + // zero. + fn validate_solution(&self, solution: Instruction) -> bool { + let mut bytes = [0; 32 + 16]; + bytes[..32].copy_from_slice(&self.seed); + bytes[32..].copy_from_slice(&solution.to_le_bytes()); + let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); + let difficulty = self.difficulty as usize; + digest[..difficulty].iter().all(|&b| b == 0) + } + + fn next_data(self) -> [u8; 33] { + let mut result = [0; 33]; + result[0] = self.difficulty; + result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); + result + } +} + +/// A pinata program +fn main() { + // Read input accounts. + // It is expected to receive only two accounts: [pinata_account, winner_account] + let ProgramInput { + pre_states, + instruction: solution, + } = read_nssa_inputs::(); + + let [pinata, winner] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let data = Challenge::new(&pinata.account.data); + + if !data.validate_solution(solution) { + return; + } + + let mut pinata_post = pinata.account.clone(); + let mut winner_post = winner.account.clone(); + pinata_post.balance -= PRIZE; + pinata_post.data = data.next_data().to_vec(); + winner_post.balance += PRIZE; + + write_nssa_outputs(vec![pinata, winner], vec![pinata_post, winner_post]); +} diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 66358e9..9577411 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -2,7 +2,9 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; -use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; +use program_methods::{ + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, +}; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; @@ -75,6 +77,16 @@ impl Program { } } +// TODO: Testnet only. Refactor to prevent compilation on mainnet. +impl Program { + pub fn pinata() -> Self { + Self { + id: PINATA_ID, + elf: PINATA_ELF, + } + } +} + #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountWithMetadata}; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3dfca41..a4f9220 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -211,6 +211,24 @@ impl V01State { } } +// TODO: Testnet only. Refactor to prevent compilation on mainnet. +impl V01State { + pub fn add_pinata_program(&mut self, address: Address) { + self.insert_program(Program::pinata()); + + self.public_state.insert( + address, + Account { + program_owner: Program::pinata().id(), + balance: 1500, + // Difficulty: 3 + data: vec![3; 33], + nonce: 0, + }, + ); + } +} + #[cfg(test)] pub mod tests { diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 52074cc..72a8cc4 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -24,3 +24,7 @@ path = "../common" [dependencies.nssa] path = "../nssa" + +[features] +default = [] +testnet = [] diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index efbf7c9..2d09bd4 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -28,8 +28,16 @@ impl SequecerChainStore { .map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance)) .collect(); + #[cfg(not(feature = "testnet"))] let state = nssa::V01State::new_with_genesis_accounts(&init_accs, initial_commitments); + #[cfg(feature = "testnet")] + let state = { + let mut this = nssa::V01State::new_with_genesis_accounts(&init_accs, initial_commitments); + this.add_pinata_program("cafe".repeat(16).parse().unwrap()); + this + }; + let mut data = [0; 32]; let mut prev_block_hash = [0; 32]; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 80144ce..400441c 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -2,8 +2,8 @@ use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use common::{ - sequencer_client::SequencerClient, - transaction::{EncodedTransaction, NSSATransaction}, + sequencer_client::{json::SendTxResponse, SequencerClient}, + transaction::{EncodedTransaction, NSSATransaction}, ExecutionFailureKind, }; use anyhow::Result; @@ -85,6 +85,33 @@ impl WalletCore { .generate_new_privacy_preserving_transaction_key_chain() } + // pub fn search_for_initial_account(&self, acc_addr: Address) -> Option { + // for initial_acc in &self.storage.wallet_config.initial_accounts { + // if initial_acc.address() == acc_addr { + // return Some(initial_acc.account().clone()); + // } + // } + // None + // } + + pub async fn claim_pinata( + &self, + pinata_addr: Address, + winner_addr: Address, + solution: u128, + ) -> Result { + let addresses = vec![pinata_addr, winner_addr]; + let program_id = nssa::program::Program::pinata().id(); + let message = + nssa::public_transaction::Message::try_new(program_id, addresses, vec![], solution) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_public(tx).await?) + } + ///Get account balance pub async fn get_account_balance(&self, acc: Address) -> Result { Ok(self @@ -252,6 +279,19 @@ pub enum Command { #[arg(short, long)] addr: String, }, + // TODO: Testnet only. Refactor to prevent compilation on mainnet. + // Claim piƱata prize + ClaimPinata { + ///pinata_addr - valid 32 byte hex string + #[arg(long)] + pinata_addr: String, + ///winner_addr - valid 32 byte hex string + #[arg(long)] + winner_addr: String, + ///solution - solution to pinata challenge + #[arg(long)] + solution: u128, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -596,6 +636,20 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { let account: HumanReadableAccount = wallet_core.get_account(addr).await?.into(); println!("{}", serde_json::to_string(&account).unwrap()); } + Command::ClaimPinata { + pinata_addr, + winner_addr, + solution, + } => { + let res = wallet_core + .claim_pinata( + pinata_addr.parse().unwrap(), + winner_addr.parse().unwrap(), + solution, + ) + .await?; + info!("Results of tx send is {res:#?}"); + } } Ok(())