import { Signer } from "ethers" ; import { ethers } from "hardhat" ; import { expect } from "chai" ; import { simpleToExactAmount , BN } from "@utils/math" ; import { MassetDetails , MassetMachine , StandardAccounts } from "@utils/machines" ; import { Masset , MockERC20 } from "types/generated" ; import { fullScale , ratioScale , ZERO_ADDRESS } from "@utils/constants" ; import { assertBNSlightlyGTPercent , assertBasketIsHealthy } from "@utils/assertions" ; import { BassetStatus } from "@utils/mstable-objects" ; import { Account } from "types" ; describe ( "Masset - Swap" , ( ) => { let sa ; let mAssetMachine ; let details ; dev param seedBasket param useTransferFees const runSetup = async ( seedBasket = true , useTransferFees = false , useLendingMarkets = false , weights = [ 25 , 25 , 25 , 25 ] ) : Promise < void > => { details = await mAssetMachine . deployMasset ( useLendingMarkets , useTransferFees ) ; if ( seedBasket ) { await mAssetMachine . seedWithWeightings ( details , weights ) ; } } ; before ( "Init contract" , async ( ) => { const accounts = await ethers . getSigners ( ) ; mAssetMachine = await new MassetMachine ( ) . initAccounts ( accounts ) ; sa = mAssetMachine . sa ; await runSetup ( ) ; } ) ; dev param mAsset param inputBasset param outputAsset param inputQuantity param expectedReason param sender param recipient param callSwapOutput param swapOutputRevertExpected param quantitiesAreExact const assertFailedSwap = async ( mAssetContract , inputBasset , outputAsset , inputQuantity , minOutputQuantity , expectedReason , sender = sa . default . signer , recipient = sa . default . address , callSwapOutput = true , swapOutputRevertExpected = false , swapOutputExpected = 0 , quantitiesAreExact = false ) : Promise < void > => { const mAsset = mAssetContract . connect ( sender ) ; const approval = await mAssetMachine . approveMasset ( inputBasset , mAsset , inputQuantity , sa . default . signer , quantitiesAreExact ) ; const outputDecimals = await outputAsset . decimals ( ) ; const minOutputQuantityExact = quantitiesAreExact ? BN . from ( minOutputQuantity ) : simpleToExactAmount ( minOutputQuantity , outputDecimals ) ; await expect ( mAsset . swap ( inputBasset . address , outputAsset . address , approval , minOutputQuantityExact , recipient ) , `swap tx should revert with "${ expectedReason }"` ) . to . be . revertedWith ( expectedReason ) ; if ( callSwapOutput ) { if ( swapOutputRevertExpected ) { await expect ( mAsset . getSwapOutput ( inputBasset . address , outputAsset . address , approval ) , `getSwapOutput call should revert with "${ expectedReason }"` ) . to . be . revertedWith ( expectedReason ) ; } else { const swapOutputExpectedExact = quantitiesAreExact ? BN . from ( swapOutputExpected ) : simpleToExactAmount ( swapOutputExpected , outputDecimals ) ; const output = await mAsset . getSwapOutput ( inputBasset . address , outputAsset . address , approval ) ; expect ( output , "getSwapOutput call output" ) . eq ( swapOutputExpectedExact ) ; } } } ; dev param md param inputBasset param outputAsset param inputQuantity param expectSwapFee param recipient param sender param ignoreHealthAssertions param swapQuantityIsExact const assertSwap = async ( md , inputBasset , outputAsset , inputQuantity , recipient = sa . default . address , sender = sa . default , ignoreHealthAssertions = false , swapQuantityIsExact = false , minOutputQuantity = 1 ) : Promise < BN > => { const { platform } = md ; const mAsset = md . mAsset . connect ( sender . signer ) ; if ( ! ignoreHealthAssertions ) await assertBasketIsHealthy ( mAssetMachine , md ) ; const swapperInputBalBefore = await inputBasset . balanceOf ( sender . address ) ; const recipientOutputBalBefore = await outputAsset . balanceOf ( recipient ) ; const inputBassetBefore = await mAssetMachine . getBasset ( details , inputBasset . address ) ; const outputBassetBefore = await mAssetMachine . getBasset ( details , outputAsset . address ) ; const { surplus : surplusBefore } = await mAsset . data ( ) ; const approval0 = await mAssetMachine . approveMasset ( inputBasset , mAsset , BN . from ( inputQuantity ) , sender . signer , swapQuantityIsExact ) ; const expectedOutputValue = await mAsset . getSwapOutput ( inputBasset . address , outputAsset . address , approval0 ) ; const inputQuantityExact = approval0 ; const platformInteractionIn = await MassetMachine . getPlatformInteraction ( mAsset , "deposit" , approval0 , inputBassetBefore ) ; const platformInteractionOut = await MassetMachine . getPlatformInteraction ( mAsset , "withdrawal" , expectedOutputValue , outputBassetBefore ) ; const swapTx = mAsset . swap ( inputBasset . address , outputAsset . address , approval0 , minOutputQuantity , recipient ) ; await expect ( swapTx ) . to . emit ( mAsset , "Swapped" ) ; const { events } = await ( await swapTx ) . wait ( ) ; const swappedEvent = events . find ( ( e ) => e . event === "Swapped" ) ; expect ( swappedEvent . args [ 0 ] ) . to . eq ( sender . address ) ; expect ( swappedEvent . args [ 1 ] ) . to . eq ( inputBasset . address ) ; expect ( swappedEvent . args [ 2 ] ) . to . eq ( outputAsset . address ) ; expect ( swappedEvent . args [ 3 ] ) . to . eq ( expectedOutputValue ) ; const scaledFee = swappedEvent . args [ 4 ] ; expect ( swappedEvent . args [ 5 ] ) . to . eq ( recipient ) ; await expect ( swapTx , "Transfer event for input bAsset from sender to platform integration or mAsset" ) . to . emit ( inputBasset , "Transfer" ) . withArgs ( sender . address , inputBassetBefore . integrator ? inputBassetBefore . integratorAddr : mAsset . address , inputQuantityExact ) ; await expect ( swapTx , "Transfer event for output bAsset from platform integration or mAsset to recipient" ) . to . emit ( outputAsset , "Transfer" ) . withArgs ( outputBassetBefore . integrator ? outputBassetBefore . integratorAddr : mAsset . address , recipient , expectedOutputValue ) ; await swapTx ; const inputIntegratorBalAfter = await inputBassetBefore . contract . balanceOf ( inputBassetBefore . integrator ? inputBassetBefore . integratorAddr : mAsset . address ) ; expect ( inputIntegratorBalAfter , "Input destination raw balance" ) . eq ( platformInteractionIn . rawBalance ) ; const outputIntegratorBalAfter = await outputBassetBefore . contract . balanceOf ( outputBassetBefore . integrator ? outputBassetBefore . integratorAddr : mAsset . address ) ; expect ( outputIntegratorBalAfter , "Output source raw balance" ) . eq ( platformInteractionOut . rawBalance ) ; if ( platformInteractionIn . expectInteraction ) { await expect ( swapTx ) . to . emit ( platform , "Deposit" ) . withArgs ( inputBasset . address , inputBassetBefore . pToken , platformInteractionIn . amount ) ; } const swapperBassetBalAfter = await inputBasset . balanceOf ( sender . address ) ; expect ( swapperBassetBalAfter , "swapperBassetBalAfter incorrect" ) . eq ( swapperInputBalBefore . sub ( inputQuantityExact ) ) ; const inputBassetAfter = await mAsset . getBasset ( inputBasset . address ) ; expect ( BN . from ( inputBassetAfter . bData . vaultBalance ) , "inputBassetAfter incorrect" ) . eq ( BN . from ( inputBassetBefore . vaultBalance ) . add ( inputQuantityExact ) ) ; if ( platformInteractionOut . expectInteraction ) { await expect ( swapTx ) . to . emit ( platform , "PlatformWithdrawal" ) . withArgs ( outputAsset . address , outputBassetBefore . pToken , platformInteractionOut . amount , expectedOutputValue ) ; } else if ( platformInteractionOut . hasLendingMarket ) { await expect ( swapTx ) . to . emit ( platform , "Withdrawal" ) . withArgs ( outputAsset . address , ZERO_ADDRESS , expectedOutputValue ) ; } const recipientBalAfter = await outputAsset . balanceOf ( recipient ) ; expect ( recipientBalAfter , "recipientBalAfter incorrect" ) . eq ( recipientOutputBalBefore . add ( expectedOutputValue ) ) ; expect ( expectedOutputValue , "expectedOutputValue incorrect" ) . eq ( recipientBalAfter . sub ( recipientOutputBalBefore ) ) ; const outputBassetAfter = await mAsset . getBasset ( outputAsset . address ) ; expect ( BN . from ( outputBassetAfter . bData . vaultBalance ) , "outputBassetAfter incorrect" ) . eq ( BN . from ( outputBassetBefore . vaultBalance ) . sub ( expectedOutputValue ) ) ; const { surplus : surplusAfter } = await mAsset . data ( ) ; expect ( BN . from ( surplusAfter ) , "surplusAfter incorrect" ) . eq ( BN . from ( surplusBefore ) . add ( scaledFee ) ) ; if ( ! ignoreHealthAssertions ) await assertBasketIsHealthy ( mAssetMachine , md ) ; return expectedOutputValue ; } ; describe ( "swapping assets" , ( ) => { context ( "when within the invariant validator limits" , ( ) => { context ( "and different quantities" , ( ) => { before ( async ( ) => { await runSetup ( ) ; } ) ; const testQuantities = [ 1 , 10 , 4 ] ; testQuantities . forEach ( ( qty ) => { it ( `should swap using ${ qty } quantity` , async ( ) => { const { bAssets } = details ; await assertSwap ( details , bAssets [ 1 ] , bAssets [ 0 ] , qty ) ; } ) ; } ) ; } ) ; it ( "should fail if min qty < output qty" , async ( ) => { const { bAssets , mAsset } = details ; await assertFailedSwap ( mAsset , bAssets [ 2 ] , bAssets [ 1 ] , simpleToExactAmount ( 5 , 12 ) , simpleToExactAmount ( 4.986727 , 6 ) , "Output qty < minimum qty" , sa . default . signer , sa . default . address , false , false , undefined , true ) ; } ) ; it ( "should swap with min qty same as qty less swap fee" , async ( ) => { const { bAssets , mAsset } = details ; const inputBasset = bAssets [ 2 ] ; const outputBasset = bAssets [ 1 ] ; const inputQty = simpleToExactAmount ( 5 , 12 ) ; const expectedOutputQty = simpleToExactAmount ( 4.986726 , 6 ) ; const outputQty = await mAsset . getSwapOutput ( inputBasset . address , outputBasset . address , inputQty ) ; expect ( outputQty , "incorrect swap output quantity" ) . to . eq ( expectedOutputQty ) ; await assertSwap ( details , inputBasset , outputBasset , inputQty , undefined , undefined , undefined , true , expectedOutputQty ) ; } ) ; it ( "should swap using a different recipient to the sender" , async ( ) => { const { bAssets } = details ; await assertSwap ( details , bAssets [ 0 ] , bAssets [ 1 ] , 2 , sa . dummy1 . address ) ; } ) ; it ( "should swap out asset when recipient is a contract" , async ( ) => { const { bAssets } = details ; await assertSwap ( details , bAssets [ 0 ] , bAssets [ 1 ] , 3 , details . managerLib . address ) ; } ) ; context ( "when bAssets have different decimals" , ( ) => { it ( "should swap 6 decimals bAsset for 12 decimal bAsset" , async ( ) => { const { bAssets } = details ; await assertSwap ( details , bAssets [ 1 ] , bAssets [ 2 ] , 10 , details . managerLib . address ) ; } ) ; it ( "should swap 12 decimals bAsset for 6 decimal bAsset" , async ( ) => { const { bAssets } = details ; await assertSwap ( details , bAssets [ 2 ] , bAssets [ 1 ] , 10 , details . managerLib . address ) ; } ) ; it ( "should swap 6 decimals bAsset for 18 decimal bAsset" , async ( ) => { const { bAssets } = details ; await assertSwap ( details , bAssets [ 1 ] , bAssets [ 3 ] , 10 , details . managerLib . address ) ; } ) ; it ( "should swap 18 decimals bAsset for 18 decimal bAsset" , async ( ) => { const { bAssets } = details ; await assertSwap ( details , bAssets [ 0 ] , bAssets [ 3 ] , 10 , details . managerLib . address ) ; } ) ; } ) ; context ( "and specifying one bAsset base unit" , async ( ) => { before ( async ( ) => { await runSetup ( ) ; } ) ; it ( "should fail if output has less decimals" , async ( ) => { const { bAssets , mAsset } = details ; const input = bAssets [ 0 ] ; const output = bAssets [ 1 ] ; expect ( await input . decimals ( ) ) . eq ( 18 ) ; expect ( await output . decimals ( ) ) . eq ( 6 ) ; await assertFailedSwap ( mAsset , input , output , 1 , 0 , "Zero swap output" , sa . default . signer , sa . default . address , false , true , undefined , true ) ; } ) ; it ( "should swap a higher q of bAsset base units if output has more decimals" , async ( ) => { const { bAssets } = details ; const input = bAssets [ 1 ] ; const output = bAssets [ 0 ] ; expect ( await input . decimals ( ) ) . eq ( 6 ) ; expect ( await output . decimals ( ) ) . eq ( 18 ) ; await assertSwap ( details , input , output , 1 , undefined , undefined , false , true ) ; } ) ; } ) ; context ( "passing invalid arguments" , async ( ) => { before ( async ( ) => { await runSetup ( ) ; } ) ; it ( "should fail if identical bAssets" , async ( ) => { const { bAssets , mAsset } = details ; const input = bAssets [ 0 ] ; const output = input ; await assertFailedSwap ( mAsset , input , output , 1 , 0 , "Invalid pair" , sa . default . signer , sa . default . address , true , true ) ; } ) ; it ( "should fail when 0 quantity" , async ( ) => { const { bAssets , mAsset } = details ; await assertFailedSwap ( mAsset , bAssets [ 0 ] , bAssets [ 1 ] , 0 , 0 , "Invalid swap quantity" , sa . default . signer , sa . default . address , true , true ) ; } ) ; it ( "should fail if recipient is 0x0" , async ( ) => { const { bAssets , mAsset } = details ; await assertFailedSwap ( mAsset , bAssets [ 0 ] , bAssets [ 1 ] , 1 , 0 , "Invalid recipient" , sa . default . signer , ZERO_ADDRESS , true , false , 0.999003 ) ; } ) ; it ( "should fail if sender doesn't have sufficient liquidity" , async ( ) => { const { bAssets , mAsset } = details ; await assertFailedSwap ( mAsset , bAssets [ 0 ] , bAssets [ 1 ] , 1 , 0 , "ERC20: transfer amount exceeds balance" , sa . dummy1 . signer , sa . dummy1 . address , true , false , 0.999003 ) ; } ) ; it ( "should fail if sender doesn't give approval" , async ( ) => { const { bAssets , mAsset } = details ; const input = bAssets [ 0 ] ; const sender = sa . dummy2 ; await input . transfer ( sender . address , 10000 ) ; expect ( await input . allowance ( sender . address , mAsset . address ) ) . eq ( 0 ) ; expect ( await input . balanceOf ( sender . address ) ) . eq ( 10000 ) ; await expect ( mAsset . connect ( sender . signer ) . swap ( input . address , bAssets [ 1 ] . address , 5 , 1 , sa . default . address ) ) . to . revertedWith ( "ERC20: transfer amount exceeds allowance" ) ; } ) ; it ( "should fail if *either* bAsset does not exist" , async ( ) => { const { bAssets , mAsset } = details ; const realBasset = bAssets [ 0 ] . address ; const fakeBasset = sa . dummy1 . address ; const recipient = sa . dummy2 . address ; const expectedReason = "Invalid asset" ; await expect ( mAsset . swap ( fakeBasset , realBasset , 1 , 0 , recipient ) ) . to . revertedWith ( expectedReason ) ; await expect ( mAsset . swap ( realBasset , fakeBasset , 1 , 0 , recipient ) ) . to . revertedWith ( expectedReason ) ; } ) ; it ( "should fail if *either* bAsset is ZERO" , async ( ) => { const { bAssets , mAsset } = details ; const realBasset = bAssets [ 0 ] ; const fakeBasset = ZERO_ADDRESS ; const recipient = sa . default . address ; const expectedReason = "Invalid asset" ; await expect ( mAsset . swap ( realBasset . address , fakeBasset , 1 , 0 , recipient ) ) . to . revertedWith ( expectedReason ) ; await expect ( mAsset . getSwapOutput ( realBasset . address , fakeBasset , 1 ) ) . to . revertedWith ( expectedReason ) ; await expect ( mAsset . swap ( fakeBasset , realBasset . address , 1 , 0 , recipient ) ) . to . revertedWith ( expectedReason ) ; await expect ( mAsset . getSwapOutput ( fakeBasset , realBasset . address , 1 ) ) . to . revertedWith ( expectedReason ) ; } ) ; it ( "should fail using the mAsset as the input asset" , async ( ) => { const { bAssets , mAsset } = details ; await assertFailedSwap ( mAsset , mAsset , bAssets [ 0 ] , 1 , 0 , "Invalid asset" , undefined , undefined , true , true ) ; } ) ; it ( "should fail using an input asset not in the basket" , async ( ) => { const { bAssets , mAsset } = details ; const invalidBasset = await mAssetMachine . loadBassetProxy ( "Wrapped ETH" , "WETH" , 18 ) ; await assertFailedSwap ( mAsset , invalidBasset , bAssets [ 0 ] , 1 , 0 , "Invalid asset" , undefined , undefined , true , true ) ; } ) ; } ) ; context ( "using bAssets with transfer fees" , async ( ) => { context ( "when no lending market" , async ( ) => { before ( async ( ) => { await runSetup ( true , true ) ; } ) ; context ( "and input has xfer fee" , ( ) => { it ( "should have lower input and proportionately lower output" , async ( ) => { const { mAsset , bAssets } = details ; const sender = sa . default . address ; const recipient = sa . default . address ; const inputBasset = bAssets [ 3 ] ; const outputAsset = bAssets [ 0 ] ; const swapQuantity = 1 ; await assertBasketIsHealthy ( mAssetMachine , details ) ; const swapperInputBalBefore = await inputBasset . balanceOf ( sender ) ; const recipientOutputBalBefore = await outputAsset . balanceOf ( recipient ) ; const inputBassetBefore = await mAsset . getBasset ( inputBasset . address ) ; const outputBassetBefore = await mAsset . getBasset ( outputAsset . address ) ; const approval0 = await mAssetMachine . approveMasset ( inputBasset , mAsset , swapQuantity , sa . default . signer ) ; await mAsset . swap ( inputBasset . address , outputAsset . address , approval0 , 0 , recipient ) ; const inputQuantityExact = simpleToExactAmount ( swapQuantity , await inputBasset . decimals ( ) ) ; const scaledInputQuantity = simpleToExactAmount ( swapQuantity , 18 ) ; const expectedOutputValue = scaledInputQuantity . mul ( ratioScale ) . div ( outputBassetBefore . bData . ratio ) ; const { swapFee : feeRate } = await mAsset . data ( ) ; const fee = expectedOutputValue . mul ( feeRate ) . div ( fullScale ) ; const swapperBassetBalAfter = await inputBasset . balanceOf ( sender ) ; expect ( swapperBassetBalAfter ) . eq ( swapperInputBalBefore . sub ( inputQuantityExact ) ) ; const inputBassetAfter = await mAsset . getBasset ( inputBasset . address ) ; assertBNSlightlyGTPercent ( inputQuantityExact , BN . from ( inputBassetAfter . bData . vaultBalance ) . sub ( inputBassetBefore . bData . vaultBalance ) , "0.3" , true ) ; const recipientBalAfter = await outputAsset . balanceOf ( recipient ) ; assertBNSlightlyGTPercent ( expectedOutputValue . sub ( fee ) , recipientBalAfter . sub ( recipientOutputBalBefore ) , "0.3" , true ) ; await assertBasketIsHealthy ( mAssetMachine , details ) ; } ) ; } ) ; } ) ; context ( "when a lending market integration" , async ( ) => { before ( async ( ) => { await runSetup ( true , true , true ) ; } ) ; context ( "and input has xfer fee" , ( ) => { it ( "should pass but return less output that input" , async ( ) => { const { mAsset , bAssets } = details ; const sender = sa . default . address ; const recipient = sa . default . address ; const inputBasset = bAssets [ 3 ] ; const outputAsset = bAssets [ 0 ] ; const swapQuantity = 1 ; await assertBasketIsHealthy ( mAssetMachine , details ) ; const swapperInputBalBefore = await inputBasset . balanceOf ( sender ) ; const recipientOutputBalBefore = await outputAsset . balanceOf ( recipient ) ; const inputBassetBefore = await mAsset . getBasset ( inputBasset . address ) ; const outputBassetBefore = await mAsset . getBasset ( outputAsset . address ) ; const approval0 = await mAssetMachine . approveMasset ( inputBasset , mAsset , swapQuantity , sa . default . signer ) ; await mAsset . swap ( inputBasset . address , outputAsset . address , approval0 , 0 , recipient ) ; const inputQuantityExact = simpleToExactAmount ( swapQuantity , await inputBasset . decimals ( ) ) ; const scaledInputQuantity = simpleToExactAmount ( swapQuantity , 18 ) ; const expectedOutputValue = scaledInputQuantity . mul ( ratioScale ) . div ( outputBassetBefore . bData . ratio ) ; const { swapFee : feeRate } = await mAsset . data ( ) ; const fee = expectedOutputValue . mul ( feeRate ) . div ( fullScale ) ; const swapperBassetBalAfter = await inputBasset . balanceOf ( sender ) ; expect ( swapperBassetBalAfter ) . eq ( swapperInputBalBefore . sub ( inputQuantityExact ) ) ; const inputBassetAfter = await mAsset . getBasset ( inputBasset . address ) ; assertBNSlightlyGTPercent ( inputQuantityExact , BN . from ( inputBassetAfter . bData . vaultBalance ) . sub ( inputBassetBefore . bData . vaultBalance ) , "0.3" , true ) ; const recipientBalAfter = await outputAsset . balanceOf ( recipient ) ; assertBNSlightlyGTPercent ( expectedOutputValue . sub ( fee ) , recipientBalAfter . sub ( recipientOutputBalBefore ) , "0.3" , true ) ; await assertBasketIsHealthy ( mAssetMachine , details ) ; } ) ; it ( "should fail if the system doesn't know about the fee" , async ( ) => { const { bAssets , mAsset } = details ; await mAsset . connect ( sa . governor . signer ) . setTransferFeesFlag ( bAssets [ 3 ] . address , false ) ; await assertFailedSwap ( mAsset , bAssets [ 3 ] , bAssets [ 0 ] , 1 , 0 , "Asset not fully transferred" , sa . default . signer , sa . default . address , false ) ; } ) ; } ) ; } ) ; describe ( "when output has xfer fee" , async ( ) => { before ( async ( ) => { await runSetup ( true , true ) ; } ) ; it ( "should have same input but lower physical output" , async ( ) => { const { mAsset , bAssets } = details ; const sender = sa . default . address ; const recipient = sa . default . address ; const inputBasset = bAssets [ 0 ] ; const outputAsset = bAssets [ 3 ] ; const swapQuantity = 1 ; await assertBasketIsHealthy ( mAssetMachine , details ) ; const swapperInputBalBefore = await inputBasset . balanceOf ( sender ) ; const recipientOutputBalBefore = await outputAsset . balanceOf ( recipient ) ; const inputBassetBefore = await mAsset . getBasset ( inputBasset . address ) ; const outputBassetBefore = await mAsset . getBasset ( outputAsset . address ) ; const approval0 = await mAssetMachine . approveMasset ( inputBasset , mAsset , swapQuantity , sa . default . signer ) ; await mAsset . swap ( inputBasset . address , outputAsset . address , approval0 , 0 , recipient ) ; const inputQuantityExact = simpleToExactAmount ( swapQuantity , await inputBasset . decimals ( ) ) ; const scaledInputQuantity = simpleToExactAmount ( swapQuantity , 18 ) ; const expectedOutputValue = scaledInputQuantity . mul ( ratioScale ) . div ( outputBassetBefore . bData . ratio ) ; const { swapFee : feeRate } = await mAsset . data ( ) ; const fee = expectedOutputValue . mul ( feeRate ) . div ( fullScale ) ; const swapperBassetBalAfter = await inputBasset . balanceOf ( sender ) ; expect ( swapperBassetBalAfter ) . eq ( swapperInputBalBefore . sub ( inputQuantityExact ) ) ; const inputBassetAfter = await mAsset . getBasset ( inputBasset . address ) ; expect ( BN . from ( inputBassetAfter . bData . vaultBalance ) ) . eq ( BN . from ( inputBassetBefore . bData . vaultBalance ) . add ( inputQuantityExact ) ) ; const recipientBalAfter = await outputAsset . balanceOf ( recipient ) ; assertBNSlightlyGTPercent ( expectedOutputValue . sub ( fee ) , recipientBalAfter . sub ( recipientOutputBalBefore ) , "0.3" , true ) ; await assertBasketIsHealthy ( mAssetMachine , details ) ; } ) ; it ( "should continue to pay out" , async ( ) => { const { bAssets , mAsset } = details ; const sender = sa . default . address ; const recipient = sa . dummy1 . address ; const inputBasset = bAssets [ 0 ] ; const outputAsset = bAssets [ 3 ] ; const swapQuantity = 1 ; await mAsset . connect ( sa . governor . signer ) . setTransferFeesFlag ( outputAsset . address , false ) ; const swapperInputBalBefore = await inputBasset . balanceOf ( sender ) ; const recipientOutputBalBefore = await outputAsset . balanceOf ( recipient ) ; const inputBassetBefore = await mAsset . getBasset ( inputBasset . address ) ; const outputBassetBefore = await mAsset . getBasset ( outputAsset . address ) ; const approval0 = await mAssetMachine . approveMasset ( inputBasset , mAsset , swapQuantity , sa . default . signer ) ; await mAsset . swap ( inputBasset . address , outputAsset . address , approval0 , 0 , recipient ) ; const inputQuantityExact = simpleToExactAmount ( swapQuantity , await inputBasset . decimals ( ) ) ; const scaledInputQuantity = simpleToExactAmount ( swapQuantity , 18 ) ; const expectedOutputValue = scaledInputQuantity . mul ( ratioScale ) . div ( outputBassetBefore . bData . ratio ) ; const { swapFee : feeRate } = await mAsset . data ( ) ; const fee = expectedOutputValue . mul ( feeRate ) . div ( fullScale ) ; const swapperBassetBalAfter = await inputBasset . balanceOf ( sender ) ; expect ( swapperBassetBalAfter ) . eq ( swapperInputBalBefore . sub ( inputQuantityExact ) ) ; const inputBassetAfter = await mAsset . getBasset ( inputBasset . address ) ; expect ( BN . from ( inputBassetAfter . bData . vaultBalance ) ) . eq ( BN . from ( inputBassetBefore . bData . vaultBalance ) . add ( inputQuantityExact ) ) ; const recipientBalAfter = await outputAsset . balanceOf ( recipient ) ; assertBNSlightlyGTPercent ( expectedOutputValue . sub ( fee ) , recipientBalAfter . sub ( recipientOutputBalBefore ) , "0.3" , true ) ; } ) ; } ) ; } ) ; context ( "with an affected bAsset" , async ( ) => { beforeEach ( async ( ) => { await runSetup ( ) ; } ) ; it ( "should fail if input basset has lost its peg" , async ( ) => { const { bAssets , mAsset } = details ; await assertBasketIsHealthy ( mAssetMachine , details ) ; const input = bAssets [ 0 ] ; const output = bAssets [ 1 ] ; await mAsset . connect ( sa . governor . signer ) . handlePegLoss ( input . address , true ) ; const inputBasset = await mAsset . getBasset ( input . address ) ; expect ( inputBasset . personal . status ) . to . eq ( BassetStatus . BrokenBelowPeg ) ; const outputBasset = await mAsset . getBasset ( output . address ) ; expect ( outputBasset . personal . status ) . to . eq ( BassetStatus . Normal ) ; await assertFailedSwap ( mAsset , input , output , 1 , 0 , "Unhealthy" , undefined , undefined , true , false , 0.999003 ) ; } ) ; it ( "should fail if output basset has lost its peg" , async ( ) => { const { bAssets , mAsset } = details ; await assertBasketIsHealthy ( mAssetMachine , details ) ; const input = bAssets [ 0 ] ; const output = bAssets [ 1 ] ; await mAsset . connect ( sa . governor . signer ) . handlePegLoss ( output . address , true ) ; const inputBasset = await mAsset . getBasset ( input . address ) ; expect ( inputBasset . personal . status ) . to . eq ( BassetStatus . Normal ) ; const outputBasset = await mAsset . getBasset ( output . address ) ; expect ( outputBasset . personal . status ) . to . eq ( BassetStatus . BrokenBelowPeg ) ; await assertFailedSwap ( mAsset , input , output , 1 , 0 , "Unhealthy" , undefined , undefined , true , false , 0.999003 ) ; } ) ; } ) ; } ) ; context ( "with assets connected to lending markets" , async ( ) => { before ( async ( ) => { await runSetup ( true , false , true ) ; } ) ; it ( "should deposit into and withdraw from the lending market" , async ( ) => { const { bAssets } = details ; const [ input , output ] = bAssets ; await mAssetMachine . seedWithWeightings ( details , [ 1 , 1 , 1 , 1 ] ) ; await assertSwap ( details , input , output , 7 ) ; await assertSwap ( details , input , output , 1 ) ; } ) ; } ) ; } ) ; } ) ;