From dd267ac2b29131a41683f70c14e12d7424641060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necip=20Baha=20Sa=C4=9F=C4=B1ro=C4=9Flu?= Date: Sat, 15 Mar 2025 17:28:05 +0300 Subject: [PATCH 1/3] add refund ticket functionality draft to ticket-app --- ticket-app/host/src/main.rs | 183 +++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 3 deletions(-) diff --git a/ticket-app/host/src/main.rs b/ticket-app/host/src/main.rs index 84d05b0..4ecc276 100644 --- a/ticket-app/host/src/main.rs +++ b/ticket-app/host/src/main.rs @@ -3,6 +3,8 @@ use clap::{Parser, Subcommand}; use client_sdk::helpers::risc0::Risc0Prover; use contract::SimpleToken; use contract::SimpleTokenAction; +// use hyle_hyllar::{Hyllar, HyllarAction}; +// use hyle_contracts::HYLLAR_ELF; use contract_identity::IdentityAction; use contract_ticket_app::TicketAppAction; use contract_ticket_app::TicketAppState; @@ -46,6 +48,7 @@ struct Cli { enum Commands { Register { token: String, price: u128 }, BuyTicket {}, + RefundTicket {}, HasTicket {}, } @@ -65,6 +68,7 @@ async fn main() { let ticket_prover = Risc0Prover::new(methods_ticket_app::GUEST_ELF); let identity_prover = Risc0Prover::new(methods_identity::GUEST_ELF); let token_prover = Risc0Prover::new(methods::GUEST_ELF); + // let token_prover = Risc0Prover::new(HYLLAR_ELF); match cli.command { Commands::Register { token, price } => { @@ -100,7 +104,7 @@ async fn main() { println!("Identity {:?}", cli.user.clone()); println!("Nonce {:?}", cli.nonce.clone()); - let identity = Identity(cli.user.clone()); + let identity: Identity = Identity(cli.user.clone()); let identity_cf: IdentityAction = IdentityAction::VerifyIdentity { account: identity.0.clone(), @@ -116,7 +120,7 @@ async fn main() { borsh::to_vec(&identity_cf).expect("Failed to encode Identity action"), ), }, - // Init pair 0 amount + // Init pair 0 amount sdk::Blob { contract_name: initial_state.ticket_price.0.clone(), data: sdk::BlobData( @@ -174,7 +178,18 @@ async fn main() { println!("Running and proving Transfer blob"); - // Build the transfer a input + // let state_commitment = client + // .get_contract(&initial_state.ticket_price.0.clone().into()) + // .await + // .unwrap() + // .state; + + // // Deserialize the state manually + // let initial_state_a: Hyllar = borsh::from_slice(&state_commitment.0) + // .map_err(|_| "Could not decode hyllar state".to_string()) + // .unwrap(); + + // // Build the transfer a input let initial_state_a: SimpleToken = client .get_contract(&initial_state.ticket_price.0.clone().into()) .await @@ -237,6 +252,168 @@ async fn main() { let proof_tx_hash = client.send_tx_proof(&proof_tx).await.unwrap(); println!("✅ Proof tx sent. Tx hash: {}", proof_tx_hash); } + Commands::RefundTicket {} => { + // Build initial state of contract + let initial_state: TicketAppState = client + .get_contract(&contract_name.clone().into()) + .await + .unwrap() + .state + .into(); + + println!("Initial State {:?}", &initial_state); + println!("Initial State {:?}", initial_state.commit()); + println!("Identity {:?}", cli.user.clone()); + println!("Nonce {:?}", cli.nonce.clone()); + + let identity: Identity = Identity(cli.user.clone()); + + let identity_cf: IdentityAction = IdentityAction::VerifyIdentity { + account: identity.0.clone(), + nonce: cli.nonce.parse().unwrap(), + }; + + let identity_contract_name = cli.user.rsplit_once(".").unwrap().1.to_string(); + + let blobs = vec![ + sdk::Blob { + contract_name: identity_contract_name.clone().into(), + data: sdk::BlobData( + borsh::to_vec(&identity_cf).expect("Failed to encode Identity action"), + ), + }, + sdk::Blob::from(sdk::StructuredBlob { + contract_name: contract_name.clone().into(), + data: sdk::StructuredBlobData { + caller: None, + callees: Some(vec![sdk::BlobIndex(2)]), + parameters: TicketAppAction::RefundTicket {}, + }, + }), + sdk::Blob::from(sdk::StructuredBlob { + contract_name: initial_state.ticket_price.0.clone(), + data: sdk::StructuredBlobData { + caller: Some(sdk::BlobIndex(1)), + callees: None, + parameters: SimpleTokenAction::Transfer { + recipient: identity.0.clone(), + amount: initial_state.ticket_price.1, + }, + }, + }), + ]; + + println!("Blobs {:?}", blobs.clone()); + + let blob_tx = BlobTransaction::new(identity.clone(), blobs.clone()); + + // Send the blob transaction + let blob_tx_hash = client.send_tx_blob(&blob_tx).await.unwrap(); + println!("✅ Blob tx sent. Tx hash: {}", blob_tx_hash); + + // prove tx + + println!("Running and proving TicketApp blob"); + + // Build the contract input + let inputs = ContractInput { + state: initial_state.as_bytes().unwrap(), + identity: identity.clone(), + tx_hash: blob_tx_hash.clone().into(), + private_input: vec![], + tx_ctx: None, + blobs: blobs.clone(), + index: sdk::BlobIndex(1), + }; + + // Generate the zk proof + let proof = ticket_prover.prove(inputs).await.unwrap(); + + let proof_tx = ProofTransaction { + proof, + contract_name: contract_name.clone().into(), + }; + + // Send the proof transaction + let proof_tx_hash = client.send_tx_proof(&proof_tx).await.unwrap(); + println!("✅ Proof tx sent. Tx hash: {}", proof_tx_hash); + + println!("Running and proving Transfer blob"); + + // let state_commitment = client + // .get_contract(&initial_state.ticket_price.0.clone().into()) + // .await + // .unwrap() + // .state; + + // // Deserialize the state manually + // let initial_state_a: Hyllar = borsh::from_slice(&state_commitment.0) + // .map_err(|_| "Could not decode hyllar state".to_string()) + // .unwrap(); + + // Build the transfer a input + let initial_state_a: SimpleToken = client + .get_contract(&initial_state.ticket_price.0.clone().into()) + .await + .unwrap() + .state + .into(); + + let inputs = ContractInput { + state: initial_state_a.as_bytes().unwrap(), + identity: identity.clone(), + tx_hash: blob_tx_hash.clone().into(), + private_input: vec![], + tx_ctx: None, + blobs: blobs.clone(), + index: sdk::BlobIndex(2), + }; + + // Generate the zk proof + let proof = token_prover.prove(inputs).await.unwrap(); + + let proof_tx = ProofTransaction { + proof, + contract_name: initial_state.ticket_price.0.clone(), + }; + + // Send the proof transaction + let proof_tx_hash = client.send_tx_proof(&proof_tx).await.unwrap(); + println!("✅ Proof tx sent. Tx hash: {}", proof_tx_hash); + + println!("Running and proving Identity blob"); + + // Fetch the initial state from the node + let initial_state_id: contract_identity::IdentityContractState = client + .get_contract(&identity_contract_name.clone().into()) + .await + .unwrap() + .state + .into(); + + // Build the contract input + let inputs = ContractInput { + state: initial_state_id.as_bytes().unwrap(), + identity: identity.clone(), + tx_hash: blob_tx_hash.clone().into(), + private_input: vec![], + tx_ctx: None, + blobs: blobs.clone(), + index: sdk::BlobIndex(0), + }; + + // Generate the zk proof + let proof = identity_prover.prove(inputs).await.unwrap(); + + let proof_tx = ProofTransaction { + proof, + contract_name: identity_contract_name.clone().into(), + }; + + // Send the proof transaction + let proof_tx_hash = client.send_tx_proof(&proof_tx).await.unwrap(); + println!("✅ Proof tx sent. Tx hash: {}", proof_tx_hash); + }, Commands::HasTicket {} => { let initial_state: TicketAppState = client .get_contract(&contract_name.clone().into()) From 542ac1eb64e25bbcd9eaafc7b63fd1d963cbba0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necip=20Baha=20Sa=C4=9F=C4=B1ro=C4=9Flu?= Date: Sat, 15 Mar 2025 17:29:38 +0300 Subject: [PATCH 2/3] Update lib.rs --- ticket-app/contract/src/lib.rs | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/ticket-app/contract/src/lib.rs b/ticket-app/contract/src/lib.rs index 2b643b1..227cff9 100644 --- a/ticket-app/contract/src/lib.rs +++ b/ticket-app/contract/src/lib.rs @@ -23,6 +23,9 @@ impl sdk::HyleContract for TicketAppState { TicketAppAction::BuyTicket {} => { self.buy_ticket(&ctx, transfer_action, transfer_action_contract_name)? } + TicketAppAction::RefundTicket {} => { + self.refund_ticket(&ctx, transfer_action, transfer_action_contract_name)? + } TicketAppAction::HasTicket {} => self.has_ticket(&ctx)?, }; @@ -38,6 +41,7 @@ impl sdk::HyleContract for TicketAppState { #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] pub enum TicketAppAction { BuyTicket {}, + RefundTicket {}, HasTicket {}, } @@ -100,6 +104,50 @@ impl TicketAppState { Ok(program_outputs) } + pub fn refund_ticket( + &mut self, + ctx: &ExecutionContext, + erc20_action: SimpleTokenAction, + erc20_name: ContractName, + ) -> Result { + // Check that a blob exists matching the given action, pop it from the callee blobs. + + if !self.tickets.contains(&ctx.caller) { + return Err(format!("Ticket not present for {:?}", &ctx.caller)); + } + + match erc20_action { + SimpleTokenAction::Transfer { recipient, amount } => { + if recipient != ctx.caller.0 { + return Err(format!( + "Transfer recipient should be {} but was {}", + ctx.caller, &recipient + )); + } + + if self.ticket_price.0 != erc20_name { + return Err(format!( + "Transfer token should be {} but was {}", + self.ticket_price.0, &erc20_name + )); + } + + if amount < self.ticket_price.1 { + return Err(format!( + "Transfer amount should be at least {} but was {}", + self.ticket_price.0, &recipient + )); + } + } + } + + let program_outputs = format!("Ticket refunded for {:?}", ctx.caller); + + self.tickets.remove(self.tickets.iter().position(|id| id == &ctx.caller).unwrap()); + + Ok(program_outputs) + } + pub fn has_ticket(&self, ctx: &ExecutionContext) -> Result { // Check that a blob exists matching the given action, pop it from the callee blobs. From 36e370226dc785192988a1dbcad7bf68c7225c52 Mon Sep 17 00:00:00 2001 From: necipsagiro Date: Thu, 22 May 2025 00:25:08 +0300 Subject: [PATCH 3/3] change refund_ticket method and refactor --- simple-token/contract/src/lib.rs | 21 ++++++++++-- ticket-app/contract/src/lib.rs | 40 ++++++++++++++++++---- ticket-app/host/src/main.rs | 57 ++++++-------------------------- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/simple-token/contract/src/lib.rs b/simple-token/contract/src/lib.rs index fc179d5..cdafd93 100644 --- a/simple-token/contract/src/lib.rs +++ b/simple-token/contract/src/lib.rs @@ -18,8 +18,7 @@ impl sdk::HyleContract for SimpleToken { /// Entry point of the contract's logic fn execute(&mut self, contract_input: &sdk::ContractInput) -> RunResult { // Parse contract inputs - let (action, ctx) = - sdk::utils::parse_raw_contract_input::(contract_input)?; + let (action, ctx) = sdk::utils::parse_raw_contract_input::(contract_input)?; // Execute the given action let res = match action { @@ -105,3 +104,21 @@ impl From for SimpleToken { .unwrap() } } + +impl sdk::ContractAction for SimpleTokenAction { + fn as_blob( + &self, + contract_name: sdk::ContractName, + caller: Option, + callees: Option>, + ) -> sdk::Blob { + sdk::Blob { + contract_name, + data: sdk::BlobData::from(sdk::StructuredBlobData { + caller, + callees, + parameters: self.clone(), + }), + } + } +} diff --git a/ticket-app/contract/src/lib.rs b/ticket-app/contract/src/lib.rs index 227cff9..6a3cdc8 100644 --- a/ticket-app/contract/src/lib.rs +++ b/ticket-app/contract/src/lib.rs @@ -19,12 +19,14 @@ impl sdk::HyleContract for TicketAppState { let transfer_action_contract_name = contract_input.blobs.get(1).unwrap().contract_name.clone(); + let user_identity = contract_input.identity.clone(); + let res = match ticket_app_action { TicketAppAction::BuyTicket {} => { self.buy_ticket(&ctx, transfer_action, transfer_action_contract_name)? } TicketAppAction::RefundTicket {} => { - self.refund_ticket(&ctx, transfer_action, transfer_action_contract_name)? + self.refund_ticket(&ctx, transfer_action, transfer_action_contract_name, user_identity)? } TicketAppAction::HasTicket {} => self.has_ticket(&ctx)?, }; @@ -38,7 +40,7 @@ impl sdk::HyleContract for TicketAppState { } } /// Enum representing the actions that can be performed by the Amm contract. -#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Serialize, Deserialize)] pub enum TicketAppAction { BuyTicket {}, RefundTicket {}, @@ -91,7 +93,7 @@ impl TicketAppState { if amount < self.ticket_price.1 { return Err(format!( "Transfer amount should be at least {} but was {}", - self.ticket_price.0, &recipient + self.ticket_price.1, &amount )); } } @@ -109,6 +111,7 @@ impl TicketAppState { ctx: &ExecutionContext, erc20_action: SimpleTokenAction, erc20_name: ContractName, + user_identity: Identity, ) -> Result { // Check that a blob exists matching the given action, pop it from the callee blobs. @@ -118,10 +121,17 @@ impl TicketAppState { match erc20_action { SimpleTokenAction::Transfer { recipient, amount } => { - if recipient != ctx.caller.0 { + if recipient != user_identity.0 { return Err(format!( "Transfer recipient should be {} but was {}", - ctx.caller, &recipient + user_identity.0, &recipient + )); + } + + if ctx.contract_name.0 != ctx.caller.0 { + return Err(format!( + "Caller should be {} but was {}", + ctx.contract_name.0, &ctx.caller.0 )); } @@ -135,7 +145,7 @@ impl TicketAppState { if amount < self.ticket_price.1 { return Err(format!( "Transfer amount should be at least {} but was {}", - self.ticket_price.0, &recipient + self.ticket_price.1, &amount )); } } @@ -168,3 +178,21 @@ impl From for TicketAppState { borsh::from_slice(&state.0).expect("Could not decode TicketAppState") } } + +impl sdk::ContractAction for TicketAppAction { + fn as_blob( + &self, + contract_name: sdk::ContractName, + caller: Option, + callees: Option>, + ) -> sdk::Blob { + sdk::Blob::from(sdk::StructuredBlob { + contract_name, + data: sdk::StructuredBlobData { + caller, + callees, + parameters: self.clone(), + }, + }) + } +} diff --git a/ticket-app/host/src/main.rs b/ticket-app/host/src/main.rs index 4ecc276..3d9a93e 100644 --- a/ticket-app/host/src/main.rs +++ b/ticket-app/host/src/main.rs @@ -3,8 +3,6 @@ use clap::{Parser, Subcommand}; use client_sdk::helpers::risc0::Risc0Prover; use contract::SimpleToken; use contract::SimpleTokenAction; -// use hyle_hyllar::{Hyllar, HyllarAction}; -// use hyle_contracts::HYLLAR_ELF; use contract_identity::IdentityAction; use contract_ticket_app::TicketAppAction; use contract_ticket_app::TicketAppState; @@ -12,7 +10,7 @@ use sdk::api::APIRegisterContract; use sdk::BlobTransaction; use sdk::Identity; use sdk::ProofTransaction; -use sdk::{ContractInput, ContractName, HyleContract}; +use sdk::{ContractInput, ContractName, HyleContract, ContractAction}; // These constants represent the RISC-V ELF and the image ID generated by risc0-build. // The ELF is used for proving and the ID is used for verification. @@ -68,7 +66,6 @@ async fn main() { let ticket_prover = Risc0Prover::new(methods_ticket_app::GUEST_ELF); let identity_prover = Risc0Prover::new(methods_identity::GUEST_ELF); let token_prover = Risc0Prover::new(methods::GUEST_ELF); - // let token_prover = Risc0Prover::new(HYLLAR_ELF); match cli.command { Commands::Register { token, price } => { @@ -111,7 +108,7 @@ async fn main() { nonce: cli.nonce.parse().unwrap(), }; - let identity_contract_name = cli.user.rsplit_once(".").unwrap().1.to_string(); + let identity_contract_name = cli.user.rsplit_once("@").unwrap().1.to_string(); let blobs = vec![ sdk::Blob { @@ -178,17 +175,6 @@ async fn main() { println!("Running and proving Transfer blob"); - // let state_commitment = client - // .get_contract(&initial_state.ticket_price.0.clone().into()) - // .await - // .unwrap() - // .state; - - // // Deserialize the state manually - // let initial_state_a: Hyllar = borsh::from_slice(&state_commitment.0) - // .map_err(|_| "Could not decode hyllar state".to_string()) - // .unwrap(); - // // Build the transfer a input let initial_state_a: SimpleToken = client .get_contract(&initial_state.ticket_price.0.clone().into()) @@ -275,6 +261,13 @@ async fn main() { let identity_contract_name = cli.user.rsplit_once(".").unwrap().1.to_string(); + // Build the blobs + let refund_action = TicketAppAction::RefundTicket {}; + let transfer_action = SimpleTokenAction::Transfer { + recipient: identity.0.clone(), + amount: initial_state.ticket_price.1, + }; + let blobs = vec![ sdk::Blob { contract_name: identity_contract_name.clone().into(), @@ -282,25 +275,8 @@ async fn main() { borsh::to_vec(&identity_cf).expect("Failed to encode Identity action"), ), }, - sdk::Blob::from(sdk::StructuredBlob { - contract_name: contract_name.clone().into(), - data: sdk::StructuredBlobData { - caller: None, - callees: Some(vec![sdk::BlobIndex(2)]), - parameters: TicketAppAction::RefundTicket {}, - }, - }), - sdk::Blob::from(sdk::StructuredBlob { - contract_name: initial_state.ticket_price.0.clone(), - data: sdk::StructuredBlobData { - caller: Some(sdk::BlobIndex(1)), - callees: None, - parameters: SimpleTokenAction::Transfer { - recipient: identity.0.clone(), - amount: initial_state.ticket_price.1, - }, - }, - }), + refund_action.as_blob(contract_name.clone().into(), None, Some(vec![sdk::BlobIndex(2)])), + transfer_action.as_blob(initial_state.ticket_price.0.clone().into(), Some(sdk::BlobIndex(1)), None), ]; println!("Blobs {:?}", blobs.clone()); @@ -340,17 +316,6 @@ async fn main() { println!("Running and proving Transfer blob"); - // let state_commitment = client - // .get_contract(&initial_state.ticket_price.0.clone().into()) - // .await - // .unwrap() - // .state; - - // // Deserialize the state manually - // let initial_state_a: Hyllar = borsh::from_slice(&state_commitment.0) - // .map_err(|_| "Could not decode hyllar state".to_string()) - // .unwrap(); - // Build the transfer a input let initial_state_a: SimpleToken = client .get_contract(&initial_state.ticket_price.0.clone().into())