@@ -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