mirror of
https://github.com/logos-blockchain/logos-blockchain-pocs.git
synced 2026-01-16 12:03:12 +00:00
294 lines
8.4 KiB
Rust
294 lines
8.4 KiB
Rust
use cl::{
|
|
crust::{
|
|
balance::{UnitWitness, NOP_COVENANT},
|
|
tx::LedgerUpdate,
|
|
InputWitness, Nonce, Nullifier, NullifierCommitment, NullifierSecret, OutputWitness, Tx,
|
|
Unit,
|
|
},
|
|
mantle::ZoneId,
|
|
};
|
|
use rand::RngCore;
|
|
use risc0_zkvm::sha::rust_crypto::{Digest, Sha256};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
const FUNDS_SK: NullifierSecret = NullifierSecret([0; 16]);
|
|
pub const ZONE_ID: [u8; 32] = [128; 32];
|
|
|
|
pub fn swap_goal_unit() -> UnitWitness {
|
|
UnitWitness::nop(b"SWAP")
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SwapOutput {
|
|
// value will be set at the market price
|
|
pub state: [u8; 32],
|
|
pub unit: Unit,
|
|
pub nonce: Nonce,
|
|
pub zone_id: ZoneId,
|
|
pub nf_pk: NullifierCommitment,
|
|
}
|
|
impl SwapOutput {
|
|
pub fn basic(
|
|
unit: Unit,
|
|
zone_id: ZoneId,
|
|
nf_pk: NullifierCommitment,
|
|
rng: impl RngCore,
|
|
) -> Self {
|
|
Self {
|
|
state: [0; 32],
|
|
unit,
|
|
nonce: Nonce::random(rng),
|
|
zone_id,
|
|
nf_pk,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SwapArgs {
|
|
// the user specifies the template forthe output note
|
|
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 {
|
|
pub fn to_output(self, value: u64) -> OutputWitness {
|
|
assert!(value >= self.limit);
|
|
OutputWitness {
|
|
state: self.output.state,
|
|
value,
|
|
unit: self.output.unit,
|
|
nonce: self.output.nonce,
|
|
zone_id: self.output.zone_id,
|
|
nf_pk: self.output.nf_pk,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn swap_goal_note(nonce: Nonce) -> InputWitness {
|
|
InputWitness {
|
|
state: [0u8; 32],
|
|
value: 1,
|
|
unit_witness: swap_goal_unit(),
|
|
nonce,
|
|
zone_id: ZONE_ID,
|
|
nf_sk: NullifierSecret::zero(),
|
|
}
|
|
}
|
|
|
|
// TODO: order pair tokens lexicographically
|
|
fn get_pair_share_unit(pair: Pair) -> UnitWitness {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(b"SWAP_PAIR_SHARE_UNIT");
|
|
hasher.update(pair.t0);
|
|
hasher.update(pair.t1);
|
|
UnitWitness {
|
|
spending_covenant: NOP_COVENANT,
|
|
minting_covenant: NOP_COVENANT,
|
|
burning_covenant: NOP_COVENANT,
|
|
arg: hasher.finalize().into(),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Swap {
|
|
pair: Pair,
|
|
t0_in: u64,
|
|
t1_in: u64,
|
|
t0_out: u64,
|
|
t1_out: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ZoneData {
|
|
pub nfs: BTreeSet<Nullifier>,
|
|
pub pools: BTreeMap<Pair, Pool>,
|
|
pub zone_id: ZoneId,
|
|
pub swaps_output: Vec<OutputWitness>,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)]
|
|
pub struct Pair {
|
|
pub t0: Unit,
|
|
pub t1: Unit,
|
|
}
|
|
|
|
impl Pair {
|
|
pub fn new(t_a: Unit, t_b: Unit) -> Self {
|
|
Self {
|
|
t0: std::cmp::min(t_a, t_b),
|
|
t1: std::cmp::max(t_a, t_b),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Pool {
|
|
pub balance_0: u64,
|
|
pub balance_1: u64,
|
|
pub shares_unit: Unit,
|
|
pub total_shares: u64,
|
|
}
|
|
|
|
impl Pool {
|
|
pub fn price(&self) -> f64 {
|
|
self.balance_1 as f64 / self.balance_0 as f64
|
|
}
|
|
}
|
|
|
|
// Txs are of the following form:
|
|
impl ZoneData {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
nfs: Default::default(),
|
|
pools: Default::default(),
|
|
zone_id: ZONE_ID,
|
|
swaps_output: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn pair_price(&self, t_in: Unit, t_out: Unit) -> Option<f64> {
|
|
let pair = Pair::new(t_in, t_out);
|
|
self.pools.get(&pair).map(|pool| pool.price()).map(|price| {
|
|
if t_in == pair.t0 {
|
|
price
|
|
} else {
|
|
1.0 / price
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn amount_out(&self, t_in: Unit, t_out: Unit, amount_in: u64) -> Option<u64> {
|
|
let pair = Pair::new(t_in, t_out);
|
|
let pool = self.pools.get(&pair)?;
|
|
|
|
let (balance_in, balance_out) = if pair.t0 == t_in {
|
|
(pool.balance_0, pool.balance_1)
|
|
} else {
|
|
(pool.balance_1, pool.balance_0)
|
|
};
|
|
|
|
let amount_in_after_fee = amount_in * 1000 - amount_in * 3;
|
|
|
|
let amount_out =
|
|
(balance_out * 1000 * amount_in_after_fee) / (balance_in * 1000 + amount_in_after_fee);
|
|
|
|
Some(amount_out / 1000)
|
|
}
|
|
|
|
/// A swap does not need to directly modify the pool balances, but the executor
|
|
/// should make sure that required funds are provided.
|
|
pub fn swap(&mut self, t_in: Unit, amount_in: u64, swap: SwapArgs) {
|
|
// TODO: calculate amout outside proof and check here for efficiency
|
|
let amount_out = self.amount_out(t_in, swap.output.unit, amount_in).unwrap();
|
|
|
|
let pair = Pair::new(t_in, swap.output.unit);
|
|
let pool = self.pools.get_mut(&pair).unwrap();
|
|
|
|
let (balance_in, balance_out) = if pair.t0 == t_in {
|
|
(&mut pool.balance_0, &mut pool.balance_1)
|
|
} else {
|
|
(&mut pool.balance_1, &mut pool.balance_0)
|
|
};
|
|
|
|
*balance_in += amount_in;
|
|
*balance_out -= amount_out;
|
|
self.swaps_output.push(swap.to_output(amount_out));
|
|
}
|
|
|
|
/// Check no pool notes are used in this tx
|
|
pub fn validate_no_pools(&self, zone_update: &LedgerUpdate) -> bool {
|
|
self.nfs.iter().all(|nf| !zone_update.has_input(nf))
|
|
}
|
|
|
|
pub fn pools_update(&mut self, tx: &Tx, pool_notes: &[InputWitness]) {
|
|
let Some(zone_update) = tx.updates.get(&self.zone_id) else {
|
|
// The tx is not involving this zone, nothing to do.
|
|
return;
|
|
};
|
|
// check all previous nullifiers are used
|
|
assert!(self.nfs.iter().all(|nf| zone_update.has_input(nf)));
|
|
self.nfs.clear();
|
|
|
|
// check the exepected pool balances are reflected in the tx outputs
|
|
let expected_pool_balances = self.expected_pool_balances();
|
|
for note in pool_notes {
|
|
assert_eq!(note.nf_sk, FUNDS_SK);
|
|
// TODO: check nonce derivation
|
|
let output = note.to_output();
|
|
let value = expected_pool_balances.get(&output.unit).unwrap();
|
|
assert_eq!(note.value, *value);
|
|
|
|
assert!(zone_update.has_output(&output.note_commitment()));
|
|
self.nfs.insert(note.nullifier());
|
|
}
|
|
}
|
|
|
|
pub fn expected_pool_balances(&self) -> BTreeMap<Unit, u64> {
|
|
let mut expected_pool_balances = BTreeMap::new();
|
|
for (Pair { t0, t1 }, pool) in self.pools.iter() {
|
|
*expected_pool_balances.entry(*t0).or_insert(0) += pool.balance_0;
|
|
*expected_pool_balances.entry(*t1).or_insert(0) += pool.balance_1;
|
|
}
|
|
|
|
expected_pool_balances
|
|
}
|
|
|
|
// TODO: only for testing purposes
|
|
pub fn add_liquidity(&mut self, t0_unit: Unit, t1_unit: Unit, t0_in: u64, t1_in: u64) {
|
|
let pair = Pair::new(t0_unit, t1_unit);
|
|
let pool = self.pools.entry(pair).or_insert(Pool {
|
|
balance_0: 0,
|
|
balance_1: 0,
|
|
shares_unit: get_pair_share_unit(pair).unit(),
|
|
total_shares: 0,
|
|
});
|
|
let (balance_0, balance_1) = if pair.t0 == t0_unit {
|
|
(&mut pool.balance_0, &mut pool.balance_1)
|
|
} else {
|
|
(&mut pool.balance_1, &mut pool.balance_0)
|
|
};
|
|
|
|
*balance_0 += t0_in;
|
|
*balance_1 += t1_in;
|
|
}
|
|
|
|
pub fn check_swaps_executed(&self, tx: &Tx) {
|
|
let zone_update = tx.updates.get(&self.zone_id).unwrap();
|
|
for output in &self.swaps_output {
|
|
assert!(zone_update.has_output(&output.note_commitment()));
|
|
}
|
|
}
|
|
|
|
pub fn update_and_commit(mut self, tx: &Tx, pool_notes: &[InputWitness]) -> [u8; 32] {
|
|
self.pools_update(&tx, pool_notes);
|
|
self.check_swaps_executed(&tx);
|
|
self.commit()
|
|
}
|
|
|
|
pub fn commit(&self) -> [u8; 32] {
|
|
let mut hasher = Sha256::new();
|
|
for nf in &self.nfs {
|
|
hasher.update(nf);
|
|
}
|
|
for (pair, pool) in self.pools.iter() {
|
|
hasher.update(pair.t0);
|
|
hasher.update(pair.t1);
|
|
hasher.update(pool.balance_0.to_le_bytes());
|
|
hasher.update(pool.balance_1.to_le_bytes());
|
|
}
|
|
hasher.update(self.zone_id);
|
|
hasher.finalize().into()
|
|
}
|
|
}
|
|
|
|
impl Default for ZoneData {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|