mirror of
https://github.com/logos-blockchain/logos-blockchain-pocs.git
synced 2026-01-06 23:23:08 +00:00
update stf proof to read Swap Args from output data
This commit is contained in:
parent
545d5b822f
commit
9a131c6718
@ -1,6 +1,7 @@
|
||||
use cl::{
|
||||
crust::{
|
||||
balance::{UnitWitness, NOP_COVENANT},
|
||||
tx::LedgerUpdate,
|
||||
InputWitness, Nonce, Nullifier, NullifierCommitment, NullifierSecret, OutputWitness, Tx,
|
||||
Unit,
|
||||
},
|
||||
@ -50,6 +51,8 @@ pub struct SwapArgs {
|
||||
pub output: SwapOutput,
|
||||
// minimum value of the output note
|
||||
pub limit: u64,
|
||||
// the nonce used in the swap goal note
|
||||
pub nonce: Nonce,
|
||||
}
|
||||
|
||||
impl SwapArgs {
|
||||
@ -66,12 +69,12 @@ impl SwapArgs {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_goal_note(rng: impl RngCore) -> OutputWitness {
|
||||
pub fn swap_goal_note(nonce: Nonce) -> OutputWitness {
|
||||
OutputWitness {
|
||||
state: [0u8; 32],
|
||||
value: 1,
|
||||
unit: swap_goal_unit().unit(),
|
||||
nonce: Nonce::random(rng),
|
||||
nonce,
|
||||
zone_id: ZONE_ID,
|
||||
nf_pk: NullifierSecret::zero().commit(),
|
||||
}
|
||||
@ -310,25 +313,38 @@ impl ZoneData {
|
||||
}
|
||||
|
||||
/// Check no pool notes are used in this tx
|
||||
pub fn validate_no_pools(&self, tx: &Tx) -> bool {
|
||||
let Some(zone_update) = tx.updates.get(&self.zone_id) else {
|
||||
// this tx is not involving this zone, therefore it is
|
||||
// guaranteed to not consume pool notes
|
||||
return true;
|
||||
};
|
||||
|
||||
pub fn validate_no_pools(&self, zone_update: &LedgerUpdate) -> bool {
|
||||
self.nfs.iter().all(|nf| !zone_update.has_input(nf))
|
||||
}
|
||||
|
||||
pub fn validate_op(&self, op: &ZoneOp) -> bool {
|
||||
match op {
|
||||
ZoneOp::Swap(swap) => self.check_swap(swap),
|
||||
ZoneOp::AddLiquidity { tx, .. } => self.validate_no_pools(tx),
|
||||
ZoneOp::RemoveLiquidity { tx, .. } => self.validate_no_pools(tx), // should we check shares exist?
|
||||
ZoneOp::AddLiquidity { tx, .. } => {
|
||||
let Some(zone_update) = tx.updates.get(&self.zone_id) else {
|
||||
// this tx is not involving this zone, therefore it is
|
||||
// guaranteed to not consume pool notes
|
||||
return true;
|
||||
};
|
||||
self.validate_no_pools(zone_update)
|
||||
}
|
||||
ZoneOp::RemoveLiquidity { tx, .. } => {
|
||||
let Some(zone_update) = tx.updates.get(&self.zone_id) else {
|
||||
// this tx is not involving this zone, therefore it is
|
||||
// guaranteed to not consume pool notes
|
||||
return true;
|
||||
};
|
||||
self.validate_no_pools(zone_update) // should we check shares exist?
|
||||
}
|
||||
ZoneOp::Ledger(tx) => {
|
||||
let Some(zone_update) = tx.updates.get(&self.zone_id) else {
|
||||
// this tx is not involving this zone, therefore it is
|
||||
// guaranteed to not consume pool notes
|
||||
return true;
|
||||
};
|
||||
// Just a ledger tx that does not directly interact with the zone,
|
||||
// just validate it's not using pool notes
|
||||
self.validate_no_pools(tx)
|
||||
self.validate_no_pools(zone_update)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -433,26 +449,19 @@ impl ZoneData {
|
||||
pub fn process_op(&mut self, op: &ZoneOp) {
|
||||
match op {
|
||||
ZoneOp::Swap(swap) => self.swap(swap),
|
||||
ZoneOp::AddLiquidity {
|
||||
tx, add_liquidity, ..
|
||||
} => {
|
||||
ZoneOp::AddLiquidity { add_liquidity, .. } => {
|
||||
self.add_liquidity(add_liquidity);
|
||||
assert!(self.validate_no_pools(tx));
|
||||
// TODo: check proof
|
||||
}
|
||||
ZoneOp::RemoveLiquidity {
|
||||
tx,
|
||||
remove_liquidity,
|
||||
..
|
||||
remove_liquidity, ..
|
||||
} => {
|
||||
self.remove_liquidity(remove_liquidity);
|
||||
assert!(self.validate_no_pools(tx));
|
||||
// TODO: check proof
|
||||
}
|
||||
ZoneOp::Ledger(tx) => {
|
||||
ZoneOp::Ledger(_) => {
|
||||
// Just a ledger tx that does not directly interact with the zone,
|
||||
// just validate it's not using pool notes
|
||||
self.validate_no_pools(tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
use app::{AddLiquidity, SwapArgs, SwapOutput, ZoneData, ZONE_ID};
|
||||
use cl::{
|
||||
crust::{InputWitness, Nonce, NullifierSecret, OutputWitness, TxWitness, UnitWitness},
|
||||
mantle::ledger::LedgerState,
|
||||
};
|
||||
use app::{AddLiquidity, ZoneData};
|
||||
use cl::crust::{Nonce, NullifierSecret, UnitWitness};
|
||||
|
||||
fn nmo() -> UnitWitness {
|
||||
UnitWitness::nop(b"NMO")
|
||||
@ -56,47 +53,3 @@ fn pair_price() {
|
||||
Some(39) // 11 MEM slippage
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_swap() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let alice_sk = NullifierSecret::random(&mut rng);
|
||||
|
||||
let alice_in = InputWitness {
|
||||
state: [0u8; 32],
|
||||
value: 10,
|
||||
unit_witness: nmo(),
|
||||
nonce: Nonce::random(&mut rng),
|
||||
zone_id: ZONE_ID,
|
||||
nf_sk: alice_sk,
|
||||
};
|
||||
|
||||
let alice_out = OutputWitness {
|
||||
state: [0u8; 32],
|
||||
value: 100,
|
||||
unit: mem().unit(),
|
||||
nonce: Nonce::random(&mut rng),
|
||||
zone_id: ZONE_ID,
|
||||
nf_pk: alice_sk.commit(),
|
||||
};
|
||||
|
||||
let mut ledger = LedgerState::default();
|
||||
|
||||
// alice's input note is already in the ledger
|
||||
let alice_in_proof = ledger.add_commitment(&alice_in.note_commitment());
|
||||
|
||||
let swap_tx = TxWitness::default()
|
||||
.add_input(alice_in, alice_in_proof)
|
||||
.add_output(alice_out, b"")
|
||||
.add_output(
|
||||
app::swap_goal_note(&mut rng),
|
||||
SwapArgs {
|
||||
output: SwapOutput::basic(mem().unit(), ZONE_ID, alice_sk.commit(), &mut rng),
|
||||
limit: 90,
|
||||
},
|
||||
);
|
||||
|
||||
panic!()
|
||||
// alice ---- swap_tx ---> executor
|
||||
}
|
||||
@ -4,10 +4,12 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
methods = { path = "../methods" }
|
||||
risc0-zkvm = { version = "1.2.0" }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
serde = "1.0"
|
||||
ledger_proof_statements = { path = "../../../../cl/ledger_proof_statements" }
|
||||
app = { path = "../../app" }
|
||||
cl = { path = "../../../../cl/cl" }
|
||||
cl = { path = "../../../../cl/cl" }
|
||||
ledger = { path = "../../../../cl/ledger" }
|
||||
|
||||
77
emmarin/apps/swapvm/stf/host/tests/swap.rs
Normal file
77
emmarin/apps/swapvm/stf/host/tests/swap.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use app::{AddLiquidity, ZoneData, ZONE_ID};
|
||||
use cl::{
|
||||
crust::{InputWitness, Nonce, NullifierSecret, TxWitness, UnitWitness},
|
||||
mantle::ledger::LedgerState,
|
||||
};
|
||||
|
||||
fn nmo() -> UnitWitness {
|
||||
UnitWitness::nop(b"NMO")
|
||||
}
|
||||
fn mem() -> UnitWitness {
|
||||
UnitWitness::nop(b"MEM")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_swap() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let alice_sk = NullifierSecret::random(&mut rng);
|
||||
|
||||
let alice_in = InputWitness {
|
||||
state: [0u8; 32],
|
||||
value: 10,
|
||||
unit_witness: nmo(),
|
||||
nonce: Nonce::random(&mut rng),
|
||||
zone_id: ZONE_ID,
|
||||
nf_sk: alice_sk,
|
||||
};
|
||||
|
||||
let mut ledger = LedgerState::default();
|
||||
|
||||
// alice's input note is already in the ledger
|
||||
let alice_in_proof = ledger.add_commitment(&alice_in.note_commitment());
|
||||
|
||||
let swap_goal_nonce = Nonce::random(&mut rng);
|
||||
|
||||
let swap_tx = TxWitness::default()
|
||||
.add_input(alice_in, alice_in_proof)
|
||||
.add_output(
|
||||
app::swap_goal_note(swap_goal_nonce),
|
||||
app::SwapArgs {
|
||||
output: app::SwapOutput::basic(mem().unit(), ZONE_ID, alice_sk.commit(), &mut rng),
|
||||
limit: 90,
|
||||
nonce: swap_goal_nonce,
|
||||
},
|
||||
);
|
||||
|
||||
let swap_tx_proof = ledger::tx::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),
|
||||
));
|
||||
|
||||
// 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(),
|
||||
9.0
|
||||
);
|
||||
|
||||
// ensure that the realized output is above the limit order
|
||||
assert!(
|
||||
swapvm_state
|
||||
.amount_out(nmo().unit(), mem().unit(), 10)
|
||||
.unwrap()
|
||||
>= 90
|
||||
);
|
||||
|
||||
panic!();
|
||||
}
|
||||
@ -1,78 +1,75 @@
|
||||
use app::{StateUpdate, ZoneData, ZoneOp};
|
||||
use app::{StateUpdate, ZoneData, ZoneOp, SwapArgs};
|
||||
use cl::{
|
||||
crust::Tx,
|
||||
mantle::{ledger::Ledger, zone::ZoneState},
|
||||
crust::{BundleWitness, TxRoot},
|
||||
mantle::{
|
||||
ledger::{Ledger, LedgerWitness},
|
||||
zone::ZoneState,
|
||||
},
|
||||
};
|
||||
use ledger_proof_statements::{
|
||||
ledger::{LedgerProofPublic, SyncLog},
|
||||
stf::StfPublic,
|
||||
};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn main() {
|
||||
let mut zone_data: ZoneData = env::read();
|
||||
let old_ledger: Ledger = env::read();
|
||||
let ledger: Ledger = env::read();
|
||||
let sync_logs: Vec<SyncLog> = env::read();
|
||||
let mut ledger_witness: LedgerWitness = env::read();
|
||||
let stf: [u8; 32] = env::read();
|
||||
let new_ledger: Ledger = env::read();
|
||||
let bundles: Vec<BundleWitness> = env::read();
|
||||
let ops: Vec<ZoneOp> = env::read();
|
||||
let update_tx: StateUpdate = env::read();
|
||||
|
||||
let zone_id = zone_data.zone_id;
|
||||
|
||||
let old_zone_data = zone_data.commit();
|
||||
let old_state = ZoneState {
|
||||
ledger: ledger_witness.commit(),
|
||||
zone_data: zone_data.commit(),
|
||||
stf,
|
||||
};
|
||||
|
||||
for op in &ops {
|
||||
zone_data.process_op(op);
|
||||
}
|
||||
|
||||
let txs: Vec<&Tx> = ops
|
||||
.iter()
|
||||
.filter_map(|op| match op {
|
||||
ZoneOp::Swap(_) => None,
|
||||
ZoneOp::AddLiquidity { tx, .. } => Some(tx),
|
||||
ZoneOp::RemoveLiquidity { tx, .. } => Some(tx),
|
||||
ZoneOp::Ledger(tx) => Some(tx),
|
||||
})
|
||||
.chain(std::iter::once(&update_tx.tx))
|
||||
.collect();
|
||||
for bundle in bundles {
|
||||
ledger_witness.add_bundle(bundle.root());
|
||||
|
||||
let outputs = txs
|
||||
.iter()
|
||||
.flat_map(|tx| tx.updates.iter().filter(|u| u.zone_id == zone_id))
|
||||
.flat_map(|u| u.outputs.iter())
|
||||
.copied()
|
||||
.collect();
|
||||
// TODO: inputs missings from ledger proof public
|
||||
let _inputs: Vec<_> = txs
|
||||
.iter()
|
||||
.flat_map(|tx| tx.updates.iter().filter(|u| u.zone_id == zone_id))
|
||||
.flat_map(|u| u.inputs.iter())
|
||||
.copied()
|
||||
.collect();
|
||||
for tx in bundle.txs {
|
||||
let Some(zone_update) = tx.updates.get(&zone_id) else {
|
||||
// this tx does not concern this zone, ignore it.
|
||||
continue
|
||||
};
|
||||
|
||||
let ledger_public = LedgerProofPublic {
|
||||
old_ledger,
|
||||
ledger,
|
||||
id: zone_id,
|
||||
sync_logs,
|
||||
outputs,
|
||||
};
|
||||
zone_data.validate_no_pools(zone_update);
|
||||
|
||||
env::verify(
|
||||
ledger_validity_proof::LEDGER_ID,
|
||||
&risc0_zkvm::serde::to_vec(&ledger_public).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
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 we had processed all the ops
|
||||
assert!(ops.is_empty());
|
||||
|
||||
// ensure that we've seen every bundle in this ledger update
|
||||
assert_eq!(ledger_witness.bundles.commit(), new_ledger.bundles_root);
|
||||
|
||||
let public = StfPublic {
|
||||
old: ZoneState {
|
||||
ledger: old_ledger,
|
||||
zone_data: old_zone_data,
|
||||
stf,
|
||||
},
|
||||
old: old_state,
|
||||
new: ZoneState {
|
||||
ledger,
|
||||
ledger: new_ledger,
|
||||
zone_data: zone_data.update_and_commit(&update_tx),
|
||||
stf,
|
||||
},
|
||||
|
||||
@ -9,7 +9,7 @@ rand_core = "0.6.0"
|
||||
hex = "0.4.3"
|
||||
risc0-zkvm = "1.2"
|
||||
itertools = "0.14"
|
||||
bincode = { version = "2", features = ["serde"] }
|
||||
bincode = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
};
|
||||
|
||||
/// An identifier of a transaction
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
|
||||
pub struct TxRoot(pub [u8; 32]);
|
||||
|
||||
/// An identifier of a bundle
|
||||
|
||||
@ -15,12 +15,9 @@ pub fn hash(data: &[u8]) -> [u8; 32] {
|
||||
|
||||
// TODO: spec serializiation
|
||||
pub fn serialize(data: impl Serialize) -> Vec<u8> {
|
||||
bincode::serde::encode_to_vec(data, bincode::config::standard()).unwrap()
|
||||
bincode::serialize(&data).unwrap()
|
||||
}
|
||||
|
||||
pub fn deserialize<T: DeserializeOwned>(bytes: &[u8]) -> T {
|
||||
let (value, bytes_read) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())
|
||||
.expect("failed to deserialize");
|
||||
assert_eq!(bytes_read, bytes.len());
|
||||
value
|
||||
bincode::deserialize(bytes).unwrap()
|
||||
}
|
||||
|
||||
@ -9,9 +9,9 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Ledger {
|
||||
cm_root: [u8; 32],
|
||||
nf_root: [u8; 32],
|
||||
bundles_root: [u8; 32],
|
||||
pub cm_root: [u8; 32],
|
||||
pub nf_root: [u8; 32],
|
||||
pub bundles_root: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user