import { ethers } from "hardhat" ; import { expect } from "chai" ; import { assertBNSlightlyGT , assertBNSlightlyGTPercent , assertBNClose } from "@utils/assertions" ; import { simpleToExactAmount , BN } from "@utils/math" ; import { increaseTime } from "@utils/time" ; import { MassetMachine , StandardAccounts } from "@utils/machines" ; import { MAX_UINT256 , ZERO_ADDRESS , TEN_MINS , DEAD_ADDRESS } from "@utils/constants" ; import { MockNexus__factory , MockNexus , MockAaveV2__factory , AaveV2Integration , AaveV2Integration__factory , MockERC20__factory , MockATokenV2__factory , ILendingPoolAddressesProviderV2__factory , MockERC20 , MockATokenV2 , } from "types/generated" ; import { BassetIntegrationDetails , Account } from "types" ; import { shouldBehaveLikeModule , IModuleBehaviourContext } from "../../shared/Module.behaviour" ; describe ( "AaveIntegration" , async ( ) => { let sa ; let mAssetMachine ; let nexus ; let mAsset ; let integrationDetails ; let aaveIntegration ; const ctx = { } ; const runSetup = async ( enableUSDTFee = false , simulateMint = false , skipInit = false ) => { nexus = await new MockNexus__factory ( sa . default . signer ) . deploy ( sa . governor . address , sa . mockSavingsManager . address , sa . mockInterestValidator . address ) ; integrationDetails = await mAssetMachine . loadBassetsLocal ( true , enableUSDTFee , mAsset . address ) ; aaveIntegration = await new AaveV2Integration__factory ( sa . default . signer ) . deploy ( nexus . address , mAsset . address , integrationDetails . aavePlatformAddress , DEAD_ADDRESS ) ; if ( ! skipInit ) { await Promise . all ( integrationDetails . aTokens . map ( ( a ) => aaveIntegration . connect ( sa . governor . signer ) . setPTokenAddress ( a . bAsset , a . aToken ) ) ) ; if ( simulateMint ) { await Promise . all ( integrationDetails . aTokens . map ( async ( { bAsset } ) => { const b1 = await new MockERC20__factory ( mAsset . signer ) . attach ( bAsset ) ; const decimals = BN . from ( await b1 . decimals ( ) ) ; const amount = BN . from ( enableUSDTFee ? 101 : 100 ) . mul ( BN . from ( 10 ) . pow ( decimals . sub ( BN . from ( 1 ) ) ) ) ; const amountD = BN . from ( 100 ) . mul ( BN . from ( 10 ) . pow ( decimals . sub ( BN . from ( 1 ) ) ) ) ; await b1 . transfer ( aaveIntegration . address , amount . toString ( ) ) ; return aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset , amountD . toString ( ) , true ) ; } ) ) ; } } } ; before ( "Init contract" , async ( ) => { const accounts = await ethers . getSigners ( ) ; mAssetMachine = await new MassetMachine ( ) . initAccounts ( accounts ) ; sa = mAssetMachine . sa ; mAsset = sa . default ; await runSetup ( ) ; } ) ; describe ( "AaveIntegration constructor" , async ( ) => { describe ( "behave like a Module" , async ( ) => { beforeEach ( async ( ) => { await runSetup ( ) ; ctx . module = aaveIntegration ; ctx . sa = sa ; } ) ; shouldBehaveLikeModule ( ctx as IModuleBehaviourContext ) ; } ) ; it ( "should properly store valid arguments" , async ( ) => { expect ( await aaveIntegration . nexus ( ) ) . eq ( nexus . address ) ; expect ( await aaveIntegration . lpAddress ( ) ) . eq ( mAsset . address ) ; expect ( await aaveIntegration . platformAddress ( ) ) . eq ( integrationDetails . aavePlatformAddress ) ; expect ( integrationDetails . aTokens [ 0 ] . aToken ) . eq ( await aaveIntegration . bAssetToPToken ( integrationDetails . aTokens [ 0 ] . bAsset ) ) ; expect ( integrationDetails . aTokens [ 1 ] . aToken ) . eq ( await aaveIntegration . bAssetToPToken ( integrationDetails . aTokens [ 1 ] . bAsset ) ) ; } ) ; it ( "should fail when mAsset address invalid" , async ( ) => { await expect ( new AaveV2Integration__factory ( sa . default . signer ) . deploy ( nexus . address , ZERO_ADDRESS , sa . mockSavingsManager . address , DEAD_ADDRESS ) ) . to . be . revertedWith ( "Invalid LP address" ) ; } ) ; it ( "should approve spending of the passed bAssets" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const addressProvider = await ILendingPoolAddressesProviderV2__factory . connect ( integrationDetails . aavePlatformAddress , sa . default . signer ) ; const approvedAddress = await addressProvider . getLendingPool ( ) ; const balance = await bAsset . allowance ( aaveIntegration . address , approvedAddress ) ; expect ( balance ) . eq ( MAX_UINT256 ) ; } ) ; it ( "should fail if passed incorrect data" , async ( ) => { await expect ( new AaveV2Integration__factory ( sa . default . signer ) . deploy ( nexus . address , mAsset . address , ZERO_ADDRESS , DEAD_ADDRESS ) ) . to . be . revertedWith ( "Invalid platform address" ) ; } ) ; } ) ; describe ( "calling initialize" , async ( ) => { beforeEach ( async ( ) => { await runSetup ( false , false , true ) ; } ) ; it ( "should properly store valid arguments" , async ( ) => { const { aTokens } = integrationDetails ; await aaveIntegration . initialize ( [ aTokens [ 0 ] . bAsset ] , [ aTokens [ 0 ] . aToken ] ) ; expect ( integrationDetails . aTokens [ 0 ] . aToken ) . eq ( await aaveIntegration . bAssetToPToken ( integrationDetails . aTokens [ 0 ] . bAsset ) ) ; } ) ; it ( "should approve spending of the passed bAssets" , async ( ) => { const { aTokens } = integrationDetails ; await aaveIntegration . initialize ( [ aTokens [ 0 ] . bAsset ] , [ aTokens [ 0 ] . aToken ] ) ; const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( aTokens [ 0 ] . bAsset ) ; const addressProvider = await ILendingPoolAddressesProviderV2__factory . connect ( integrationDetails . aavePlatformAddress , sa . default . signer ) ; const approvedAddress = await addressProvider . getLendingPool ( ) ; const balance = await bAsset . allowance ( aaveIntegration . address , approvedAddress ) ; expect ( balance ) . eq ( MAX_UINT256 ) ; } ) ; it ( "should fail when called again" , async ( ) => { const { aTokens } = integrationDetails ; await aaveIntegration . initialize ( [ aTokens [ 0 ] . bAsset ] , [ aTokens [ 0 ] . aToken ] ) ; await expect ( aaveIntegration . initialize ( [ aTokens [ 0 ] . bAsset ] , [ aTokens [ 0 ] . aToken ] ) ) . to . be . revertedWith ( "Initializable: contract is already initialized" ) ; } ) ; it ( "should fail if passed incorrect data" , async ( ) => { const { aTokens } = integrationDetails ; await expect ( aaveIntegration . initialize ( [ aTokens [ 1 ] . bAsset , sa . dummy1 . address ] , [ aTokens [ 1 ] . aToken ] ) ) . to . be . revertedWith ( "Invalid inputs" ) ; await expect ( aaveIntegration . initialize ( [ aTokens [ 1 ] . bAsset ] , [ ZERO_ADDRESS ] ) ) . to . be . revertedWith ( "Invalid addresses" ) ; await expect ( aaveIntegration . initialize ( [ aTokens [ 0 ] . bAsset , aTokens [ 0 ] . bAsset ] , [ aTokens [ 1 ] . aToken , aTokens [ 1 ] . aToken ] ) ) . to . be . revertedWith ( "pToken already set" ) ; await expect ( aaveIntegration . initialize ( [ ZERO_ADDRESS ] , [ aTokens [ 0 ] . aToken ] ) ) . to . be . reverted ; } ) ; } ) ; describe ( "setting P Token Address" , async ( ) => { let erc20Mock ; let aTokenMock ; beforeEach ( "init mocks" , async ( ) => { erc20Mock = await new MockERC20__factory ( sa . default . signer ) . deploy ( "TMP" , "TMP" , 18 , sa . default . address , "1000000" ) ; aTokenMock = await new MockATokenV2__factory ( sa . default . signer ) . deploy ( sa . other . address , erc20Mock . address ) ; await runSetup ( ) ; } ) ; it ( "should pass only when function called by the Governor" , async ( ) => { await expect ( aaveIntegration . setPTokenAddress ( erc20Mock . address , aTokenMock . address ) ) . to . be . revertedWith ( "Only governor can execute" ) ; await aaveIntegration . connect ( sa . governor . signer ) . setPTokenAddress ( erc20Mock . address , aTokenMock . address ) ; expect ( aTokenMock . address ) . eq ( await aaveIntegration . bAssetToPToken ( erc20Mock . address ) ) ; } ) ; it ( "should approve the spending of the bAsset correctly and emit event" , async ( ) => { await aaveIntegration . connect ( sa . governor . signer ) . setPTokenAddress ( erc20Mock . address , aTokenMock . address ) ; expect ( aTokenMock . address ) . eq ( await aaveIntegration . bAssetToPToken ( erc20Mock . address ) ) ; const addressProvider = await ILendingPoolAddressesProviderV2__factory . connect ( integrationDetails . aavePlatformAddress , sa . default . signer ) ; const approvedAddress = await addressProvider . getLendingPool ( ) ; const balance = await erc20Mock . allowance ( aaveIntegration . address , approvedAddress ) ; expect ( balance ) . eq ( MAX_UINT256 ) ; } ) ; it ( "should fail when passed invalid args" , async ( ) => { await expect ( aaveIntegration . connect ( sa . governor . signer ) . setPTokenAddress ( ZERO_ADDRESS , aTokenMock . address ) ) . to . be . revertedWith ( "Invalid addresses" ) ; await expect ( aaveIntegration . connect ( sa . governor . signer ) . setPTokenAddress ( erc20Mock . address , ZERO_ADDRESS ) ) . to . be . revertedWith ( "Invalid addresses" ) ; await aaveIntegration . connect ( sa . governor . signer ) . setPTokenAddress ( erc20Mock . address , aTokenMock . address ) ; await expect ( aaveIntegration . connect ( sa . governor . signer ) . setPTokenAddress ( erc20Mock . address , sa . default . address ) ) . to . be . revertedWith ( "pToken already set" ) ; } ) ; } ) ; describe ( "calling deposit" , async ( ) => { beforeEach ( "init mocks" , async ( ) => { await runSetup ( ) ; } ) ; it ( "should deposit tokens to Aave" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . pow ( bAssetDecimals ) ; const aToken = await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . aToken ) ; const addressProvider = await ILendingPoolAddressesProviderV2__factory . connect ( integrationDetails . aavePlatformAddress , sa . default . signer ) ; const bAssetRecipient = await addressProvider . getLendingPool ( ) ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await aToken . balanceOf ( aaveIntegration . address ) ; let directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( aaveIntegrationBalBefore ) ; await bAsset . transfer ( aaveIntegration . address , amount . toString ( ) ) ; const tx = aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , amount . toString ( ) , false ) ; await expect ( tx ) . to . emit ( aaveIntegration , "Deposit" ) . withArgs ( bAsset . address , aToken . address , amount ) ; await ( await tx ) . wait ( ) ; expect ( await bAsset . balanceOf ( bAssetRecipient ) ) . eq ( bAssetRecipientBalBefore . add ( amount ) ) ; const expectedBalance = aaveIntegrationBalBefore . add ( amount ) ; const actualBalance = await aToken . balanceOf ( aaveIntegration . address ) ; assertBNSlightlyGTPercent ( actualBalance , expectedBalance ) ; directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( actualBalance ) ; await increaseTime ( TEN_MINS ) ; const newBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; assertBNSlightlyGTPercent ( newBalance , directBalance , "0.0001" , false ) ; } ) ; it ( "should handle the fee calculations" , async ( ) => { await runSetup ( true ) ; const addressProvider = await ILendingPoolAddressesProviderV2__factory . connect ( integrationDetails . aavePlatformAddress , sa . default . signer ) ; const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 2 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . pow ( bAssetDecimals ) ; const aToken = await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 2 ] . aToken ) ; const bAssetRecipient = await addressProvider . getLendingPool ( ) ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await aToken . balanceOf ( aaveIntegration . address ) ; let directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( aaveIntegrationBalBefore ) ; const bal1 = await bAsset . balanceOf ( aaveIntegration . address ) ; await bAsset . transfer ( aaveIntegration . address , amount . toString ( ) ) ; const bal2 = await bAsset . balanceOf ( aaveIntegration . address ) ; const receivedAmount = bal2 . sub ( bal1 ) ; expect ( receivedAmount ) . lt ( amount ) ; const fee = amount . sub ( receivedAmount ) ; const feeRate = fee . mul ( simpleToExactAmount ( 1 ) ) . div ( amount ) ; const expectedDeposit = receivedAmount . sub ( receivedAmount . mul ( feeRate ) . div ( simpleToExactAmount ( 1 ) ) ) ; await aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , receivedAmount . toString ( ) , true ) ; const aaveIntegrationBalAfter = await aToken . balanceOf ( aaveIntegration . address ) ; expect ( await bAsset . balanceOf ( bAssetRecipient ) ) . eq ( bAssetRecipientBalBefore . add ( expectedDeposit ) ) ; assertBNClose ( aaveIntegrationBalAfter , aaveIntegrationBalBefore . add ( expectedDeposit ) , fee ) ; directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( aaveIntegrationBalAfter ) ; } ) ; it ( "should only allow a whitelisted user to call function" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 1 ] . bAsset ) ; const amount = BN . from ( 10 ) . pow ( BN . from ( 12 ) ) ; await expect ( aaveIntegration . connect ( sa . dummy1 . signer ) . deposit ( bAsset . address , amount . toString ( ) , false ) ) . to . be . revertedWith ( "Only the LP can execute" ) ; } ) ; it ( "should fail if the bAsset is not supported" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . deploy ( "MK1" , "MK" , 12 , sa . default . address , 100000 ) ; const amount = BN . from ( 10 ) . pow ( BN . from ( 12 ) ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , amount . toString ( ) , false ) ) . to . be . revertedWith ( "aToken does not exist" ) ; } ) ; it ( "should fail if we do not first pass the required bAsset" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const amount = BN . from ( 10 ) . pow ( BN . from ( 12 ) ) ; await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . aToken ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , amount . toString ( ) , false ) ) . to . be . revertedWith ( "ERC20: transfer amount exceeds balance" ) ; } ) ; it ( "should fail if we try to deposit too much" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 1 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . mul ( BN . from ( 10 ) . pow ( bAssetDecimals ) ) ; const amountHigh = BN . from ( 11 ) . mul ( BN . from ( 10 ) . pow ( bAssetDecimals ) ) ; await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 1 ] . aToken ) ; await bAsset . transfer ( aaveIntegration . address , amount . toString ( ) ) ; expect ( await bAsset . balanceOf ( aaveIntegration . address ) ) . lte ( amount ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , amountHigh . toString ( ) , false ) ) . to . be . revertedWith ( "ERC20: transfer amount exceeds balance" ) ; } ) ; it ( "should fail with broken arguments" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . pow ( bAssetDecimals ) ; const aToken = await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . aToken ) ; const addressProvider = await ILendingPoolAddressesProviderV2__factory . connect ( integrationDetails . aavePlatformAddress , sa . default . signer ) ; const bAssetRecipient = await addressProvider . getLendingPool ( ) ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await aToken . balanceOf ( aaveIntegration . address ) ; await bAsset . transfer ( aaveIntegration . address , amount . toString ( ) ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . deposit ( ZERO_ADDRESS , amount . toString ( ) , false ) ) . to . be . revertedWith ( "aToken does not exist" ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , "0" , false ) ) . to . be . revertedWith ( "Must deposit something" ) ; await aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , amount . toString ( ) , undefined ) ; expect ( await bAsset . balanceOf ( bAssetRecipient ) ) . eq ( bAssetRecipientBalBefore . add ( amount ) ) ; const newBal = await aToken . balanceOf ( aaveIntegration . address ) ; assertBNSlightlyGT ( newBal , aaveIntegrationBalBefore . add ( amount ) , BN . from ( "1000" ) ) ; const directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( newBal ) ; } ) ; it ( "should fail if lending pool does not exist" , async ( ) => { const mockAave = await new MockAaveV2__factory ( sa . default . signer ) . attach ( integrationDetails . aavePlatformAddress ) ; await mockAave . breakLendingPools ( ) ; const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 1 ] . bAsset ) ; await bAsset . transfer ( aaveIntegration . address , "1" ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . deposit ( bAsset . address , "1" , false ) ) . to . be . revertedWith ( "Lending pool does not exist" ) ; } ) ; } ) ; describe ( "withdraw" , async ( ) => { beforeEach ( "init mocks" , async ( ) => { await runSetup ( false , true ) ; } ) ; it ( "should withdraw tokens from Aave" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . pow ( bAssetDecimals ) ; const aToken = await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . aToken ) ; const bAssetRecipient = sa . dummy1 . address ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await aToken . balanceOf ( aaveIntegration . address ) ; await aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( bAssetRecipient , bAsset . address , amount . toString ( ) , false ) ; expect ( await bAsset . balanceOf ( bAssetRecipient ) ) . eq ( bAssetRecipientBalBefore . add ( amount ) ) ; const actualBalance = await aToken . balanceOf ( aaveIntegration . address ) ; const expectedBalance = aaveIntegrationBalBefore . sub ( amount ) ; assertBNSlightlyGTPercent ( actualBalance , expectedBalance , "0.001" , false ) ; const directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( actualBalance ) ; await increaseTime ( TEN_MINS ) ; const newBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; assertBNSlightlyGTPercent ( newBalance , directBalance , "0.001" , false ) ; } ) ; it ( "should handle the fee calculations" , async ( ) => { await runSetup ( true , true ) ; const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 1 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . pow ( bAssetDecimals ) ; const aToken = await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 1 ] . aToken ) ; const bAssetRecipient = sa . dummy1 . address ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await aToken . balanceOf ( aaveIntegration . address ) ; await aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( bAssetRecipient , bAsset . address , amount . toString ( ) , true ) ; const bAssetRecipientBalAfter = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalAfter = await aToken . balanceOf ( aaveIntegration . address ) ; const scale = simpleToExactAmount ( "0.99" , 18 ) ; const amountScaled = amount . mul ( scale ) ; const expectedAmount = amountScaled . div ( simpleToExactAmount ( 1 ) ) ; expect ( bAssetRecipientBalAfter ) . gte ( bAssetRecipientBalBefore . add ( expectedAmount ) ) ; expect ( bAssetRecipientBalAfter ) . lte ( bAssetRecipientBalBefore . add ( amount ) ) ; expect ( aaveIntegrationBalAfter ) . eq ( aaveIntegrationBalBefore . sub ( amount ) ) ; const expectedBalance = aaveIntegrationBalBefore . sub ( amount ) ; assertBNSlightlyGT ( aaveIntegrationBalAfter , expectedBalance , BN . from ( "100" ) ) ; const directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( expectedBalance ) ; } ) ; it ( "should only allow a whitelisted user to call function" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . pow ( bAssetDecimals ) ; await expect ( aaveIntegration . connect ( sa . dummy1 . signer ) [ "withdraw(address,address,uint256,bool)" ] ( sa . dummy1 . address , bAsset . address , amount . toString ( ) , false ) ) . to . be . revertedWith ( "Only the LP can execute" ) ; } ) ; it ( "should fail if there is insufficient balance" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 1000 ) . mul ( BN . from ( 10 ) . pow ( bAssetDecimals ) ) ; await expect ( aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( sa . default . address , bAsset . address , amount . toString ( ) , false ) ) . to . be . revertedWith ( "ERC20: burn amount exceeds balance" ) ; } ) ; it ( "should fail with broken arguments" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = BN . from ( 10 ) . pow ( bAssetDecimals ) ; const aToken = await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . aToken ) ; const bAssetRecipient = sa . dummy1 . address ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await aToken . balanceOf ( aaveIntegration . address ) ; await expect ( aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( sa . dummy1 . address , ZERO_ADDRESS , amount . toString ( ) , false ) ) . to . be . revertedWith ( "aToken does not exist" ) ; await expect ( aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( ZERO_ADDRESS , bAsset . address , BN . from ( 1 ) , false ) ) . to . be . reverted ; await expect ( aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( sa . dummy1 . address , bAsset . address , "0" , false ) ) . to . be . revertedWith ( "Must withdraw something" ) ; await aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( sa . dummy1 . address , bAsset . address , amount . toString ( ) , undefined ) ; expect ( await bAsset . balanceOf ( bAssetRecipient ) ) . eq ( bAssetRecipientBalBefore . add ( amount ) ) ; const currentBalance = await aToken . balanceOf ( aaveIntegration . address ) ; assertBNSlightlyGTPercent ( currentBalance , aaveIntegrationBalBefore . sub ( amount ) , "0.0001" , false ) ; } ) ; it ( "should fail if the bAsset is not supported" , async ( ) => { const bAsset = await mAssetMachine . loadBassetProxy ( "MK" , "MK" , 12 ) ; const amount = BN . from ( 10 ) . pow ( BN . from ( 12 ) ) ; await expect ( aaveIntegration [ "withdraw(address,address,uint256,bool)" ] ( sa . dummy1 . address , bAsset . address , amount . toString ( ) , false ) ) . to . be . revertedWith ( "aToken does not exist" ) ; } ) ; } ) ; describe ( "withdraw specific amount" , async ( ) => { describe ( "and the token does not have transfer fee" , async ( ) => { beforeEach ( "init mocks" , async ( ) => { await runSetup ( false , true ) ; } ) ; it ( "should allow withdrawal of X and give Y to the caller" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = simpleToExactAmount ( 5 , bAssetDecimals ) ; const totalAmount = amount . mul ( 2 ) ; await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . aToken ) ; const bAssetRecipient = sa . dummy1 . address ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await bAsset . balanceOf ( aaveIntegration . address ) ; const aaveBalanceBefore = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; await expect ( aaveIntegration . connect ( sa . dummy1 . signer ) [ "withdraw(address,address,uint256,uint256,bool)" ] ( bAssetRecipient , bAsset . address , amount , totalAmount , false ) ) . to . be . revertedWith ( "Only the LP can execute" ) ; await aaveIntegration [ "withdraw(address,address,uint256,uint256,bool)" ] ( bAssetRecipient , bAsset . address , amount , totalAmount , false ) ; const bAssetRecipientBalAfter = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalAfter = await bAsset . balanceOf ( aaveIntegration . address ) ; const aaveBalanceAfter = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( bAssetRecipientBalAfter ) . eq ( bAssetRecipientBalBefore . add ( amount ) ) ; expect ( aaveIntegrationBalAfter ) . eq ( aaveIntegrationBalBefore . add ( totalAmount . sub ( amount ) ) ) ; expect ( aaveBalanceAfter ) . eq ( aaveBalanceBefore . sub ( totalAmount ) ) ; } ) ; } ) ; describe ( "and the token has transfer fees" , async ( ) => { beforeEach ( "init mocks" , async ( ) => { await runSetup ( true , true ) ; } ) ; it ( "should fail if totalAmount != userAmount" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 1 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = simpleToExactAmount ( 5 , bAssetDecimals ) ; const totalAmount = amount . mul ( 2 ) ; await expect ( aaveIntegration [ "withdraw(address,address,uint256,uint256,bool)" ] ( sa . dummy1 . address , bAsset . address , amount , totalAmount , true ) ) . to . be . revertedWith ( "Cache inactive for assets with fee" ) ; } ) ; } ) ; } ) ; describe ( "withdrawRaw" , async ( ) => { beforeEach ( "init mocks" , async ( ) => { await runSetup ( false , true ) ; } ) ; it ( "should fail if caller is not whitelisetd" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; await expect ( aaveIntegration . connect ( sa . dummy1 . signer ) . withdrawRaw ( sa . dummy3 . address , bAsset . address , BN . from ( 1 ) ) ) . to . be . revertedWith ( "Only the LP can execute" ) ; } ) ; it ( "should allow the mAsset or BM to withdraw a given bAsset" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const bAssetDecimals = await bAsset . decimals ( ) ; const amount = simpleToExactAmount ( 5 , bAssetDecimals ) ; await bAsset . transfer ( aaveIntegration . address , amount ) ; const bAssetRecipient = sa . dummy1 . address ; const bAssetRecipientBalBefore = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalBefore = await bAsset . balanceOf ( aaveIntegration . address ) ; const aaveBalanceBefore = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; await aaveIntegration . connect ( mAsset . signer ) . withdrawRaw ( bAssetRecipient , bAsset . address , amount ) ; const bAssetRecipientBalAfter = await bAsset . balanceOf ( bAssetRecipient ) ; const aaveIntegrationBalAfter = await bAsset . balanceOf ( aaveIntegration . address ) ; const aaveBalanceAfter = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( bAssetRecipientBalAfter ) . eq ( bAssetRecipientBalBefore . add ( amount ) ) ; expect ( aaveIntegrationBalAfter ) . eq ( aaveIntegrationBalBefore . sub ( amount ) ) ; expect ( aaveBalanceAfter ) . eq ( aaveBalanceBefore ) ; } ) ; it ( "should fail if there is no balance in a given asset" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . withdrawRaw ( sa . dummy3 . address , bAsset . address , BN . from ( 1 ) ) ) . to . be . revertedWith ( "ERC20: transfer amount exceeds balance" ) ; } ) ; it ( "should fail if specified a 0 amount" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; await expect ( aaveIntegration . connect ( mAsset . signer ) . withdrawRaw ( sa . dummy3 . address , bAsset . address , BN . from ( 0 ) ) ) . to . be . revertedWith ( "Must withdraw something" ) ; } ) ; } ) ; describe ( "checkBalance" , async ( ) => { it ( "should return balance for any caller when supported token address passed" , async ( ) => { const bAsset = await new MockERC20__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . bAsset ) ; const aToken = await new MockATokenV2__factory ( sa . default . signer ) . attach ( integrationDetails . aTokens [ 0 ] . aToken ) ; const aaveIntegrationBal = await aToken . balanceOf ( aaveIntegration . address ) ; const directBalance = await aaveIntegration . callStatic . checkBalance ( bAsset . address ) ; expect ( directBalance ) . eq ( aaveIntegrationBal ) ; } ) ; it ( "should fail if called with inactive token" , async ( ) => { const bAsset = await mAssetMachine . loadBassetProxy ( "MK" , "MK1" , 12 ) ; await expect ( aaveIntegration . checkBalance ( bAsset . address ) ) . to . be . revertedWith ( "aToken does not exist" ) ; } ) ; } ) ; } ) ;