Skip to content

Commit 1647d21

Browse files
committed
fix amm apply_mint
1 parent 5abe1ff commit 1647d21

1 file changed

Lines changed: 182 additions & 1 deletion

File tree

lib/state/amm.rs

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ pub(in crate::state) fn apply_mint(
417417
let new_amm_pool_state = amm_pool_state.mint(amount0, amount1)?;
418418
let lp_tokens_minted = new_amm_pool_state
419419
.outstanding_lp_tokens
420-
.checked_sub(lp_token_mint)
420+
.checked_sub(amm_pool_state.outstanding_lp_tokens)
421421
.ok_or(Error::InvalidMint)?;
422422
if lp_tokens_minted != lp_token_mint {
423423
do yeet Error::InvalidMint;
@@ -522,3 +522,184 @@ pub(in crate::state) fn revert_swap(
522522
pools.put(rwtxn, &amm_pair, &new_amm_pool_state)?;
523523
Ok(())
524524
}
525+
526+
#[cfg(test)]
527+
mod test {
528+
use crate::{
529+
state::{
530+
amm::{AmmPair, PoolState, apply_burn, apply_mint},
531+
test::fresh_state,
532+
},
533+
types::{
534+
Address, AssetId, BitAssetId, FilledOutput, FilledOutputContent,
535+
FilledTransaction, OutPoint, Output, OutputContent, Transaction,
536+
TxData, Txid,
537+
},
538+
};
539+
540+
fn bitasset(byte: u8) -> BitAssetId {
541+
BitAssetId([byte; blake3::OUT_LEN])
542+
}
543+
544+
fn txid(byte: u8) -> Txid {
545+
Txid([byte; blake3::OUT_LEN])
546+
}
547+
548+
fn outpoint(byte: u8, vout: u32) -> OutPoint {
549+
OutPoint::Regular {
550+
txid: txid(byte),
551+
vout,
552+
}
553+
}
554+
555+
fn bitasset_output(asset: BitAssetId, value: u64) -> FilledOutput {
556+
Output::new(
557+
Address::ALL_ZEROS,
558+
FilledOutputContent::BitAsset(asset, value),
559+
)
560+
}
561+
562+
fn lp_output(
563+
asset0: AssetId,
564+
asset1: AssetId,
565+
amount: u64,
566+
) -> FilledOutput {
567+
Output::new(
568+
Address::ALL_ZEROS,
569+
FilledOutputContent::AmmLpToken {
570+
asset0,
571+
asset1,
572+
amount,
573+
},
574+
)
575+
}
576+
577+
#[test]
578+
fn apply_mint_burn_wrong_lp_baseline() -> anyhow::Result<()> {
579+
let (env, state) = fresh_state("apply_mint_burn_wrong_lp_baseline")?;
580+
581+
let asset_a = bitasset(1);
582+
let asset_b = bitasset(2);
583+
let asset0 = AssetId::BitAsset(asset_a);
584+
let asset1 = AssetId::BitAsset(asset_b);
585+
let pair = AmmPair::new(asset0, asset1);
586+
587+
let initial_pool = PoolState {
588+
reserve0: 1_000_000,
589+
reserve1: 1_000_000,
590+
outstanding_lp_tokens: 1_000_000,
591+
creation_txid: txid(9),
592+
};
593+
{
594+
let mut rwtxn = env.write_txn()?;
595+
state.amm_pools.put(&mut rwtxn, &pair, &initial_pool)?;
596+
rwtxn.commit()?;
597+
}
598+
599+
let correct_after_mint = initial_pool.mint(2, 2)?;
600+
let actual_lp_tokens_for_deposit = correct_after_mint
601+
.outstanding_lp_tokens
602+
- initial_pool.outstanding_lp_tokens;
603+
anyhow::ensure!(actual_lp_tokens_for_deposit == 2);
604+
605+
let mint_tx =
606+
|lp_token_mint: u64| -> anyhow::Result<FilledTransaction> {
607+
let res = FilledTransaction {
608+
transaction: Transaction {
609+
inputs: vec![outpoint(10, 0), outpoint(11, 0)],
610+
outputs: vec![Output::new(
611+
Address::ALL_ZEROS,
612+
OutputContent::AmmLpToken(lp_token_mint),
613+
)],
614+
memo: Vec::new(),
615+
data: Some(TxData::AmmMint {
616+
amount0: 2,
617+
amount1: 2,
618+
lp_token_mint,
619+
}),
620+
},
621+
spent_utxos: vec![
622+
bitasset_output(asset_a, 2),
623+
bitasset_output(asset_b, 2),
624+
],
625+
};
626+
let filled_mint_outputs = res
627+
.filled_outputs()
628+
.ok_or_else(|| anyhow::anyhow!("AMM LP output fills"))?;
629+
anyhow::ensure!(
630+
filled_mint_outputs[0].content()
631+
== &FilledOutputContent::AmmLpToken {
632+
asset0,
633+
asset1,
634+
amount: lp_token_mint,
635+
}
636+
);
637+
Ok(res)
638+
};
639+
640+
// Attempting to apply a mint with incorrect declared lp_tokens should
641+
// fail
642+
{
643+
let attacker_declared_lp = 500_001;
644+
let mint_tx = mint_tx(attacker_declared_lp)?;
645+
let mut rwtxn = env.write_txn()?;
646+
anyhow::ensure!(
647+
apply_mint(&state.amm_pools, &mut rwtxn, &mint_tx).is_err()
648+
);
649+
}
650+
// Attempting to apply a mint with correctly declared lp_tokens should
651+
// succeed
652+
{
653+
let mint_tx = mint_tx(actual_lp_tokens_for_deposit)?;
654+
let mut rwtxn = env.write_txn()?;
655+
let () = apply_mint(&state.amm_pools, &mut rwtxn, &mint_tx)?;
656+
rwtxn.commit()?;
657+
}
658+
659+
let lp_token_burn = 500_001;
660+
let burn_tx = FilledTransaction {
661+
transaction: Transaction {
662+
inputs: vec![outpoint(12, 0)],
663+
outputs: vec![
664+
Output::new(
665+
Address::ALL_ZEROS,
666+
OutputContent::BitAsset(500_001),
667+
),
668+
Output::new(
669+
Address::ALL_ZEROS,
670+
OutputContent::BitAsset(500_001),
671+
),
672+
],
673+
memo: Vec::new(),
674+
data: Some(TxData::AmmBurn {
675+
amount0: 500_001,
676+
amount1: 500_001,
677+
lp_token_burn,
678+
}),
679+
},
680+
spent_utxos: vec![lp_output(asset0, asset1, lp_token_burn)],
681+
};
682+
683+
{
684+
let mut rwtxn = env.write_txn()?;
685+
let () = apply_burn(&state.amm_pools, &mut rwtxn, &burn_tx)?;
686+
rwtxn.commit()?;
687+
}
688+
let pool_after_burn = {
689+
let rotxn = env.read_txn()?;
690+
state.amm_pools.get(&rotxn, &pair)?
691+
};
692+
assert_eq!(pool_after_burn.reserve0, 500_001);
693+
assert_eq!(pool_after_burn.reserve1, 500_001);
694+
assert_eq!(pool_after_burn.outstanding_lp_tokens, 500_001);
695+
696+
// cleanup
697+
{
698+
drop(state);
699+
let path = env.path().clone();
700+
drop(env);
701+
std::fs::remove_dir_all(path)?;
702+
}
703+
Ok(())
704+
}
705+
}

0 commit comments

Comments
 (0)