import { BigNumber , Signer } from "ethers" ; import { MAX_UINT256 , getUserTokenBalance , asyncForEach } from "./testUtils" ; import { solidity } from "ethereum-waffle" ; import { GenericERC20 } from "../build/typechain/GenericERC20" ; import { LPToken } from "../build/typechain/LPToken" ; import { FlashLoanBorrowerExample } from "../build/typechain/FlashLoanBorrowerExample" ; import { SwapFlashLoan } from "../build/typechain/SwapFlashLoan" ; import chai from "chai" ; import { deployments } from "hardhat" ; import { solidityPack } from "ethers/lib/utils" ; chai . use ( solidity ) ; const { expect } = chai ; describe ( "Swap Flashloan" , ( ) => { let signers ; let swapFlashLoan ; let flashLoanExample ; let DAI ; let USDC ; let USDT ; let SUSD ; let swapToken ; let owner ; let user1 ; let user2 ; let attacker ; let ownerAddress ; let user1Address ; let user2Address ; let swapStorage ; const INITIAL_A_VALUE = 50 ; const SWAP_FEE = 1e7 ; const LP_TOKEN_NAME = "Test LP Token Name" ; const LP_TOKEN_SYMBOL = "TESTLP" ; const TOKENS = [ ] ; const setupTest = deployments . createFixture ( async ( { deployments , ethers } ) => { const { get } = deployments ; await deployments . fixture ( ) ; TOKENS . length = 0 ; signers = await ethers . getSigners ( ) ; owner = signers [ 0 ] ; user1 = signers [ 1 ] ; user2 = signers [ 2 ] ; attacker = signers [ 10 ] ; ownerAddress = await owner . getAddress ( ) ; user1Address = await user1 . getAddress ( ) ; user2Address = await user2 . getAddress ( ) ; const erc20Factory = await ethers . getContractFactory ( "GenericERC20" ) ; DAI = await ethers . getContract ( "DAI" ) ; USDC = await ethers . getContract ( "USDC" ) ; USDT = await ethers . getContract ( "USDT" ) ; SUSD = await ethers . getContract ( "SUSD" ) ; TOKENS . push ( DAI , USDC , USDT , SUSD ) ; await asyncForEach ( [ ownerAddress , user1Address , user2Address , await attacker . getAddress ( ) ] , async ( address ) => { await DAI . mint ( address , String ( 1e20 ) ) ; await USDC . mint ( address , String ( 1e8 ) ) ; await USDT . mint ( address , String ( 1e8 ) ) ; await SUSD . mint ( address , String ( 1e20 ) ) ; } ) ; swapFlashLoan = await ethers . getContract ( "SwapFlashLoan" ) ; await swapFlashLoan . initialize ( [ DAI . address , USDC . address , USDT . address , SUSD . address ] , [ 18 , 6 , 6 , 18 ] , LP_TOKEN_NAME , LP_TOKEN_SYMBOL , INITIAL_A_VALUE , SWAP_FEE , 0 , ( await deployments . get ( "LPToken" ) ) . address ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . be . eq ( 0 ) ; swapStorage = await swapFlashLoan . swapStorage ( ) ; swapToken = ( await ethers . getContractAt ( "LPToken" , swapStorage . lpToken ) ) as LPToken ; await asyncForEach ( [ owner , user1 , user2 , attacker ] , async ( signer ) => { await DAI . connect ( signer ) . approve ( swapFlashLoan . address , MAX_UINT256 ) ; await USDC . connect ( signer ) . approve ( swapFlashLoan . address , MAX_UINT256 ) ; await USDT . connect ( signer ) . approve ( swapFlashLoan . address , MAX_UINT256 ) ; await SUSD . connect ( signer ) . approve ( swapFlashLoan . address , MAX_UINT256 ) ; } ) ; await swapFlashLoan . addLiquidity ( [ String ( 50e18 ) , String ( 50e6 ) , String ( 50e6 ) , String ( 50e18 ) ] , 0 , MAX_UINT256 ) ; expect ( await swapFlashLoan . getTokenBalance ( 0 ) ) . to . be . eq ( String ( 50e18 ) ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . be . eq ( String ( 50e6 ) ) ; expect ( await swapFlashLoan . getTokenBalance ( 2 ) ) . to . be . eq ( String ( 50e6 ) ) ; expect ( await swapFlashLoan . getTokenBalance ( 3 ) ) . to . be . eq ( String ( 50e18 ) ) ; expect ( await getUserTokenBalance ( owner , swapToken ) ) . to . be . eq ( String ( 200e18 ) ) ; const flashLoanExampleFactory = await ethers . getContractFactory ( "FlashLoanBorrowerExample" ) ; flashLoanExample = ( await flashLoanExampleFactory . deploy ( ) ) as FlashLoanBorrowerExample ; await swapFlashLoan . setFlashLoanFees ( 100 , 5000 ) ; } ) ; beforeEach ( async ( ) => { await setupTest ( ) ; } ) ; it ( "Reverts when the borrower does not have enough to pay back" , async ( ) => { await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , [ ] ) ) . to . be . revertedWith ( "ERC20: transfer amount exceeds balance" ) ; } ) ; it ( "Reverts when flashloan debt is not paid" , async ( ) => { await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , solidityPack ( [ "string" ] , [ "dontRepayDebt" ] ) ) ) . to . be . revertedWith ( "flashLoan fee is not met" ) ; } ) ; it ( "Reverts when calling re-entering swap contract via `addLiquidity`" , async ( ) => { await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , solidityPack ( [ "string" ] , [ "reentrancy_addLiquidity" ] ) ) ) . to . be . revertedWith ( "ReentrancyGuard: reentrant call" ) ; } ) ; it ( "Reverts when calling re-entering swap contract via `swap`" , async ( ) => { await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , solidityPack ( [ "string" ] , [ "reentrancy_swap" ] ) ) ) . to . be . revertedWith ( "ReentrancyGuard: reentrant call" ) ; } ) ; it ( "Reverts when calling re-entering swap contract via `removeLiquidity`" , async ( ) => { await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , solidityPack ( [ "string" ] , [ "reentrancy_removeLiquidity" ] ) ) ) . to . be . revertedWith ( "ReentrancyGuard: reentrant call" ) ; } ) ; it ( "Reverts when calling re-entering swap contract via `removeLiquidityOneToken`" , async ( ) => { await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , solidityPack ( [ "string" ] , [ "reentrancy_removeLiquidityOneToken" ] ) ) ) . to . be . revertedWith ( "ReentrancyGuard: reentrant call" ) ; } ) ; it ( "Succeeds when fee is paid off" , async ( ) => { const flashLoanAmount = BigNumber . from ( 1e6 ) ; const flashLoanFee = flashLoanAmount . mul ( await swapFlashLoan . flashLoanFeeBPS ( ) ) . div ( 10000 ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000000000000000000" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50000000" ) ; await USDC . connect ( user1 ) . transfer ( flashLoanExample . address , flashLoanFee ) ; await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , [ ] ) ) . to . emit ( swapFlashLoan , "FlashLoan" ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000024999981618719" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50005000" ) ; expect ( await swapFlashLoan . getAdminBalance ( 1 ) ) . to . eq ( "5000" ) ; expect ( await USDC . balanceOf ( swapFlashLoan . address ) ) . to . eq ( "50010000" ) ; await USDC . connect ( user1 ) . transfer ( flashLoanExample . address , flashLoanFee ) ; await expect ( flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , [ ] ) ) . to . emit ( swapFlashLoan , "FlashLoan" ) ; expect ( await USDC . balanceOf ( flashLoanExample . address ) ) . to . eq ( 0 ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000049999926479164" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50010000" ) ; expect ( await swapFlashLoan . getAdminBalance ( 1 ) ) . to . eq ( "10000" ) ; expect ( await USDC . balanceOf ( swapFlashLoan . address ) ) . to . eq ( "50020000" ) ; await swapFlashLoan . withdrawAdminFees ( ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000049999926479164" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50010000" ) ; expect ( await swapFlashLoan . getAdminBalance ( 1 ) ) . to . eq ( "0" ) ; expect ( await USDC . balanceOf ( swapFlashLoan . address ) ) . to . eq ( "50010000" ) ; } ) ; describe ( "setFlashLoanFees" , ( ) => { it ( "Reverts when called by non-owner" , async ( ) => { await expect ( swapFlashLoan . connect ( user1 ) . setFlashLoanFees ( 100 , 5000 ) ) . to . be . revertedWith ( "Ownable: caller is not the owner" ) ; } ) ; it ( "Reverts when fees are not in the range" , async ( ) => { await expect ( swapFlashLoan . setFlashLoanFees ( 0 , 5000 ) ) . to . be . revertedWith ( "fees are not in valid range" ) ; await expect ( swapFlashLoan . setFlashLoanFees ( 100000 , 5000 ) ) . to . be . revertedWith ( "fees are not in valid range" ) ; await expect ( swapFlashLoan . setFlashLoanFees ( 100 , 100000 ) ) . to . be . revertedWith ( "fees are not in valid range" ) ; await expect ( swapFlashLoan . setFlashLoanFees ( 100000 , 100000 ) ) . to . be . revertedWith ( "fees are not in valid range" ) ; } ) ; it ( "Succeeds when protocol fee bps is set to 0" , async ( ) => { const newFlashLoanFeeBPS = 8 ; const newProtocolFeeBPS = 0 ; await swapFlashLoan . setFlashLoanFees ( newFlashLoanFeeBPS , newProtocolFeeBPS ) ; expect ( await swapFlashLoan . flashLoanFeeBPS ( ) ) . to . eq ( newFlashLoanFeeBPS ) ; expect ( await swapFlashLoan . protocolFeeShareBPS ( ) ) . to . eq ( newProtocolFeeBPS ) ; const flashLoanAmount = BigNumber . from ( 1e6 ) ; const flashLoanFee = flashLoanAmount . mul ( newFlashLoanFeeBPS ) . div ( 10000 ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000000000000000000" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50000000" ) ; await USDC . connect ( user1 ) . transfer ( flashLoanExample . address , flashLoanFee ) ; await flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , [ ] ) ; expect ( await USDC . balanceOf ( flashLoanExample . address ) ) . to . eq ( 0 ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000003999999529416" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50000800" ) ; expect ( await swapFlashLoan . getAdminBalance ( 1 ) ) . to . eq ( "0" ) ; expect ( await USDC . balanceOf ( swapFlashLoan . address ) ) . to . eq ( "50000800" ) ; } ) ; it ( "Succeeds when fees are in the valid range" , async ( ) => { const newFlashLoanFeeBPS = 50 ; const newProtocolFeeBPS = 100 ; await swapFlashLoan . setFlashLoanFees ( newFlashLoanFeeBPS , newProtocolFeeBPS ) ; expect ( await swapFlashLoan . flashLoanFeeBPS ( ) ) . to . eq ( newFlashLoanFeeBPS ) ; expect ( await swapFlashLoan . protocolFeeShareBPS ( ) ) . to . eq ( newProtocolFeeBPS ) ; const flashLoanAmount = BigNumber . from ( 1e6 ) ; const flashLoanFee = flashLoanAmount . mul ( newFlashLoanFeeBPS ) . div ( 10000 ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000000000000000000" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50000000" ) ; await USDC . connect ( user1 ) . transfer ( flashLoanExample . address , flashLoanFee ) ; await flashLoanExample . flashLoan ( swapFlashLoan . address , USDC . address , 1e6 , [ ] ) ; expect ( await USDC . balanceOf ( flashLoanExample . address ) ) . to . eq ( 0 ) ; expect ( await swapFlashLoan . getVirtualPrice ( ) ) . to . eq ( "1000024749981984496" ) ; expect ( await swapFlashLoan . getTokenBalance ( 1 ) ) . to . eq ( "50004950" ) ; expect ( await swapFlashLoan . getAdminBalance ( 1 ) ) . to . eq ( "50" ) ; expect ( await USDC . balanceOf ( swapFlashLoan . address ) ) . to . eq ( "50005000" ) ; } ) ; } ) ; } ) ;