import { BigNumber , Signer , Wallet } from "ethers" ; import { MAX_UINT256 , asyncForEach , deployContractWithLibraries , getUserTokenBalance , getUserTokenBalances , } from "./testUtils" ; import { deployContract , solidity } from "ethereum-waffle" ; import { deployments } from "hardhat" ; import { GenericERC20 } from "../build/typechain/GenericERC20" ; import GenericERC20Artifact from "../build/artifacts/contracts/helper/GenericERC20.sol/GenericERC20.json" ; import { LPToken } from "../build/typechain/LPToken" ; import LPTokenArtifact from "../build/artifacts/contracts/LPToken.sol/LPToken.json" ; import { Swap } from "../build/typechain/Swap" ; import { MetaSwap } from "../build/typechain/MetaSwap" ; import MetaSwapArtifact from "../build/artifacts/contracts/meta/MetaSwap.sol/MetaSwap.json" ; import { MetaSwapUtils } from "../build/typechain/MetaSwapUtils" ; import MetaSwapUtilsArtifact from "../build/artifacts/contracts/meta/MetaSwapUtils.sol/MetaSwapUtils.json" ; import { MetaSwapDeposit } from "../build/typechain/MetaSwapDeposit" ; import MetaSwapDepositArtifact from "../build/artifacts/contracts/meta/MetaSwapDeposit.sol/MetaSwapDeposit.json" ; import chai from "chai" ; chai . use ( solidity ) ; const { expect } = chai ; describe ( "Meta-Swap with inflated baseVirtualPrice" , async ( ) => { let signers ; let baseSwap ; let metaSwap ; let metaSwapUtils ; let metaSwapDeposit ; let susd ; let dai ; let usdc ; let usdt ; let baseLPToken ; let metaLPToken ; let owner ; let user1 ; let user2 ; let ownerAddress ; let user1Address ; let user2Address ; const INITIAL_A_VALUE = 2000 ; const SWAP_FEE = 4e6 ; const ADMIN_FEE = 0 ; const LP_TOKEN_NAME = "Test LP Token Name" ; const LP_TOKEN_SYMBOL = "TESTLP" ; const AMOUNT = 1e17 ; const INFLATED_VP = "1013335719282759415" ; const setupTest = deployments . createFixture ( async ( { deployments , ethers } ) => { const { get } = deployments ; await deployments . fixture ( ) ; signers = await ethers . getSigners ( ) ; owner = signers [ 0 ] ; user1 = signers [ 1 ] ; user2 = signers [ 2 ] ; ownerAddress = await owner . getAddress ( ) ; user1Address = await user1 . getAddress ( ) ; user2Address = await user2 . getAddress ( ) ; baseSwap = await ethers . getContract ( "Swap" ) ; dai = await ethers . getContract ( "DAI" ) ; usdc = await ethers . getContract ( "USDC" ) ; usdt = await ethers . getContract ( "USDT" ) ; await baseSwap . initialize ( [ dai . address , usdc . address , usdt . address ] , [ 18 , 6 , 6 ] , LP_TOKEN_NAME , LP_TOKEN_SYMBOL , INITIAL_A_VALUE , SWAP_FEE , 0 , ( await get ( "LPToken" ) ) . address ) ; baseLPToken = ( await ethers . getContractAt ( GenericERC20Artifact . abi , ( await baseSwap . swapStorage ( ) ) . lpToken ) ) as GenericERC20 ; susd = ( await deployContract ( owner as Wallet , GenericERC20Artifact , [ "Synthetix USD" , "sUSD" , "18" , ] ) ) as GenericERC20 ; await asyncForEach ( [ ownerAddress , user1Address , user2Address ] , async ( address ) => { await dai . mint ( address , BigNumber . from ( 10 ) . pow ( 18 ) . mul ( 100000 ) ) ; await usdc . mint ( address , BigNumber . from ( 10 ) . pow ( 6 ) . mul ( 100000 ) ) ; await usdt . mint ( address , BigNumber . from ( 10 ) . pow ( 6 ) . mul ( 100000 ) ) ; await susd . mint ( address , BigNumber . from ( 10 ) . pow ( 18 ) . mul ( 100000 ) ) ; } ) ; metaSwapUtils = ( await deployContract ( owner , MetaSwapUtilsArtifact ) ) as MetaSwapUtils ; await metaSwapUtils . deployed ( ) ; metaSwap = ( await deployContractWithLibraries ( owner , MetaSwapArtifact , { SwapUtils : ( await get ( "SwapUtils" ) ) . address , MetaSwapUtils : ( await get ( "MetaSwapUtils" ) ) . address , AmplificationUtils : ( await get ( "AmplificationUtils" ) ) . address , } ) ) as MetaSwap ; await metaSwap . deployed ( ) ; await asyncForEach ( [ owner , user1 , user2 ] , async ( signer ) => { await susd . connect ( signer ) . approve ( metaSwap . address , MAX_UINT256 ) ; await dai . connect ( signer ) . approve ( metaSwap . address , MAX_UINT256 ) ; await usdc . connect ( signer ) . approve ( metaSwap . address , MAX_UINT256 ) ; await usdt . connect ( signer ) . approve ( metaSwap . address , MAX_UINT256 ) ; await dai . connect ( signer ) . approve ( baseSwap . address , MAX_UINT256 ) ; await usdc . connect ( signer ) . approve ( baseSwap . address , MAX_UINT256 ) ; await usdt . connect ( signer ) . approve ( baseSwap . address , MAX_UINT256 ) ; await baseLPToken . connect ( signer ) . approve ( metaSwap . address , MAX_UINT256 ) ; await baseSwap . connect ( signer ) . addLiquidity ( [ String ( 1e20 ) , String ( 1e8 ) , String ( 1e8 ) ] , 0 , MAX_UINT256 ) ; } ) ; for ( let index = 0 ; index < 100 ; index ++ ) { await baseSwap . connect ( user1 ) . swap ( 0 , 1 , String ( 1e20 ) , 0 , MAX_UINT256 ) ; await baseSwap . connect ( user1 ) . swap ( 1 , 2 , String ( 1e8 ) , 0 , MAX_UINT256 ) ; await baseSwap . connect ( user1 ) . swap ( 2 , 0 , String ( 1e8 ) , 0 , MAX_UINT256 ) ; } expect ( await baseSwap . getVirtualPrice ( ) ) . to . eq ( INFLATED_VP ) ; await metaSwap . initializeMetaSwap ( [ susd . address , baseLPToken . address ] , [ 18 , 18 ] , LP_TOKEN_NAME , LP_TOKEN_SYMBOL , INITIAL_A_VALUE , SWAP_FEE , ADMIN_FEE , ( await get ( "LPToken" ) ) . address , baseSwap . address ) ; metaLPToken = ( await ethers . getContractAt ( LPTokenArtifact . abi , ( await metaSwap . swapStorage ( ) ) . lpToken ) ) as LPToken ; await asyncForEach ( [ owner , user1 , user2 ] , async ( signer ) => { await metaLPToken . connect ( signer ) . approve ( metaSwap . address , MAX_UINT256 ) ; } ) ; metaSwapDeposit = ( await deployContract ( owner , MetaSwapDepositArtifact ) ) as MetaSwapDeposit ; await metaSwapDeposit . initialize ( baseSwap . address , metaSwap . address , metaLPToken . address ) ; await asyncForEach ( [ owner , user1 , user2 ] , async ( signer ) => { await asyncForEach ( [ susd , dai , usdc , usdt , metaLPToken ] , async ( token ) => { await token . connect ( signer ) . approve ( metaSwapDeposit . address , MAX_UINT256 ) ; } ) ; } ) ; await metaSwap . addLiquidity ( [ INFLATED_VP , String ( 1e18 ) ] , 0 , MAX_UINT256 ) ; expect ( await susd . balanceOf ( metaSwap . address ) ) . to . eq ( INFLATED_VP ) ; expect ( await baseLPToken . balanceOf ( metaSwap . address ) ) . to . eq ( String ( 1e18 ) ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . eq ( String ( 1e18 ) ) ; } ) ; beforeEach ( async ( ) => { await setupTest ( ) ; } ) ; describe ( "addLiquidity" , ( ) => { it ( "Virtual price doesn't decrease after depositing only susd" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . addLiquidity ( [ String ( AMOUNT ) , 0 ] , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after depositing only baseLPToken" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . addLiquidity ( [ 0 , String ( AMOUNT ) ] , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after depositing only susd via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . addLiquidity ( [ String ( AMOUNT ) , 0 , 0 , 0 ] , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after depositing only dai via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . addLiquidity ( [ 0 , String ( AMOUNT ) , 0 , 0 ] , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; } ) ; describe ( "removeLiquidity" , ( ) => { it ( "Virtual price doesn't decrease after removeLiquidity" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . removeLiquidity ( String ( AMOUNT ) , [ 0 , 0 ] , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after removeLiquidity via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . removeLiquidity ( String ( AMOUNT ) , [ 0 , 0 , 0 , 0 ] , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; } ) ; describe ( "removeLiquidityImbalance" , ( ) => { it ( "Virtual price doesn't decrease after removeLiquidityImbalance susd" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . removeLiquidityImbalance ( [ String ( AMOUNT ) , 0 ] , String ( 2 * AMOUNT ) , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after removeLiquidityImbalance baseLPToken" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . removeLiquidityImbalance ( [ 0 , String ( AMOUNT ) ] , String ( 2 * AMOUNT ) , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after removeLiquidityImbalance susd via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . removeLiquidityImbalance ( [ String ( AMOUNT ) , 0 , 0 , 0 ] , String ( 2 * AMOUNT ) , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after removeLiquidityImbalance dai via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . removeLiquidityImbalance ( [ 0 , String ( AMOUNT ) , 0 , 0 ] , String ( 2 * AMOUNT ) , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; } ) ; describe ( "removeLiquidityOneToken" , ( ) => { it ( "Virtual price doesn't decrease after removeLiquidityOneToken susd" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . removeLiquidityOneToken ( String ( AMOUNT ) , 0 , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after removeLiquidityOneToken baseLPToken" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . removeLiquidityOneToken ( String ( AMOUNT ) , 1 , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after removeLiquidityOneToken susd via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . removeLiquidityOneToken ( String ( AMOUNT ) , 0 , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after removeLiquidityOneToken dai via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . removeLiquidityOneToken ( String ( AMOUNT ) , 1 , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; } ) ; describe ( "swap" , ( ) => { it ( "Virtual price doesn't decrease after swap susd -> baseLPToken" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . swap ( 0 , 1 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after swap baseLPToken -> susd" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . swap ( 1 , 0 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "susd -> baseLPToken -> susd: outcome not great not terrible" , async ( ) => { const [ tokenFromBalanceBefore , tokenToBalanceBefore ] = await getUserTokenBalances ( user1 , [ susd , baseLPToken ] ) ; await metaSwap . connect ( user1 ) . swap ( 0 , 1 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; const tokenToBalanceAfterFirst = await getUserTokenBalance ( user1 , baseLPToken ) ; await metaSwap . connect ( user1 ) . swap ( 1 , 0 , String ( tokenToBalanceAfterFirst . sub ( tokenToBalanceBefore ) ) , 0 , MAX_UINT256 ) ; const [ tokenFromBalanceAfterSecond , tokenToBalanceAfterSecond ] = await getUserTokenBalances ( user1 , [ susd , baseLPToken ] ) ; expect ( tokenToBalanceBefore ) . to . eq ( tokenToBalanceAfterSecond ) ; expect ( tokenFromBalanceAfterSecond ) . to . lt ( tokenFromBalanceBefore ) ; const lossBP = tokenFromBalanceBefore . sub ( tokenFromBalanceAfterSecond ) . mul ( 10000 ) . div ( String ( AMOUNT ) ) ; expect ( lossBP ) . to . lt ( 10 ) ; } ) ; } ) ; describe ( "swapUnderlying" , ( ) => { it ( "Virtual price doesn't decrease after swapUnderlying susd -> dai" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . swapUnderlying ( 0 , 1 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after swapUnderlying dai -> susd" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwap . swapUnderlying ( 1 , 0 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after swap susd -> dai via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . swap ( 0 , 1 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "Virtual price doesn't decrease after swap dai -> susd via MetaSwapDeposit" , async ( ) => { const virtualPriceBefore = await metaSwap . getVirtualPrice ( ) ; await metaSwapDeposit . swap ( 1 , 0 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; expect ( await metaSwap . getVirtualPrice ( ) ) . to . gte ( virtualPriceBefore ) ; } ) ; it ( "susd -> dai -> susd: outcome not great not terrible" , async ( ) => { const [ tokenFromBalanceBefore , tokenToBalanceBefore ] = await getUserTokenBalances ( user1 , [ susd , dai ] ) ; await metaSwap . connect ( user1 ) . swapUnderlying ( 0 , 1 , String ( AMOUNT ) , 0 , MAX_UINT256 ) ; const tokenToBalanceAfterFirst = await getUserTokenBalance ( user1 , dai ) ; await metaSwap . connect ( user1 ) . swapUnderlying ( 1 , 0 , String ( tokenToBalanceAfterFirst . sub ( tokenToBalanceBefore ) ) , 0 , MAX_UINT256 ) ; const [ tokenFromBalanceAfterSecond , tokenToBalanceAfterSecond ] = await getUserTokenBalances ( user1 , [ susd , dai ] ) ; expect ( tokenToBalanceBefore ) . to . eq ( tokenToBalanceAfterSecond ) ; expect ( tokenFromBalanceAfterSecond ) . to . lt ( tokenFromBalanceBefore ) ; const lossBP = tokenFromBalanceBefore . sub ( tokenFromBalanceAfterSecond ) . mul ( 10000 ) . div ( String ( AMOUNT ) ) ; expect ( lossBP ) . to . lt ( 15 ) ; } ) ; } ) ; } ) ;