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 2b643b1..6a3cdc8 100644 --- a/ticket-app/contract/src/lib.rs +++ b/ticket-app/contract/src/lib.rs @@ -19,10 +19,15 @@ 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, user_identity)? + } TicketAppAction::HasTicket {} => self.has_ticket(&ctx)?, }; @@ -35,9 +40,10 @@ 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 {}, HasTicket {}, } @@ -87,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 )); } } @@ -100,6 +106,58 @@ impl TicketAppState { Ok(program_outputs) } + pub fn refund_ticket( + &mut self, + 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. + + if !self.tickets.contains(&ctx.caller) { + return Err(format!("Ticket not present for {:?}", &ctx.caller)); + } + + match erc20_action { + SimpleTokenAction::Transfer { recipient, amount } => { + if recipient != user_identity.0 { + return Err(format!( + "Transfer recipient should be {} but was {}", + 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 + )); + } + + 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.1, &amount + )); + } + } + } + + 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. @@ -120,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 84d05b0..3d9a93e 100644 --- a/ticket-app/host/src/main.rs +++ b/ticket-app/host/src/main.rs @@ -10,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. @@ -46,6 +46,7 @@ struct Cli { enum Commands { Register { token: String, price: u128 }, BuyTicket {}, + RefundTicket {}, HasTicket {}, } @@ -100,14 +101,14 @@ 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(), 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 { @@ -116,7 +117,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 +175,7 @@ async fn main() { println!("Running and proving Transfer blob"); - // Build the transfer a input + // // Build the transfer a input let initial_state_a: SimpleToken = client .get_contract(&initial_state.ticket_price.0.clone().into()) .await @@ -237,6 +238,147 @@ 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(); + + // 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(), + data: sdk::BlobData( + borsh::to_vec(&identity_cf).expect("Failed to encode Identity action"), + ), + }, + 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()); + + 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"); + + // 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())