Skip to content

Add refund ticket functionality #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions simple-token/contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<SimpleTokenAction>(contract_input)?;
let (action, ctx) = sdk::utils::parse_raw_contract_input::<SimpleTokenAction>(contract_input)?;

// Execute the given action
let res = match action {
Expand Down Expand Up @@ -105,3 +104,21 @@ impl From<sdk::StateCommitment> for SimpleToken {
.unwrap()
}
}

impl sdk::ContractAction for SimpleTokenAction {
fn as_blob(
&self,
contract_name: sdk::ContractName,
caller: Option<sdk::BlobIndex>,
callees: Option<Vec<sdk::BlobIndex>>,
) -> sdk::Blob {
sdk::Blob {
contract_name,
data: sdk::BlobData::from(sdk::StructuredBlobData {
caller,
callees,
parameters: self.clone(),
}),
}
}
}
80 changes: 78 additions & 2 deletions ticket-app/contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?,
};

Expand All @@ -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 {},
}

Expand Down Expand Up @@ -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
));
}
}
Expand All @@ -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<String, String> {
// 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<String, String> {
// Check that a blob exists matching the given action, pop it from the callee blobs.

Expand All @@ -120,3 +178,21 @@ impl From<sdk::StateCommitment> 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<sdk::BlobIndex>,
callees: Option<Vec<sdk::BlobIndex>>,
) -> sdk::Blob {
sdk::Blob::from(sdk::StructuredBlob {
contract_name,
data: sdk::StructuredBlobData {
caller,
callees,
parameters: self.clone(),
},
})
}
}
152 changes: 147 additions & 5 deletions ticket-app/host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -46,6 +46,7 @@ struct Cli {
enum Commands {
Register { token: String, price: u128 },
BuyTicket {},
RefundTicket {},
HasTicket {},
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: if you update to a newer version, you might need to change that as we now use the @ instead of . to split the identity & contract name


// 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())
Expand Down