import "module-alias/register" ; import { BigNumber } from "ethers" ; import { Address } from "@utils/types" ; import { Account } from "@utils/test/types" ; import { PerpV2 , PerpV2Mock , InvokeMock , SetToken } from "@utils/contracts" ; import { PerpV2BaseToken } from "@utils/contracts/perpV2" ; import DeployHelper from "@utils/deploys" ; import { ether , } from "@utils/index" ; import { getAccounts , getSystemFixture , getWaffleExpect , addSnapshotBeforeRestoreAfterEach , getPerpV2Fixture } from "@utils/test/index" ; import { PerpV2Fixture , SystemFixture } from "@utils/fixtures" ; import { MAX_UINT_256 , ZERO_BYTES , ADDRESS_ZERO , ZERO } from "@utils/constants" ; const expect = getWaffleExpect ( ) ; describe ( "PerpV2" , ( ) => { let owner ; let maker ; let otherTrader ; let deployer ; let perpLib ; let perpLibMock ; let invokeLibMock ; let setup ; let perpSetup ; let setToken ; let vETH ; before ( async ( ) => { [ owner , maker , otherTrader ] = await getAccounts ( ) ; deployer = new DeployHelper ( owner . wallet ) ; setup = getSystemFixture ( owner . address ) ; await setup . initialize ( ) ; perpLib = await deployer . libraries . deployPerpV2 ( ) ; perpLibMock = await deployer . mocks . deployPerpV2Mock ( "contracts/protocol/integration/lib/PerpV2.sol:PerpV2" , perpLib . address ) ; invokeLibMock = await deployer . mocks . deployInvokeMock ( ) ; await setup . controller . addModule ( perpLibMock . address ) ; await setup . controller . addModule ( invokeLibMock . address ) ; perpSetup = getPerpV2Fixture ( owner . address ) ; await perpSetup . initialize ( maker , otherTrader ) ; vETH = perpSetup . vETH ; await perpSetup . initializePoolWithLiquidityWide ( vETH , ether ( 1000 ) , ether ( 10000 ) ) ; setToken = await setup . createSetToken ( [ perpSetup . usdc . address ] , [ ether ( 1000 ) ] , [ setup . issuanceModule . address , perpLibMock . address , invokeLibMock . address ] ) ; await setup . issuanceModule . initialize ( setToken . address , ADDRESS_ZERO ) ; await invokeLibMock . initializeModuleOnSet ( setToken . address ) ; await perpLibMock . initializeModuleOnSet ( setToken . address ) ; await perpSetup . usdc . approve ( setup . issuanceModule . address , MAX_UINT_256 ) ; await setup . issuanceModule . issue ( setToken . address , ether ( 1 ) , owner . address ) ; } ) ; addSnapshotBeforeRestoreAfterEach ( ) ; describe ( "#getDepositCalldata" , async ( ) => { let subjectAsset ; let subjectAmountNotional ; let subjectVault ; beforeEach ( async ( ) => { subjectAsset = perpSetup . usdc . address ; subjectAmountNotional = ether ( 1 ) ; subjectVault = perpSetup . vault . address ; } ) ; async function subject ( ) { return await perpLibMock . testGetDepositCalldata ( subjectVault , subjectAsset , subjectAmountNotional ) ; } it ( "should get correct data" , async ( ) => { const [ target , value , calldata ] = await subject ( ) ; const expectedCalldata = perpSetup . vault . interface . encodeFunctionData ( "deposit" , [ subjectAsset , subjectAmountNotional ] ) ; expect ( target ) . to . eq ( subjectVault ) ; expect ( value ) . to . eq ( ZERO ) ; expect ( calldata ) . to . eq ( expectedCalldata ) ; } ) ; } ) ; describe ( "#invokeDeposit" , async ( ) => { let subjectSetToken ; let subjectAsset ; let subjectAmountNotional ; let subjectVault ; beforeEach ( async ( ) => { await invokeLibMock . testInvokeApprove ( setToken . address , perpSetup . usdc . address , perpSetup . vault . address , MAX_UINT_256 ) ; subjectSetToken = setToken . address ; subjectAsset = perpSetup . usdc . address ; subjectAmountNotional = ether ( 1 ) ; subjectVault = perpSetup . vault . address ; } ) ; async function subject ( ) { return await perpLibMock . testInvokeDeposit ( subjectSetToken , subjectVault , subjectAsset , subjectAmountNotional ) ; } it ( "should create a USDC collateral balance" , async ( ) => { const previousCollateralBalance = await perpSetup . vault . getBalance ( setToken . address ) ; await subject ( ) ; const currentCollateralBalance = await perpSetup . vault . getBalance ( setToken . address ) ; const expectedCollateralBalance = previousCollateralBalance . add ( subjectAmountNotional ) ; expect ( currentCollateralBalance ) . to . eq ( expectedCollateralBalance ) ; } ) ; } ) ; describe ( "#getWithdrawCalldata" , async ( ) => { let subjectAsset ; let subjectAmountNotional ; let subjectVault ; beforeEach ( async ( ) => { subjectAsset = perpSetup . usdc . address ; subjectAmountNotional = ether ( 1 ) ; subjectVault = perpSetup . vault . address ; } ) ; async function subject ( ) { return await perpLibMock . testGetWithdrawCalldata ( subjectVault , subjectAsset , subjectAmountNotional ) ; } it ( "should get correct data" , async ( ) => { const [ target , value , calldata ] = await subject ( ) ; const expectedCalldata = perpSetup . vault . interface . encodeFunctionData ( "withdraw" , [ subjectAsset , subjectAmountNotional ] ) ; expect ( target ) . to . eq ( subjectVault ) ; expect ( value ) . to . eq ( ZERO ) ; expect ( calldata ) . to . eq ( expectedCalldata ) ; } ) ; } ) ; describe ( "#invokeWithdraw" , async ( ) => { let subjectSetToken ; let subjectAsset ; let subjectAmountNotional ; let subjectVault ; beforeEach ( async ( ) => { await invokeLibMock . testInvokeApprove ( setToken . address , perpSetup . usdc . address , perpSetup . vault . address , MAX_UINT_256 ) ; await perpLibMock . testInvokeDeposit ( setToken . address , perpSetup . vault . address , perpSetup . usdc . address , ether ( 1 ) ) ; subjectSetToken = setToken . address ; subjectAsset = perpSetup . usdc . address ; subjectAmountNotional = ether ( 1 ) ; subjectVault = perpSetup . vault . address ; } ) ; async function subject ( ) { return await perpLibMock . testInvokeWithdraw ( subjectSetToken , subjectVault , subjectAsset , subjectAmountNotional ) ; } it ( "should withdraw USDC collateral and return USDC" , async ( ) => { const previousCollateralBalance = await perpSetup . vault . getBalance ( setToken . address ) ; const previousUSDCBalance = await perpSetup . usdc . balanceOf ( setToken . address ) ; await subject ( ) ; const currentCollateralBalance = await perpSetup . vault . getBalance ( setToken . address ) ; const currentUSDCBalance = await perpSetup . usdc . balanceOf ( setToken . address ) ; const expectedCollateralBalance = previousCollateralBalance . sub ( subjectAmountNotional ) ; const expectedUSDCBalance = previousUSDCBalance . add ( subjectAmountNotional ) ; expect ( currentCollateralBalance ) . to . eq ( expectedCollateralBalance ) ; expect ( currentUSDCBalance ) . to . eq ( expectedUSDCBalance ) ; } ) ; } ) ; describe ( "#getOpenPositionCalldata" , async ( ) => { let subjectClearingHouse ; let subjectVETH ; let subjectIsBaseToQuote ; let subjectIsExactInput ; let subjectTradeQuoteAmount ; let subjectOppositeAmountBound ; let subjectDeadline ; let subjectSqrtPriceLimitX96 ; let subjectReferralCode ; beforeEach ( async ( ) => { subjectClearingHouse = perpSetup . clearingHouse . address ; subjectVETH = vETH ; subjectIsBaseToQuote = false ; subjectIsExactInput = true ; subjectTradeQuoteAmount = ether ( 1 ) ; subjectOppositeAmountBound = ZERO ; subjectDeadline = MAX_UINT_256 ; subjectSqrtPriceLimitX96 = ZERO ; subjectReferralCode = ZERO_BYTES ; } ) ; async function subject ( ) { return await perpLibMock . testGetOpenPositionCalldata ( subjectClearingHouse , { baseToken : subjectVETH . address , isBaseToQuote : subjectIsBaseToQuote , isExactInput : subjectIsExactInput , amount : subjectTradeQuoteAmount , oppositeAmountBound : subjectOppositeAmountBound , deadline : subjectDeadline , sqrtPriceLimitX96 : subjectSqrtPriceLimitX96 , referralCode : subjectReferralCode } ) ; } it ( "should get correct data" , async ( ) => { const [ target , value , calldata ] = await subject ( ) ; const expectedCalldata = perpSetup . clearingHouse . interface . encodeFunctionData ( "openPosition" , [ { baseToken : subjectVETH . address , isBaseToQuote : subjectIsBaseToQuote , isExactInput : subjectIsExactInput , amount : subjectTradeQuoteAmount , oppositeAmountBound : subjectOppositeAmountBound , deadline : subjectDeadline , sqrtPriceLimitX96 : subjectSqrtPriceLimitX96 , referralCode : subjectReferralCode } ] ) ; expect ( target ) . to . eq ( subjectClearingHouse ) ; expect ( value ) . to . eq ( ZERO ) ; expect ( calldata ) . to . eq ( expectedCalldata ) ; } ) ; } ) ; describe ( "#invokeOpenPosition" , async ( ) => { let subjectSetToken ; let subjectClearingHouse ; let subjectVETH ; let subjectIsBaseToQuote ; let subjectIsExactInput ; let subjectTradeQuoteAmount ; let subjectOppositeAmountBound ; let subjectDeadline ; let subjectSqrtPriceLimitX96 ; let subjectReferralCode ; beforeEach ( async ( ) => { await invokeLibMock . testInvokeApprove ( setToken . address , perpSetup . usdc . address , perpSetup . vault . address , MAX_UINT_256 ) ; await perpLibMock . testInvokeDeposit ( setToken . address , perpSetup . vault . address , perpSetup . usdc . address , ether ( 1 ) ) ; subjectSetToken = setToken . address ; subjectClearingHouse = perpSetup . clearingHouse . address ; subjectVETH = vETH ; subjectIsBaseToQuote = false ; subjectIsExactInput = true ; subjectTradeQuoteAmount = ether ( 1 ) ; subjectOppositeAmountBound = ZERO ; subjectDeadline = MAX_UINT_256 ; subjectSqrtPriceLimitX96 = ZERO ; subjectReferralCode = ZERO_BYTES ; } ) ; async function subject ( ) { return await perpLibMock . testInvokeOpenPosition ( subjectSetToken , subjectClearingHouse , { baseToken : subjectVETH . address , isBaseToQuote : subjectIsBaseToQuote , isExactInput : subjectIsExactInput , amount : subjectTradeQuoteAmount , oppositeAmountBound : subjectOppositeAmountBound , deadline : subjectDeadline , sqrtPriceLimitX96 : subjectSqrtPriceLimitX96 , referralCode : subjectReferralCode } ) ; } it ( "should open a position" , async ( ) => { const previousBaseBalance = await perpSetup . accountBalance . getBase ( setToken . address , subjectVETH . address ) ; const previousQuoteBalance = await perpSetup . accountBalance . getQuote ( setToken . address , subjectVETH . address ) ; await subject ( ) ; const currentBaseBalance = await perpSetup . accountBalance . getBase ( setToken . address , subjectVETH . address ) ; const currentQuoteBalance = await perpSetup . accountBalance . getQuote ( setToken . address , subjectVETH . address ) ; const expectedQuoteBalance = previousQuoteBalance . sub ( subjectTradeQuoteAmount ) ; expect ( previousBaseBalance ) . to . eq ( 0 ) ; expect ( currentBaseBalance ) . to . be . gt ( previousBaseBalance ) ; expect ( currentQuoteBalance ) . to . eq ( expectedQuoteBalance ) ; } ) ; } ) ; describe ( "#getSwapCalldata" , async ( ) => { let subjectQuoter ; let subjectVETH ; let subjectIsBaseToQuote ; let subjectIsExactInput ; let subjectTradeQuoteAmount ; let subjectSqrtPriceLimitX96 ; beforeEach ( async ( ) => { subjectQuoter = perpSetup . clearingHouse . address ; subjectVETH = vETH ; subjectIsBaseToQuote = false ; subjectIsExactInput = true ; subjectTradeQuoteAmount = ether ( 1 ) ; subjectSqrtPriceLimitX96 = ZERO ; } ) ; async function subject ( ) { return await perpLibMock . testGetSwapCalldata ( subjectQuoter , { baseToken : subjectVETH . address , isBaseToQuote : subjectIsBaseToQuote , isExactInput : subjectIsExactInput , amount : subjectTradeQuoteAmount , sqrtPriceLimitX96 : subjectSqrtPriceLimitX96 , } ) ; } it ( "should get correct data" , async ( ) => { const [ target , value , calldata ] = await subject ( ) ; const expectedCalldata = perpSetup . quoter . interface . encodeFunctionData ( "swap" , [ { baseToken : subjectVETH . address , isBaseToQuote : subjectIsBaseToQuote , isExactInput : subjectIsExactInput , amount : subjectTradeQuoteAmount , sqrtPriceLimitX96 : subjectSqrtPriceLimitX96 , } ] ) ; expect ( target ) . to . eq ( subjectQuoter ) ; expect ( value ) . to . eq ( ZERO ) ; expect ( calldata ) . to . eq ( expectedCalldata ) ; } ) ; } ) ; describe ( "#invokeSwap" , async ( ) => { let subjectSetToken ; let subjectQuoter ; let subjectVETH ; let subjectIsBaseToQuote ; let subjectIsExactInput ; let subjectTradeQuoteAmount ; let subjectSqrtPriceLimitX96 ; beforeEach ( async ( ) => { await invokeLibMock . testInvokeApprove ( setToken . address , perpSetup . usdc . address , perpSetup . vault . address , MAX_UINT_256 ) ; await perpLibMock . testInvokeDeposit ( setToken . address , perpSetup . vault . address , perpSetup . usdc . address , ether ( 1 ) ) ; subjectSetToken = setToken . address ; subjectQuoter = perpSetup . quoter . address ; subjectVETH = vETH ; subjectIsBaseToQuote = false ; subjectIsExactInput = true ; subjectTradeQuoteAmount = ether ( 1 ) ; subjectSqrtPriceLimitX96 = ZERO ; } ) ; async function subject ( callStatic ) { const params = { baseToken : subjectVETH . address , isBaseToQuote : subjectIsBaseToQuote , isExactInput : subjectIsExactInput , amount : subjectTradeQuoteAmount , sqrtPriceLimitX96 : subjectSqrtPriceLimitX96 , } ; return ( callStatic ) ? await perpLibMock . callStatic . testInvokeSwap ( subjectSetToken , subjectQuoter , params ) : await perpLibMock . testInvokeSwap ( subjectSetToken , subjectQuoter , params ) ; } it ( "should return the same deltaBase & deltaQuote values as `openPosition`" , async ( ) => { const { deltaBase : expectedDeltaAvailableBase , deltaQuote : expectedDeltaAvailableQuote } = await perpLibMock . callStatic . testInvokeOpenPosition ( subjectSetToken , perpSetup . clearingHouse . address , { baseToken : subjectVETH . address , isBaseToQuote : subjectIsBaseToQuote , isExactInput : subjectIsExactInput , amount : subjectTradeQuoteAmount , oppositeAmountBound : ZERO , deadline : MAX_UINT_256 , sqrtPriceLimitX96 : ZERO , referralCode : ZERO_BYTES } ) ; const { deltaAvailableBase : quotedDeltaAvailableBase , deltaAvailableQuote : quotedDeltaAvailableQuote } = await subject ( true ) ; expect ( expectedDeltaAvailableBase ) . to . eq ( quotedDeltaAvailableBase ) ; expect ( expectedDeltaAvailableQuote ) . to . eq ( quotedDeltaAvailableQuote ) ; } ) ; it ( "should only simulate the trade" , async ( ) => { const previousQuoteBalance = await await perpSetup . accountBalance . getQuote ( setToken . address , subjectVETH . address ) ; await subject ( false ) ; const currentQuoteBalance = await perpSetup . accountBalance . getQuote ( setToken . address , subjectVETH . address ) ; expect ( currentQuoteBalance ) . to . eq ( previousQuoteBalance ) ; } ) ; } ) ; } ) ;