swap scenario

This commit is contained in:
David Rusu 2025-03-11 19:02:55 +04:00
parent 104ad9bbff
commit bde46cefe7
3 changed files with 184 additions and 39 deletions

View File

@ -1,10 +1,98 @@
use app::{StateUpdate, ZoneOp};
use cl::crust::BundleWitness;
use cl::mantle::{ledger::Ledger, zone::ZoneData};
use std::collections::BTreeMap;
use app::{swap_goal_unit, StateUpdate, ZoneData, ZoneOp};
use cl::crust::{BundleWitness, InputWitness, NoteCommitment, Nullifier, Unit};
use cl::ds::mmr::{MMRProof, MMR};
use cl::mantle::ledger::Ledger;
use cl::mantle::ledger::LedgerState;
use ledger_proof_statements::ledger::SyncLog;
use methods::{STF_ELF, STF_ID};
use risc0_zkvm::{ExecutorEnv, Prover, Receipt, Result};
#[derive(Debug)]
struct FundNote {
note: InputWitness,
mmr: MMR,
path: MMRProof,
}
#[derive(Debug, Default)]
pub struct ExecutorState {
pub ledger: LedgerState,
pub swapvm: ZoneData,
pub fund_notes: BTreeMap<Unit, FundNote>,
}
impl ExecutorState {
pub fn observe_cms(&mut self, cms: impl IntoIterator<Item = NoteCommitment>) {
for cm in cms {
self.ledger.add_commitment(&cm);
// update merkle proofs for each fund note.
for (_, fund_note) in self.fund_notes.iter_mut() {
let folds = fund_note.mmr.folds(&cm.0);
fund_note
.path
.update(&fund_note.note.note_commitment().0, folds);
}
}
}
pub fn observe_nfs(&mut self, nfs: Vec<Nullifier>) {
self.ledger.add_nullifiers(nfs);
}
// pub fn bundle_tx(&mut self, proved_tx: ProvedTx) -> StfPrivate {
// let tx = proved_tx.public();
// if tx.balance.unit_balance(swap_goal_unit()).is_neg() {
// // this is a SWAP
// let (swap_cm, swap_args_bytes) = &tx.outputs[0];
// let swap_args: SwapArgs = cl::deserialize(&swap_args_bytes);
// // verify the user proved the correct swap goal note
// assert_eq!(
// swap_cm,
// &app::swap_goal_note(swap_args.nonce).note_commitment()
// );
// // assume there are only the goal unit and tokenIn units at play
// assert_eq!(tx.balance.balances.len(), 2);
// let balance_in = tx
// .balance
// .balances
// .iter()
// .find(|bal| bal.unit != swap_goal_unit())
// .unwrap();
// let token_in = balance_in.unit;
// assert_eq!(balance_in.neg, 0);
// assert!(balance_in.pos > 0);
// let amount_in = balance_in.pos;
// let amount_out = self
// .swapvm
// .amount_out(token_in, swap_args.output.unit, amount_in)
// .unwrap();
// // ensure we can satisfy the limit order
// assert!(amount_out > swap_args.limit);
// // now build the balancing tx
// let balancing_tx = TxWitness::default()
// .add_input(self.fund_notes[token_in], self.fund_notes[token_in].1)
// .add_input(self.fund_notes[token_out]);
// }
// }
pub fn set_fund_note(&mut self, note: InputWitness, mmr: MMR, path: MMRProof) {
self.fund_notes
.insert(note.unit_witness.unit(), FundNote { note, mmr, path });
}
}
pub struct StfPrivate {
pub zone_data: ZoneData,
pub old_ledger: Ledger,

View File

@ -1,8 +1,8 @@
use app::{AddLiquidity, ZoneData, ZONE_ID};
use cl::{
crust::{InputWitness, Nonce, NullifierSecret, TxWitness, UnitWitness},
mantle::ledger::LedgerState,
};
use app::{AddLiquidity, ZONE_ID};
use cl::crust::{InputWitness, Nonce, NullifierSecret, TxWitness, UnitWitness};
use host::ExecutorState;
use ledger::tx::ProvedTx;
use rand::RngCore;
fn nmo() -> UnitWitness {
UnitWitness::nop(b"NMO")
@ -11,10 +11,59 @@ fn mem() -> UnitWitness {
UnitWitness::nop(b"MEM")
}
fn setup_executor(mut rng: impl RngCore) -> ExecutorState {
let mut exec_state = ExecutorState::default();
let nmo_fund = InputWitness {
state: [0u8; 32],
value: 1348,
unit_witness: nmo(),
nonce: Nonce::random(&mut rng),
zone_id: ZONE_ID,
nf_sk: NullifierSecret::zero(),
};
let (mmr, mmr_proof) = exec_state
.ledger
.add_commitment(&nmo_fund.note_commitment());
exec_state.set_fund_note(nmo_fund, mmr, mmr_proof);
let mem_fund = InputWitness {
state: [0u8; 32],
value: 14102,
unit_witness: mem(),
nonce: Nonce::random(&mut rng),
zone_id: ZONE_ID,
nf_sk: NullifierSecret::zero(),
};
let (mmr, mmr_proof) = exec_state
.ledger
.add_commitment(&mem_fund.note_commitment());
exec_state.set_fund_note(mem_fund, mmr, mmr_proof);
// HACK: we don't currently support liquidity notes, we directly hard code the corresponding liquidity
// in the swapvm instead of minting pool LP tokens
exec_state.swapvm.add_liquidity(&AddLiquidity::new(
nmo().unit(),
nmo_fund.value,
mem().unit(),
mem_fund.value,
NullifierSecret::random(&mut rng).commit(),
Nonce::random(&mut rng),
));
exec_state
}
#[test]
fn simple_swap() {
let mut rng = rand::thread_rng();
// ---- setup scenario ----
let mut exec_state = setup_executor(&mut rng);
// setup fund notes
let alice_sk = NullifierSecret::random(&mut rng);
let alice_in = InputWitness {
@ -26,13 +75,18 @@ fn simple_swap() {
nf_sk: alice_sk,
};
let mut ledger = LedgerState::default();
// alice's note lands in the ledger through some other executor
let mut other_exec_ledger = exec_state.ledger.clone();
let alice_in_proof = other_exec_ledger.add_commitment(&alice_in.note_commitment());
// alice's input note is already in the ledger
let alice_in_proof = ledger.add_commitment(&alice_in.note_commitment());
// executor becomes aware of the commitment through observing a zone update
exec_state.observe_cms([alice_in.note_commitment()]);
// ----- end setup ----
// Alice now has a valid 10 NMO note, she wants to swap it for 90 MEM
// ---- begin swap ----
let swap_goal_nonce = Nonce::random(&mut rng);
let swap_tx = TxWitness::default()
.add_input(alice_in, alice_in_proof)
.add_output(
@ -44,30 +98,26 @@ fn simple_swap() {
},
);
let swap_tx_proof = ledger::tx::ProvedTx::prove(swap_tx, vec![], vec![]).unwrap();
let swap_tx_proof = ProvedTx::prove(swap_tx, vec![], vec![]).unwrap();
//
// alice ---- (swap_tx, swap_tx_proof) ---> executor
let mut swapvm_state = ZoneData::new();
swapvm_state.add_liquidity(&AddLiquidity::new(
nmo().unit(),
1348,
mem().unit(),
14102,
NullifierSecret::random(&mut rng).commit(),
Nonce::random(&mut rng),
));
//
// alice sends the tx to an executor
// ensure the pair price is above the minimum realized price (90 out / 10 in = 9.0)
assert_eq!(
swapvm_state.pair_price(nmo().unit(), mem().unit()).unwrap(),
exec_state
.swapvm
.pair_price(nmo().unit(), mem().unit())
.unwrap(),
9.0
);
// ensure that the realized output is above the limit order
assert!(
swapvm_state
exec_state
.swapvm
.amount_out(nmo().unit(), mem().unit(), 10)
.unwrap()
>= 90

View File

@ -41,21 +41,28 @@ fn main() {
continue
};
zone_data.validate_no_pools(zone_update);
if zone_data.validate_no_pools(zone_update) {
// This tx does not touch pool notes, we can allow zone ops.
if tx.balance.unit_balance(app::swap_goal_unit().unit()).is_neg() {
// This TX encodes a SWAP request.
// as a simplifying assumption, we will assume that the SWAP goal note is the only output
assert_eq!(zone_update.outputs.len(), 1);
let (swap_goal_cm, swap_args_bytes) = &zone_update.outputs[0];
let swap_args: SwapArgs = cl::deserialize(&swap_args_bytes);
// is it a SWAP?
if tx.balance.unit_balance(app::swap_goal_unit().unit()).is_neg() {
// This TX encodes a SWAP request.
// as a simplifying assumption, we will assume that the SWAP goal note is the only output
assert_eq!(zone_update.outputs.len(), 1);
let (swap_goal_cm, swap_args_bytes) = &zone_update.outputs[0];
let swap_args: SwapArgs = cl::deserialize(&swap_args_bytes);
// ensure the witness corresponds to the swap goal cm
assert_eq!(
swap_goal_cm,
&app::swap_goal_note(swap_args.nonce).note_commitment()
);
panic!("zone_data.swap()");
// ensure the witness corresponds to the swap goal cm
assert_eq!(
swap_goal_cm,
&app::swap_goal_note(swap_args.nonce).note_commitment()
);
panic!("zone_data.swap()");
}
// otherwise it's a normal ledger Tx
} else {
// This tx does touch pool notes. therefore we must ensure the changes reflect the zone balances
panic!();
}
}
}