import { ethers , network , upgrades } from "hardhat" ; import { BigNumber , Signer } from "ethers" ; import { ProxyWallet , PositionManager__factory , BookKeeper , BookKeeper__factory , PositionManager , AlpacaStablecoinProxyActions , AlpacaStablecoinProxyActions__factory , IbTokenAdapter__factory , BEP20__factory , AlpacaToken__factory , FairLaunch__factory , Shield__factory , IbTokenAdapter , BEP20 , ShowStopper , ShowStopper__factory , StabilityFeeCollector , StabilityFeeCollector__factory , AlpacaStablecoin__factory , AlpacaStablecoin , StablecoinAdapter__factory , StablecoinAdapter , TokenAdapter__factory , TokenAdapter , AlpacaToken , FairLaunch , CollateralPoolConfig__factory , CollateralPoolConfig , AccessControlConfig__factory , AccessControlConfig , SimplePriceFeed__factory , SimplePriceFeed , SystemDebtEngine , SystemDebtEngine__factory , } from "../../../typechain" ; import { expect } from "chai" ; import { loadProxyWalletFixtureHandler } from "../../helper/proxy" ; import { formatBytes32String , parseEther , parseUnits } from "ethers/lib/utils" ; import { expectEmit , getEvent } from "../../helper/event" ; import { DebtToken__factory , MockWBNB , MockWBNB__factory , SimpleVaultConfig__factory , Vault , Vault__factory , WNativeRelayer , WNativeRelayer__factory , } from "@alpaca-finance/alpaca-contract/typechain" ; import { WeiPerRad , WeiPerRay , WeiPerWad } from "../../helper/unit" ; import * as TimeHelpers from "../../helper/time" ; import * as AssertHelpers from "../../helper/assert" ; import { AddressZero } from "../../helper/address" ; import { TransactionReceipt } from "@ethersproject/abstract-provider" ; type Fixture = { positionManager : PositionManager ; alpacaStablecoinProxyActions : AlpacaStablecoinProxyActions ; bookKeeper : BookKeeper ; stabilityFeeCollector : StabilityFeeCollector ; ibTokenAdapter : IbTokenAdapter ; ibTokenAdapter2 : IbTokenAdapter ; wbnbTokenAdapter : TokenAdapter ; alpacaTokenAdapter : TokenAdapter ; stablecoinAdapter : StablecoinAdapter ; vault : Vault ; fairLaunch : FairLaunch ; baseToken : BEP20 ; alpacaToken : AlpacaToken ; alpacaStablecoin : AlpacaStablecoin ; collateralPoolConfig : CollateralPoolConfig ; } ; const CLOSE_FACTOR_BPS = BigNumber . from ( 5000 ) ; const LIQUIDATOR_INCENTIVE_BPS = BigNumber . from ( 12500 ) ; const TREASURY_FEE_BPS = BigNumber . from ( 2500 ) ; const loadFixtureHandler = async ( ) : Promise < Fixture > => { const [ deployer , alice , , dev ] = await ethers . getSigners ( ) ; const ALPACA_BONUS_LOCK_UP_BPS = 7000 ; const ALPACA_REWARD_PER_BLOCK = ethers . utils . parseEther ( "5000" ) ; const RESERVE_POOL_BPS = "1000" ; const KILL_PRIZE_BPS = "1000" ; const INTEREST_RATE = "3472222222222" ; const MIN_DEBT_SIZE = ethers . utils . parseEther ( "1" ) ; const KILL_TREASURY_BPS = "100" ; const WBNB = new MockWBNB__factory ( deployer ) ; const wbnb = ( await WBNB . deploy ( ) ) as MockWBNB ; await wbnb . deployed ( ) ; const WNativeRelayer = new WNativeRelayer__factory ( deployer ) ; const wNativeRelayer = ( await WNativeRelayer . deploy ( wbnb . address ) ) as WNativeRelayer ; await wNativeRelayer . deployed ( ) ; const BEP20 = ( await ethers . getContractFactory ( "BEP20" , deployer ) ) as BEP20__factory ; const ibDUMMY = await BEP20 . deploy ( "ibDUMMY" , "ibDUMMY" ) ; await ibDUMMY . deployed ( ) ; const baseToken = await BEP20 . deploy ( "BTOKEN" , "BTOKEN" ) ; await baseToken . deployed ( ) ; await baseToken . mint ( await deployer . getAddress ( ) , ethers . utils . parseEther ( "100" ) ) ; await baseToken . mint ( await alice . getAddress ( ) , ethers . utils . parseEther ( "100" ) ) ; const DebtToken = new DebtToken__factory ( deployer ) ; const debtToken = await DebtToken . deploy ( ) ; await debtToken . deployed ( ) ; await debtToken . initialize ( "debtibBTOKEN_V2" , "debtibBTOKEN_V2" , deployer . address ) ; const AlpacaToken = new AlpacaToken__factory ( deployer ) ; const alpacaToken = await AlpacaToken . deploy ( 132 , 137 ) ; await alpacaToken . deployed ( ) ; await alpacaToken . mint ( await alice . getAddress ( ) , WeiPerWad . mul ( 100 ) ) ; const FairLaunch = new FairLaunch__factory ( deployer ) ; const fairLaunch = ( await FairLaunch . deploy ( alpacaToken . address , deployer . address , ALPACA_REWARD_PER_BLOCK , 0 , ALPACA_BONUS_LOCK_UP_BPS , 0 ) ) as FairLaunch ; await fairLaunch . deployed ( ) ; const Shield = ( await ethers . getContractFactory ( "Shield" , deployer ) ) as Shield__factory ; const shield = await Shield . deploy ( deployer . address , fairLaunch . address ) ; await shield . deployed ( ) ; const SimpleVaultConfig = new SimpleVaultConfig__factory ( deployer ) ; const simpleVaultConfig = await SimpleVaultConfig . deploy ( ) ; await simpleVaultConfig . deployed ( ) ; await simpleVaultConfig . initialize ( MIN_DEBT_SIZE , INTEREST_RATE , RESERVE_POOL_BPS , KILL_PRIZE_BPS , wbnb . address , wNativeRelayer . address , fairLaunch . address , KILL_TREASURY_BPS , deployer . address ) ; const Vault = new Vault__factory ( deployer ) ; const busdVault = await Vault . deploy ( ) ; await busdVault . deployed ( ) ; await busdVault . initialize ( simpleVaultConfig . address , baseToken . address , "Interest Bearing BTOKEN" , "ibBTOKEN" , 18 , debtToken . address ) ; await fairLaunch . addPool ( 1 , busdVault . address , true ) ; await fairLaunch . transferOwnership ( shield . address ) ; await shield . transferOwnership ( await deployer . getAddress ( ) ) ; await alpacaToken . transferOwnership ( fairLaunch . address ) ; const AlpacaStablecoin = new AlpacaStablecoin__factory ( deployer ) ; const alpacaStablecoin = ( await upgrades . deployProxy ( AlpacaStablecoin , [ "Alpaca USD" , "AUSD" ] ) ) as AlpacaStablecoin ; const AccessControlConfig = ( await ethers . getContractFactory ( "AccessControlConfig" , deployer ) ) as AccessControlConfig__factory ; const accessControlConfig = ( await upgrades . deployProxy ( AccessControlConfig , [ ] ) ) as AccessControlConfig ; const CollateralPoolConfig = ( await ethers . getContractFactory ( "CollateralPoolConfig" , deployer ) ) as CollateralPoolConfig__factory ; const collateralPoolConfig = ( await upgrades . deployProxy ( CollateralPoolConfig , [ accessControlConfig . address , ] ) ) as CollateralPoolConfig ; const BookKeeper = new BookKeeper__factory ( deployer ) ; const bookKeeper = ( await upgrades . deployProxy ( BookKeeper , [ collateralPoolConfig . address , accessControlConfig . address , ] ) ) as BookKeeper ; await accessControlConfig . grantRole ( await accessControlConfig . BOOK_KEEPER_ROLE ( ) , bookKeeper . address ) ; await accessControlConfig . grantRole ( await accessControlConfig . PRICE_ORACLE_ROLE ( ) , deployer . address ) ; const ShowStopper = ( await ethers . getContractFactory ( "ShowStopper" , deployer ) ) as ShowStopper__factory ; const showStopper = ( await upgrades . deployProxy ( ShowStopper , [ bookKeeper . address ] ) ) as ShowStopper ; const PositionManager = new PositionManager__factory ( deployer ) ; const positionManager = ( await upgrades . deployProxy ( PositionManager , [ bookKeeper . address , showStopper . address , ] ) ) as PositionManager ; const AlpacaStablecoinProxyActions = new AlpacaStablecoinProxyActions__factory ( deployer ) ; const alpacaStablecoinProxyActions = await AlpacaStablecoinProxyActions . deploy ( ) ; const IbTokenAdapter = ( await ethers . getContractFactory ( "IbTokenAdapter" , deployer ) ) as IbTokenAdapter__factory ; const ibTokenAdapter = ( await upgrades . deployProxy ( IbTokenAdapter , [ bookKeeper . address , formatBytes32String ( "ibBUSD" ) , busdVault . address , alpacaToken . address , fairLaunch . address , 0 , shield . address , await deployer . getAddress ( ) , BigNumber . from ( 1000 ) , await dev . getAddress ( ) , positionManager . address , ] ) ) as IbTokenAdapter ; const ibTokenAdapter2 = ( await upgrades . deployProxy ( IbTokenAdapter , [ bookKeeper . address , formatBytes32String ( "ibBUSD2" ) , busdVault . address , alpacaToken . address , fairLaunch . address , 0 , shield . address , await deployer . getAddress ( ) , BigNumber . from ( 1000 ) , await dev . getAddress ( ) , positionManager . address , ] ) ) as IbTokenAdapter ; const WBNBTokenAdapter = new TokenAdapter__factory ( deployer ) ; const wbnbTokenAdapter = ( await upgrades . deployProxy ( WBNBTokenAdapter , [ bookKeeper . address , formatBytes32String ( "WBNB" ) , wbnb . address , ] ) ) as TokenAdapter ; const AlpacaTokenAdapter = new TokenAdapter__factory ( deployer ) ; const alpacaTokenAdapter = ( await upgrades . deployProxy ( AlpacaTokenAdapter , [ bookKeeper . address , formatBytes32String ( "ALPACA" ) , alpacaToken . address , ] ) ) as TokenAdapter ; const StablecoinAdapter = new StablecoinAdapter__factory ( deployer ) ; const stablecoinAdapter = ( await upgrades . deployProxy ( StablecoinAdapter , [ bookKeeper . address , alpacaStablecoin . address , ] ) ) as StablecoinAdapter ; const SystemDebtEngine = ( await ethers . getContractFactory ( "SystemDebtEngine" , deployer ) ) as SystemDebtEngine__factory ; const systemDebtEngine = ( await upgrades . deployProxy ( SystemDebtEngine , [ bookKeeper . address ] ) ) as SystemDebtEngine ; const StabilityFeeCollector = new StabilityFeeCollector__factory ( deployer ) ; const stabilityFeeCollector = ( await upgrades . deployProxy ( StabilityFeeCollector , [ bookKeeper . address , systemDebtEngine . address , ] ) ) as StabilityFeeCollector ; await accessControlConfig . grantRole ( ethers . utils . solidityKeccak256 ( [ "string" ] , [ "ADAPTER_ROLE" ] ) , ibTokenAdapter . address ) ; await accessControlConfig . grantRole ( ethers . utils . solidityKeccak256 ( [ "string" ] , [ "ADAPTER_ROLE" ] ) , ibTokenAdapter2 . address ) ; await accessControlConfig . grantRole ( await accessControlConfig . ADAPTER_ROLE ( ) , wbnbTokenAdapter . address ) ; await accessControlConfig . grantRole ( await accessControlConfig . ADAPTER_ROLE ( ) , alpacaTokenAdapter . address ) ; await accessControlConfig . grantRole ( ethers . utils . solidityKeccak256 ( [ "string" ] , [ "POSITION_MANAGER_ROLE" ] ) , positionManager . address ) ; await accessControlConfig . grantRole ( ethers . utils . solidityKeccak256 ( [ "string" ] , [ "COLLATERAL_MANAGER_ROLE" ] ) , positionManager . address ) ; await accessControlConfig . grantRole ( ethers . utils . solidityKeccak256 ( [ "string" ] , [ "STABILITY_FEE_COLLECTOR_ROLE" ] ) , stabilityFeeCollector . address ) ; await alpacaStablecoin . grantRole ( await alpacaStablecoin . MINTER_ROLE ( ) , stablecoinAdapter . address ) ; const SimplePriceFeed = ( await ethers . getContractFactory ( "SimplePriceFeed" , deployer ) ) as SimplePriceFeed__factory ; const simplePriceFeed = ( await upgrades . deployProxy ( SimplePriceFeed , [ accessControlConfig . address , ] ) ) as SimplePriceFeed ; await simplePriceFeed . deployed ( ) ; await bookKeeper . setTotalDebtCeiling ( WeiPerRad . mul ( 10000 ) ) ; await collateralPoolConfig . initCollateralPool ( formatBytes32String ( "ibBUSD" ) , WeiPerRad . mul ( 100 ) , WeiPerRad . mul ( 1 ) , simplePriceFeed . address , WeiPerRay , WeiPerRay , ibTokenAdapter . address , CLOSE_FACTOR_BPS , LIQUIDATOR_INCENTIVE_BPS , TREASURY_FEE_BPS , AddressZero ) ; await collateralPoolConfig . initCollateralPool ( formatBytes32String ( "ibBUSD2" ) , WeiPerRad . mul ( 100 ) , WeiPerRad . mul ( 1 ) , simplePriceFeed . address , WeiPerRay , WeiPerRay , ibTokenAdapter2 . address , CLOSE_FACTOR_BPS , LIQUIDATOR_INCENTIVE_BPS , TREASURY_FEE_BPS , AddressZero ) ; await collateralPoolConfig . setPriceWithSafetyMargin ( formatBytes32String ( "ibBUSD" ) , WeiPerRay ) ; await collateralPoolConfig . setPriceWithSafetyMargin ( formatBytes32String ( "ibBUSD2" ) , WeiPerRay ) ; await collateralPoolConfig . initCollateralPool ( formatBytes32String ( "WBNB" ) , WeiPerRad . mul ( 1000 ) , WeiPerRad . mul ( 1 ) , simplePriceFeed . address , WeiPerRay , WeiPerRay , ibTokenAdapter . address , CLOSE_FACTOR_BPS , LIQUIDATOR_INCENTIVE_BPS , TREASURY_FEE_BPS , AddressZero ) ; await collateralPoolConfig . setPriceWithSafetyMargin ( formatBytes32String ( "WBNB" ) , WeiPerRay . mul ( 400 ) ) ; await collateralPoolConfig . initCollateralPool ( formatBytes32String ( "ALPACA" ) , WeiPerRad . mul ( 1000 ) , WeiPerRad . mul ( 1 ) , simplePriceFeed . address , WeiPerRay , WeiPerRay , ibTokenAdapter . address , CLOSE_FACTOR_BPS , LIQUIDATOR_INCENTIVE_BPS , TREASURY_FEE_BPS , AddressZero ) ; await collateralPoolConfig . setPriceWithSafetyMargin ( formatBytes32String ( "ALPACA" ) , WeiPerRay . mul ( 10 ) ) ; return { alpacaStablecoinProxyActions , positionManager , bookKeeper , stabilityFeeCollector , ibTokenAdapter , ibTokenAdapter2 , wbnbTokenAdapter , alpacaTokenAdapter , stablecoinAdapter , vault : busdVault , fairLaunch , baseToken , alpacaToken , alpacaStablecoin , collateralPoolConfig , } ; } ; describe ( "position manager" , ( ) => { let deployer ; let alice ; let bob ; let deployerAddress ; let aliceAddress ; let bobAddress ; let deployerProxyWallet ; let aliceProxyWallet ; let bobProxyWallet ; let positionManager ; let alpacaStablecoinProxyActions ; let alpacaStablecoinProxyActionsAsAlice ; let bookKeeper ; let ibTokenAdapter ; let ibTokenAdapter2 ; let wbnbTokenAdapter ; let alpacaTokenAdapter ; let stablecoinAdapter ; let vault ; let fairLaunch ; let baseToken ; let alpacaToken ; let stabilityFeeCollector ; let alpacaStablecoin ; let collateralPoolConfig ; beforeEach ( async ( ) => { ; [ deployer , alice , bob ] = await ethers . getSigners ( ) ; [ deployerAddress , aliceAddress , bobAddress ] = await Promise . all ( [ deployer . getAddress ( ) , alice . getAddress ( ) , bob . getAddress ( ) , ] ) ; ( { proxyWallets : [ deployerProxyWallet , aliceProxyWallet , bobProxyWallet ] , } = await loadProxyWalletFixtureHandler ( ) ) ; ( { alpacaStablecoinProxyActions , positionManager , bookKeeper , ibTokenAdapter , ibTokenAdapter2 , wbnbTokenAdapter , alpacaTokenAdapter , stablecoinAdapter , vault , fairLaunch , baseToken , alpacaToken , stabilityFeeCollector , alpacaToken , alpacaStablecoin , collateralPoolConfig , } = await loadFixtureHandler ( ) ) ; const baseTokenAsAlice = BEP20__factory . connect ( baseToken . address , alice ) ; const vaultAsAlice = Vault__factory . connect ( vault . address , alice ) ; const alpacaStablecoinAsAlice = Vault__factory . connect ( alpacaStablecoin . address , alice ) ; const alpacaTokenAsAlice = AlpacaToken__factory . connect ( alpacaToken . address , alice ) ; alpacaStablecoinProxyActionsAsAlice = AlpacaStablecoinProxyActions__factory . connect ( alpacaStablecoinProxyActions . address , alice ) ; await baseTokenAsAlice . approve ( aliceProxyWallet . address , WeiPerWad . mul ( 10000 ) ) ; await vaultAsAlice . approve ( aliceProxyWallet . address , WeiPerWad . mul ( 10000 ) ) ; await alpacaStablecoinAsAlice . approve ( aliceProxyWallet . address , WeiPerWad . mul ( 10000 ) ) ; await alpacaTokenAsAlice . approve ( aliceProxyWallet . address , WeiPerWad . mul ( 10000 ) ) ; } ) ; describe ( "ibToken collateral pool" , ( ) => { describe ( "user opens a new position" , ( ) => { context ( "alice opens a new position" , ( ) => { it ( "alice should be able to open a position" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "ibBUSD" ) , aliceProxyWallet . address , ] ) ; const tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const txReceipt = await tx . wait ( ) ; const event = await getEvent ( positionManager , txReceipt . blockNumber , "LogNewPosition" ) ; expectEmit ( event , aliceProxyWallet . address , aliceProxyWallet . address , 1 ) ; expect ( await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ) . to . be . equal ( 1 ) ; } ) ; } ) ; } ) ; describe ( "user opens a new position and convert token to ibToken and lock ibToken and mint AUSD in separated transactions" , ( ) => { context ( "alice doesn't have collateral token" , ( ) => { it ( "should be revert" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "ibBUSD" ) , aliceProxyWallet . address , ] ) ; const openTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const lockCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "lockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , lockCall ) ) . to . be . revertedWith ( "!safeTransferFrom" ) ; } ) ; } ) ; context ( "alice mint AUSD over a lock collateral value" , ( ) => { it ( "should be revert" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "ibBUSD" ) , aliceProxyWallet . address , ] ) ; const openTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const convertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "tokenToIbToken" , [ vault . address , WeiPerWad . mul ( 10 ) , true , ] ) ; const convertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertCall ) ; const lockCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "lockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const lockTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , lockCall ) ; const drawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "draw" , [ positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 15 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , drawCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice opens a new position and convert token to ibToken and lock ibToken and mint AUSD in separated transactions" , ( ) => { it ( "alice should be able to opens a new position and convert token to ibToken and lock ibToken and mint AUSD in separated transactions" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "ibBUSD" ) , aliceProxyWallet . address , ] ) ; const openTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const ibTokenBefore = await vault . balanceOf ( aliceAddress ) ; const convertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "tokenToIbToken" , [ vault . address , WeiPerWad . mul ( 10 ) , true , ] ) ; const convertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertCall ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; const ibTokenAfterConvert = await vault . balanceOf ( aliceAddress ) ; expect ( baseTokenBefore . sub ( baseTokenAfter ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( ibTokenAfterConvert . sub ( ibTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; const [ lockedCollateralBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const lockCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "lockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const lockTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , lockCall ) ; const ibTokenAfterLock = await vault . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfterlock , debtShareAfterlock ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( ibTokenAfterConvert . sub ( ibTokenAfterLock ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( lockedCollateralAfterlock . sub ( lockedCollateralBefore ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const drawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "draw" , [ positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const drawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , drawCall ) ; const [ lockedCollateralAfterDraw , debtShareAfterDraw ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinAfterDraw = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( debtShareAfterDraw . sub ( debtShareAfterlock ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; expect ( alpacaStablecoinAfterDraw . sub ( alpacaStablecoinBefore ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; } ) ; } ) ; } ) ; describe ( "user opens a new position and convert token to ibToken and lock ibToken and mint AUSD in single transactions" , ( ) => { context ( "bob doesn't have collateral token" , ( ) => { it ( "should be revert" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 15 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ bobAddress ] ) , ] ) ; await expect ( bobProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ) . to . be . revertedWith ( "!safeTransferFrom" ) ; } ) ; } ) ; context ( "alice mint AUSD over a lock collateral value" , ( ) => { it ( "should be revert" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 15 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice opens a new position and convert token to ibToken and lock ibToken and mint AUSD in single transactions" , ( ) => { it ( "alice should be able to opens a new position and convert token to ibToken and lock ibToken and mint AUSD in single transactions" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const txReceipt = await convertOpenLockTokenAndDrawTx . wait ( ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( lockedCollateralAfter ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( debtShareAfter ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; expect ( alpacaStablecoinAfter ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; const harvestCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "harvest" , [ positionManager . address , ibTokenAdapter . address , 1 , alpacaToken . address , ] ) ; await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , harvestCall ) ; const harvestedALPACABalance = await alpacaToken . balanceOf ( aliceAddress ) ; expect ( harvestedALPACABalance ) . to . be . equal ( ethers . utils . parseEther ( "4600" ) ) ; } ) ; } ) ; } ) ; describe ( "user repay some AUSD and unlock ibToken and convert ibToken to token in separated transactions" , ( ) => { context ( "alice repay some AUSD and unlock ibToken and convert ibToken to token in separated transactions" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ibToken and convert ibToken to token in separated transactions" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 2 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const ibTokenBefore = await vault . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 2 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const ibTokenAfter = await vault . balanceOf ( aliceAddress ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( ibTokenAfter . sub ( ibTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const convertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "ibTokenToToken" , [ vault . address , WeiPerWad . mul ( 2 ) , ] ) ; const convertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertCall ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; expect ( baseTokenAfter . sub ( baseTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; } ) ; } ) ; context ( "alice repay AUSD over debt" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ibToken and convert ibToken to token in separated transactions" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const convertOpenLockTokenAndDraw2Call = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDraw2Tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const bookKeeperStablecoinBefore = await bookKeeper . stablecoin ( positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 10 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const bookKeeperStablecoinAfter = await bookKeeper . stablecoin ( positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const ibTokenBefore = await vault . balanceOf ( aliceAddress ) ; expect ( bookKeeperStablecoinAfter . sub ( bookKeeperStablecoinBefore ) ) . to . be . equal ( WeiPerRad . mul ( 5 ) ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const ibTokenAfter = await vault . balanceOf ( aliceAddress ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( ibTokenAfter . sub ( ibTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const convertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "ibTokenToToken" , [ vault . address , WeiPerWad . mul ( 10 ) , ] ) ; const convertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertCall ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; expect ( baseTokenAfter . sub ( baseTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; } ) ; } ) ; context ( "alice unlock ibToken over lock collateral" , ( ) => { it ( "should be revert" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const ibTokenBefore = await vault . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 15 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ) . to . be . reverted ; } ) ; } ) ; context ( "alice unlock ibToken over position safe" , ( ) => { it ( "should be revert" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 2 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const ibTokenBefore = await vault . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; } ) ; describe ( "user repay some AUSD and unlock ibToken and convert ibToken to token in single transactions" , ( ) => { context ( "alice repay AUSD over debt" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ibToken and convert ibToken to token in single transactions" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const convertOpenLockTokenAndDraw2Call = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDraw2Tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bookeeperStablecoinBefore = await bookKeeper . stablecoin ( positionAddress ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const wipeUnlockTokenAndConvertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeUnlockTokenAndConvert" , [ vault . address , positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 2 ) , WeiPerWad . mul ( 10 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeUnlockTokenAndConvertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeUnlockTokenAndConvertCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bookeeperStablecoinAfter = await bookKeeper . stablecoin ( positionAddress ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( bookeeperStablecoinAfter . sub ( bookeeperStablecoinBefore ) ) . to . be . equal ( WeiPerRad . mul ( 5 ) ) ; expect ( baseTokenAfter . sub ( baseTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( await bookKeeper . stablecoin ( positionAddress ) ) . to . be . equal ( WeiPerRad . mul ( 5 ) ) ; } ) ; } ) ; context ( "alice unlock ibToken over lock collateral" , ( ) => { it ( "should be revert" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeUnlockTokenAndConvertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeUnlockTokenAndConvert" , [ vault . address , positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 15 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeUnlockTokenAndConvertCall ) ) . to . be . reverted ; } ) ; } ) ; context ( "alice unlock ibToken over position safe" , ( ) => { it ( "should be revert" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeUnlockTokenAndConvertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeUnlockTokenAndConvert" , [ vault . address , positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 1 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeUnlockTokenAndConvertCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice repay some AUSD and unlock ibToken and convert ibToken to token in single transactions" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ibToken and convert ibToken to token in single transactions" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const wipeUnlockTokenAndConvertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeUnlockTokenAndConvert" , [ vault . address , positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 2 ) , WeiPerWad . mul ( 2 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeUnlockTokenAndConvertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeUnlockTokenAndConvertCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( baseTokenAfter . sub ( baseTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; } ) ; } ) ; } ) ; describe ( "user repay all AUSD and unlock ibToken and convert ibToken to token in separated transactions" , ( ) => { context ( "alice repay all AUSD and unlock ibToken and convert ibToken to token in separated transactions" , ( ) => { it ( "alice should be able to repay all AUSD and unlock ibToken and convert ibToken to token in separated transactions" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeAllCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAll" , [ positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAllCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const ibTokenBefore = await vault . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 2 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const ibTokenAfter = await vault . balanceOf ( aliceAddress ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( ibTokenAfter . sub ( ibTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const convertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "ibTokenToToken" , [ vault . address , WeiPerWad . mul ( 2 ) , ] ) ; const convertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertCall ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; expect ( baseTokenAfter . sub ( baseTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; } ) ; } ) ; } ) ; describe ( "user repay all AUSD and unlock ibToken and convert ibToken to token in single transactions" , ( ) => { context ( "alice repay all AUSD and unlock ibToken and convert ibToken to token in single transactions" , ( ) => { it ( "alice should be able to repay all AUSD and unlock ibToken and convert ibToken to token in single transactions" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const wipeAllUnlockTokenAndConvertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAllUnlockTokenAndConvert" , [ vault . address , positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 2 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeUnlockTokenAndConvertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAllUnlockTokenAndConvertCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; expect ( baseTokenAfter . sub ( baseTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( WeiPerWad . mul ( 2 ) ) ; } ) ; } ) ; } ) ; describe ( "user mint AUSD and repay AUSD (fee 20%)" , ( ) => { context ( "alice mint AUSD and repay AUSD" , ( ) => { it ( "should be able to mint and repay" , async ( ) => { await collateralPoolConfig . setStabilityFeeRate ( formatBytes32String ( "ibBUSD" ) , BigNumber . from ( "1000000005781378656804591713" ) ) ; const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ , debtAccumulatedRateBefore ] = await collateralPoolConfig . collateralPools ( formatBytes32String ( "ibBUSD" ) ) ; const alpacaTokenBefore = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; await TimeHelpers . increase ( TimeHelpers . duration . seconds ( ethers . BigNumber . from ( "31536000" ) ) ) ; await stabilityFeeCollector . collect ( formatBytes32String ( "ibBUSD" ) ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const baseTokenBefore = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const [ , debtAccumulatedRateAfter ] = await collateralPoolConfig . collateralPools ( formatBytes32String ( "ibBUSD" ) ) ; const wipeUnlockTokenAndConvertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeUnlockTokenAndConvert" , [ vault . address , positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 1 ) , WeiPerWad . mul ( 1 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeUnlockTokenAndConvertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeUnlockTokenAndConvertCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const baseTokenAfter = await baseToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ibBUSD" ) , positionAddress ) ; const alpacaTokenAfter = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( WeiPerWad . mul ( 1 ) ) ; expect ( baseTokenAfter . sub ( baseTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 1 ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( WeiPerWad . mul ( 1 ) ) ; AssertHelpers . assertAlmostEqual ( debtShareBefore . sub ( debtShareAfter ) . toString ( ) , "833333333333333333" ) ; expect ( alpacaTokenAfter ) . to . be . gt ( alpacaTokenBefore ) ; } ) ; } ) ; } ) ; describe ( "user mint AUSD and repay AUSD (check alpaca reward)" , ( ) => { context ( "alice mint AUSD and repay AUSD" , ( ) => { it ( "should be able to mint and repay and get alpaca reward" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ , debtAccumulatedRateBefore ] = await collateralPoolConfig . collateralPools ( formatBytes32String ( "ibBUSD" ) ) ; const alpacaTokenBefore = await alpacaToken . balanceOf ( aliceAddress ) ; const wipeUnlockTokenAndConvertCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeUnlockTokenAndConvert" , [ vault . address , positionManager . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 1 ) , WeiPerWad . mul ( 1 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeUnlockTokenAndConvertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeUnlockTokenAndConvertCall ) ; const claimFromProxyWalletCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "transfer" , [ alpacaToken . address , aliceAddress , await alpacaToken . balanceOf ( aliceProxyWallet . address ) , ] ) ; await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , claimFromProxyWalletCall ) ; const alpacaTokenAfter = await alpacaToken . balanceOf ( aliceAddress ) ; expect ( alpacaTokenAfter . sub ( alpacaTokenBefore ) ) . to . be . equal ( WeiPerWad . mul ( 4500 ) ) ; } ) ; } ) ; } ) ; describe ( "user adjust position" , ( ) => { context ( "alice convert and lock collateral" , ( ) => { it ( "should be able to adjust position" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockCaollateralBefore , debtShareBefor ] = await bookKeeper . positions ( ethers . utils . formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( lockCaollateralBefore ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; const convertAndLockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertAndLockToken" , [ vault . address , positionManager . address , ibTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , "0x" ] ) ; const convertAndLockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertAndLockTokenCall ) ; const [ lockCaollateralAfter , debtShareAfter ] = await bookKeeper . positions ( ethers . utils . formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( lockCaollateralAfter ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; } ) ; } ) ; context ( "alice convert and lock collateral and mint AUSD" , ( ) => { it ( "should be able to adjust position" , async ( ) => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockCaollateralBefore , debtShareBefor ] = await bookKeeper . positions ( ethers . utils . formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( lockCaollateralBefore ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( debtShareBefor ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; const convertLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , "0x" , ] ) ; const convertLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertLockTokenAndDrawCall ) ; const [ lockCaollateralAfter , debtShareAfter ] = await bookKeeper . positions ( ethers . utils . formatBytes32String ( "ibBUSD" ) , positionAddress ) ; expect ( lockCaollateralAfter ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; expect ( debtShareAfter ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; } ) ; } ) ; } ) ; describe ( "user openAndLock multiple positions" , ( ) => { context ( "alice openAndLock multiple position and want to claim ALPACA rewards" , ( ) => { const openWithAdapter1 = async ( ) : Promise < TransactionReceipt > => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const txReceipt = await convertOpenLockTokenAndDrawTx . wait ( ) ; return txReceipt ; } ; const openWithAdapter2 = async ( ) : Promise < TransactionReceipt > => { const convertOpenLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "convertOpenLockTokenAndDraw" , [ vault . address , positionManager . address , stabilityFeeCollector . address , ibTokenAdapter2 . address , stablecoinAdapter . address , formatBytes32String ( "ibBUSD2" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const convertOpenLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , convertOpenLockTokenAndDrawCall ) ; const txReceipt = await convertOpenLockTokenAndDrawTx . wait ( ) ; return txReceipt ; } ; it ( "should revert when harvest invalid position id" , async ( ) => { const harvestCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "harvest" , [ positionManager . address , ibTokenAdapter . address , 999 , alpacaToken . address , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , harvestCall ) ) . to . be . revertedWith ( "IbTokenAdapter/harvest-to-address-zero" ) ; const harvestMultipleCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "harvestMultiple" , [ positionManager . address , [ ibTokenAdapter . address , ibTokenAdapter . address ] , [ 888 , 999 ] , alpacaToken . address , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , harvestMultipleCall ) ) . to . be . revertedWith ( "IbTokenAdapter/harvest-to-address-zero" ) ; } ) ; it ( "alice should be able to harvest reward one position at a time" , async ( ) => { await openWithAdapter1 ( ) ; const aliceAlpacaBefore = await alpacaToken . balanceOf ( aliceAddress ) ; const proxyWalletAlpacaBefore = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; const harvestCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "harvest" , [ positionManager . address , ibTokenAdapter . address , 1 , alpacaToken . address , ] ) ; await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , harvestCall ) ; const aliceAlpacaAfter = await alpacaToken . balanceOf ( aliceAddress ) ; const proxyWalletAlpacaAfter = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; expect ( aliceAlpacaBefore ) . to . be . equal ( ethers . utils . parseEther ( "100" ) ) ; expect ( aliceAlpacaAfter ) . to . be . equal ( ethers . utils . parseEther ( "4600" ) ) ; expect ( proxyWalletAlpacaBefore ) . to . be . equal ( 0 ) ; expect ( proxyWalletAlpacaAfter ) . to . be . equal ( 0 ) ; } ) ; it ( "alice should be able to harvest reward multiple positions at a time" , async ( ) => { const block = await TimeHelpers . latestBlockNumber ( ) ; await openWithAdapter1 ( ) ; await openWithAdapter1 ( ) ; await openWithAdapter1 ( ) ; await openWithAdapter1 ( ) ; const aliceAlpacaBefore = await alpacaToken . balanceOf ( aliceAddress ) ; const proxyWalletAlpacaBefore = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; const harvestMultipleCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "harvestMultiple" , [ positionManager . address , [ ibTokenAdapter . address , ibTokenAdapter . address , ibTokenAdapter . address , ibTokenAdapter . address ] , [ 1 , 2 , 3 , 4 ] , alpacaToken . address , ] ) ; await TimeHelpers . advanceBlockTo ( block . toNumber ( ) + 100 ) ; await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , harvestMultipleCall ) ; const aliceAlpacaAfter = await alpacaToken . balanceOf ( aliceAddress ) ; const proxyWalletAlpacaAfter = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; expect ( aliceAlpacaBefore ) . to . be . equal ( ethers . utils . parseEther ( "100" ) ) ; expect ( aliceAlpacaAfter ) . to . be . equal ( ethers . utils . parseEther ( "450099.999999999982000000" ) ) ; expect ( proxyWalletAlpacaBefore ) . to . be . equal ( 0 ) ; expect ( proxyWalletAlpacaAfter ) . to . be . equal ( 0 ) ; } ) ; it ( "alice should be able to harvest reward multiple positions from different adapter" , async ( ) => { const block = await TimeHelpers . latestBlockNumber ( ) ; await openWithAdapter1 ( ) ; await openWithAdapter1 ( ) ; await openWithAdapter1 ( ) ; await openWithAdapter2 ( ) ; await openWithAdapter2 ( ) ; const aliceAlpacaBefore = await alpacaToken . balanceOf ( aliceAddress ) ; const proxyWalletAlpacaBefore = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; const harvestMultipleCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "harvestMultiple" , [ positionManager . address , [ ibTokenAdapter2 . address , ibTokenAdapter2 . address , ibTokenAdapter . address , ibTokenAdapter . address , ibTokenAdapter . address , ] , [ 4 , 5 , 1 , 2 , 3 ] , alpacaToken . address , ] ) ; await TimeHelpers . advanceBlockTo ( block . toNumber ( ) + 100 ) ; await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , harvestMultipleCall ) ; const aliceAlpacaAfter = await alpacaToken . balanceOf ( aliceAddress ) ; const proxyWalletAlpacaAfter = await alpacaToken . balanceOf ( aliceProxyWallet . address ) ; expect ( aliceAlpacaBefore ) . to . be . equal ( ethers . utils . parseEther ( "100" ) ) ; expect ( aliceAlpacaAfter ) . to . be . equal ( ethers . utils . parseEther ( "450099.999999999982000000" ) ) ; expect ( proxyWalletAlpacaBefore ) . to . be . equal ( 0 ) ; expect ( proxyWalletAlpacaAfter ) . to . be . equal ( 0 ) ; } ) ; } ) ; } ) ; } ) ; describe ( "BNB collateral pool" , ( ) => { describe ( "user opens a new position" , ( ) => { context ( "alice opens a new position" , ( ) => { it ( "alice should be able to open a position" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "WBNB" ) , aliceProxyWallet . address , ] ) ; const tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const txReceipt = await tx . wait ( ) ; const event = await getEvent ( positionManager , txReceipt . blockNumber , "LogNewPosition" ) ; expectEmit ( event , aliceProxyWallet . address , aliceProxyWallet . address , 1 ) ; expect ( await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ) . to . be . equal ( 1 ) ; } ) ; } ) ; } ) ; describe ( "user opens a new position lock BNB and mint AUSD in separated transactions" , ( ) => { context ( "alice mint AUSD over a lock collateral value" , ( ) => { it ( "should be revert" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "WBNB" ) , aliceProxyWallet . address , ] ) ; const openTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const lockCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "lockBNB" , [ positionManager . address , wbnbTokenAdapter . address , positionId , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const lockTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , lockCall , { value : WeiPerWad . mul ( 1 ) } ) ; const drawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "draw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 500 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , drawCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice opens a new position and lock BNB and mint AUSD in separated transactions" , ( ) => { it ( "alice should be able to opens a new position and lock BNB and mint AUSD in separated transactions" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "WBNB" ) , aliceProxyWallet . address , ] ) ; const openTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const lockCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "lockBNB" , [ positionManager . address , wbnbTokenAdapter . address , positionId , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const lockTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , lockCall , { value : WeiPerWad . mul ( 1 ) } ) ; const [ lockedCollateralAfterlock , debtShareAfterlock ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; expect ( lockedCollateralAfterlock . sub ( lockedCollateralBefore ) ) . to . be . equal ( WeiPerWad . mul ( 1 ) ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const drawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "draw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 200 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const drawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , drawCall ) ; const [ lockedCollateralAfterDraw , debtShareAfterDraw ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const alpacaStablecoinAfterDraw = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( debtShareAfterDraw . sub ( debtShareAfterlock ) ) . to . be . equal ( WeiPerWad . mul ( 200 ) ) ; expect ( alpacaStablecoinAfterDraw . sub ( alpacaStablecoinBefore ) ) . to . be . equal ( WeiPerWad . mul ( 200 ) ) ; } ) ; } ) ; } ) ; describe ( "user opens a new position and lock BNB and mint AUSD in single transactions" , ( ) => { context ( "alice mint AUSD over a lock collateral value" , ( ) => { it ( "should be revert" , async ( ) => { const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , WeiPerWad . mul ( 500 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : WeiPerWad . mul ( 1 ) , } ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice opens a new position and lock BNB and mint AUSD in single transactions" , ( ) => { it ( "alice should be able to opens a new position and lock BNB and mint AUSD in single transactions" , async ( ) => { const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , WeiPerWad . mul ( 200 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : WeiPerWad . mul ( 1 ) } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( lockedCollateralAfter ) . to . be . equal ( WeiPerWad . mul ( 1 ) ) ; expect ( debtShareAfter ) . to . be . equal ( WeiPerWad . mul ( 200 ) ) ; expect ( alpacaStablecoinAfter ) . to . be . equal ( WeiPerWad . mul ( 200 ) ) ; } ) ; } ) ; } ) ; describe ( "user repay some AUSD and unlock WBNB in separated transactions" , ( ) => { context ( "alice repay some AUSD and unlock WBNB in separated transactions" , ( ) => { it ( "alice should be able to repay some AUSD and unlock WBNB in separated transactions" , async ( ) => { await network . provider . send ( "hardhat_setNextBlockBaseFeePerGas" , [ "0x0" ] ) ; const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "100" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall , { gasPrice : 0 } ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bnbBefore = await alice . getBalance ( ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( parseEther ( "100" ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( parseEther ( "100" ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , positionId , parseEther ( "0.5" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall , { gasPrice : 0 } ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const bnbAfter = await alice . getBalance ( ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; expect ( bnbAfter . sub ( bnbBefore ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; } ) ; } ) ; context ( "alice repay AUSD over debt" , ( ) => { it ( "alice should be able to repay some AUSD and unlock BNB in separated transactions" , async ( ) => { await network . provider . send ( "hardhat_setNextBlockBaseFeePerGas" , [ "0x0" ] ) ; const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const openLockBNBAndDraw2Call = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDraw2Tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bookKeeperStablecoinBefore = await bookKeeper . stablecoin ( positionAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "400" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall , { gasPrice : 0 } ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const positionAddress2 = await positionManager . positions ( 1 ) ; const [ lockedCollateralAfterWipe2 , debtShareAfterWipe2 ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress2 ) ; const bookKeeperStablecoinAfterWipe = await bookKeeper . stablecoin ( positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( bookKeeperStablecoinAfterWipe . sub ( bookKeeperStablecoinBefore ) ) . to . be . equal ( parseUnits ( "200" , 45 ) ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( parseEther ( "400" ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( parseEther ( "200" ) ) ; const bnbBefore = await alice . getBalance ( ) ; const unlockBNBCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , positionId , parseEther ( "0.5" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockBNBTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockBNBCall , { gasPrice : 0 } ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const bnbAfter = await alice . getBalance ( ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; expect ( bnbAfter . sub ( bnbBefore ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; } ) ; } ) ; context ( "alice unlock WBNB over lock collateral" , ( ) => { it ( "should be revert" , async ( ) => { const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "100" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , positionId , parseEther ( "1.5" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ) . to . be . reverted ; } ) ; } ) ; context ( "alice unlock WBNB over position safe" , ( ) => { it ( "should be revert" , async ( ) => { const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "100" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall , { gasPrice : 0 } ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , positionId , parseEther ( "1" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; } ) ; describe ( "user repay some AUSD and unlock BNB in single transactions" , ( ) => { context ( "alice repay AUSD over debt" , ( ) => { it ( "alice should be able to repay some AUSD and unlock BNB in single transactions" , async ( ) => { await network . provider . send ( "hardhat_setNextBlockBaseFeePerGas" , [ "0x0" ] ) ; const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; await TimeHelpers . advanceBlock ( ) ; const openLockBNBAndDraw2Call = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDraw2Tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; await TimeHelpers . advanceBlock ( ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bookeeperStablecoinBefore = await bookKeeper . stablecoin ( positionAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const wipeAndUnlockBNBCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "0.5" ) , parseEther ( "400" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeAndUnlockBNBTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockBNBCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bookeeperStablecoinAfter = await bookKeeper . stablecoin ( positionAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( parseEther ( "400" ) ) ; expect ( bookeeperStablecoinAfter . sub ( bookeeperStablecoinBefore ) ) . to . be . equal ( parseUnits ( "200" , 45 ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( parseEther ( "200" ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; } ) ; } ) ; context ( "alice unlock BNB over lock collateral" , ( ) => { it ( "should be revert" , async ( ) => { await network . provider . send ( "hardhat_setNextBlockBaseFeePerGas" , [ "0x0" ] ) ; const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeAndUnlockBNBCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "1.5" ) , parseEther ( "100" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockBNBCall ) ) . to . be . reverted ; } ) ; } ) ; context ( "alice unlock BNB over position safe" , ( ) => { it ( "should be revert" , async ( ) => { await network . provider . send ( "hardhat_setNextBlockBaseFeePerGas" , [ "0x0" ] ) ; const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeAndUnlockBNBCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "1" ) , parseEther ( "100" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockBNBCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice repay some AUSD and unlock BNB in single transactions" , ( ) => { it ( "alice should be able to repay some AUSD and unlock BNB in single transactions" , async ( ) => { await network . provider . send ( "hardhat_setNextBlockBaseFeePerGas" , [ "0x0" ] ) ; const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const wipeAndUnlockBNBCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "0.5" ) , parseEther ( "100" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeUnlockTokenAndConvertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockBNBCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( parseEther ( "100" ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( parseEther ( "100" ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; } ) ; } ) ; } ) ; describe ( "user repay all AUSD and unlock BNB in separated transactions" , ( ) => { context ( "alice repay all AUSD and unlock BNB in separated transactions" , ( ) => { it ( "alice should be able to repay all AUSD and unlock BNB in separated transactions" , async ( ) => { await network . provider . send ( "hardhat_setNextBlockBaseFeePerGas" , [ "0x0" ] ) ; const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeAllCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAll" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAllCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( parseEther ( "200" ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( parseEther ( "200" ) ) ; const unlockBNBCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , positionId , parseEther ( "0.5" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockBNBTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockBNBCall ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; } ) ; } ) ; } ) ; describe ( "user repay all AUSD and unlock BNB in single transactions" , ( ) => { context ( "alice repay all AUSD and unlock BNB in single transactions" , ( ) => { it ( "alice should be able to repay all AUSD and unlock BNB in single transactions" , async ( ) => { const openLockBNBAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockBNBAndDraw" , [ positionManager . address , stabilityFeeCollector . address , wbnbTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "WBNB" ) , parseEther ( "200" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockBNBAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockBNBAndDrawCall , { value : parseEther ( "1" ) , gasPrice : 0 } ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; const wipeAllAndUnlockBNBCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAllAndUnlockBNB" , [ positionManager . address , wbnbTokenAdapter . address , stablecoinAdapter . address , positionId , parseEther ( "0.5" ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeUnlockTokenAndConvertTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAllAndUnlockBNBCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "WBNB" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( parseEther ( "200" ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( parseEther ( "200" ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( parseEther ( "0.5" ) ) ; } ) ; } ) ; } ) ; } ) ; describe ( "alpaca collateral pool" , ( ) => { describe ( "user opens a new position" , ( ) => { context ( "alice opens a new position" , ( ) => { it ( "alice should be able to open a position" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "ALPACA" ) , aliceProxyWallet . address , ] ) ; const tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const txReceipt = await tx . wait ( ) ; const event = await getEvent ( positionManager , txReceipt . blockNumber , "LogNewPosition" ) ; expectEmit ( event , aliceProxyWallet . address , aliceProxyWallet . address , 1 ) ; expect ( await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ) . to . be . equal ( 1 ) ; } ) ; } ) ; } ) ; describe ( "user opens a new position and lock ALPACA and mint AUSD in separated transactions" , ( ) => { context ( "alice mint AUSD over a lock collateral value" , ( ) => { it ( "should be revert" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "ALPACA" ) , aliceProxyWallet . address , ] ) ; const openTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const lockCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "lockToken" , [ positionManager . address , alpacaTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const lockTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , lockCall ) ; const drawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "draw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 150 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , drawCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice opens a new position and lock ALPACA and mint AUSD in separated transactions" , ( ) => { it ( "alice should be able to opens a new position and lock ALPACA and mint AUSD in separated transactions" , async ( ) => { const openPositionCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "open" , [ positionManager . address , formatBytes32String ( "ALPACA" ) , aliceProxyWallet . address , ] ) ; const openTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openPositionCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const tokenBefore = await alpacaToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const lockCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "lockToken" , [ positionManager . address , alpacaTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const lockTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , lockCall ) ; const tokenAfterLock = await alpacaToken . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfterlock , debtShareAfterlock ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; expect ( tokenBefore . sub ( tokenAfterLock ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( lockedCollateralAfterlock . sub ( lockedCollateralBefore ) ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const drawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "draw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 50 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const drawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , drawCall ) ; const [ lockedCollateralAfterDraw , debtShareAfterDraw ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinAfterDraw = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( debtShareAfterDraw . sub ( debtShareAfterlock ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; expect ( alpacaStablecoinAfterDraw . sub ( alpacaStablecoinBefore ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; } ) ; } ) ; } ) ; describe ( "user opens a new position and lock ALPACA and mint AUSD in single transactions" , ( ) => { context ( "alice mint AUSD over a lock collateral value" , ( ) => { it ( "should be revert" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 150 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice opens a new position and lock ALPACA and mint AUSD in single transactions" , ( ) => { it ( "alice should be able to opens a new position and lock ALPACA and mint AUSD in single transactions" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( lockedCollateralAfter ) . to . be . equal ( WeiPerWad . mul ( 10 ) ) ; expect ( debtShareAfter ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; expect ( alpacaStablecoinAfter ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; } ) ; } ) ; } ) ; describe ( "user repay some AUSD and unlock ALPACA in separated transactions" , ( ) => { context ( "alice repay some AUSD and unlock ALPACA in separated transactions" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ALPACA in separated transactions" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 30 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 30 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 30 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , alpacaTokenAdapter . address , positionId , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; } ) ; } ) ; context ( "alice repay AUSD over debt" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ALPACA in separated transactions" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const openLockTokenAndDraw2Call = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDraw2Tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const bookKeeperStablecoinBefore = await bookKeeper . stablecoin ( positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 100 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const bookKeeperStablecoinAfter = await bookKeeper . stablecoin ( positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( bookKeeperStablecoinAfter . sub ( bookKeeperStablecoinBefore ) ) . to . be . equal ( WeiPerRad . mul ( 50 ) ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 100 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , alpacaTokenAdapter . address , positionId , WeiPerWad . mul ( 5 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( WeiPerWad . mul ( 5 ) ) ; } ) ; } ) ; context ( "alice unlock ALPACA over lock collateral" , ( ) => { it ( "should be revert" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 20 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , alpacaTokenAdapter . address , positionId , WeiPerWad . mul ( 15 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ) . to . be . reverted ; } ) ; } ) ; context ( "alice unlock ALPACA over position safe" , ( ) => { it ( "should be revert" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipe" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 20 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , alpacaTokenAdapter . address , positionId , WeiPerWad . mul ( 10 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; } ) ; describe ( "user repay some AUSD and unlock ALPACA in single transactions" , ( ) => { context ( "alice repay AUSD over debt" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ALPACA in single transactions" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const openLockTokenAndDraw2Call = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDraw2Tx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bookeeperStablecoinBefore = await bookKeeper . stablecoin ( positionAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const wipeAndUnlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockToken" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 4 ) , WeiPerWad . mul ( 100 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeAndUnlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockTokenCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const bookeeperStablecoinAfter = await bookKeeper . stablecoin ( positionAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( WeiPerWad . mul ( 100 ) ) ; expect ( bookeeperStablecoinAfter . sub ( bookeeperStablecoinBefore ) ) . to . be . equal ( WeiPerRad . mul ( 50 ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( WeiPerWad . mul ( 4 ) ) ; } ) ; } ) ; context ( "alice unlock ALPACA over lock collateral" , ( ) => { it ( "should be revert" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeAndUnlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockToken" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 15 ) , WeiPerWad . mul ( 20 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockTokenCall ) ) . to . be . reverted ; } ) ; } ) ; context ( "alice unlock ALPACA over position safe" , ( ) => { it ( "should be revert" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const wipeAndUnlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockToken" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 20 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; await expect ( aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockTokenCall ) ) . to . be . revertedWith ( "BookKeeper/not-safe" ) ; } ) ; } ) ; context ( "alice repay some AUSD and unlock ALPACA in single transactions" , ( ) => { it ( "alice should be able to repay some AUSD and unlock ALPACA in single transactions" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const wipeAndUnlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAndUnlockToken" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 4 ) , WeiPerWad . mul ( 20 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeAndUnlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAndUnlockTokenCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( WeiPerWad . mul ( 20 ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( WeiPerWad . mul ( 4 ) ) ; } ) ; } ) ; } ) ; describe ( "user repay all AUSD and unlock ALPACA in separated transactions" , ( ) => { context ( "alice repay all AUSD and unlock ALPACA in separated transactions" , ( ) => { it ( "alice should be able to repay all AUSD and unlock ALPACA in separated transactions" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const wipeAllCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAll" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAllCall ) ; const [ lockedCollateralAfterWipe , debtShareAfterWipe ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const alpacaStablecoinAfterWipe = await alpacaStablecoin . balanceOf ( aliceAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; expect ( debtShareBefore . sub ( debtShareAfterWipe ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; const unlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "unlockToken" , [ positionManager . address , alpacaTokenAdapter . address , positionId , WeiPerWad . mul ( 4 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const unlockTokenTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , unlockTokenCall ) ; const [ lockedCollateralAfterUnlock , debtShareAfterUnlock ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; expect ( lockedCollateralAfterWipe . sub ( lockedCollateralAfterUnlock ) ) . to . be . equal ( WeiPerWad . mul ( 4 ) ) ; } ) ; } ) ; } ) ; describe ( "user repay all AUSD and unlock ALPACA in single transactions" , ( ) => { context ( "alice repay all AUSD and unlock ALPACA in single transactions" , ( ) => { it ( "alice should be able to repay all AUSD and unlock ALPACA in single transactions" , async ( ) => { const openLockTokenAndDrawCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "openLockTokenAndDraw" , [ positionManager . address , stabilityFeeCollector . address , alpacaTokenAdapter . address , stablecoinAdapter . address , formatBytes32String ( "ALPACA" ) , WeiPerWad . mul ( 10 ) , WeiPerWad . mul ( 50 ) , true , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const openLockTokenAndDrawTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , openLockTokenAndDrawCall ) ; const positionId = await positionManager . ownerLastPositionId ( aliceProxyWallet . address ) ; const positionAddress = await positionManager . positions ( positionId ) ; const alpacaStablecoinBefore = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralBefore , debtShareBefore ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; const wipeAllAndUnlockTokenCall = alpacaStablecoinProxyActions . interface . encodeFunctionData ( "wipeAllAndUnlockToken" , [ positionManager . address , alpacaTokenAdapter . address , stablecoinAdapter . address , positionId , WeiPerWad . mul ( 4 ) , ethers . utils . defaultAbiCoder . encode ( [ "address" ] , [ aliceAddress ] ) , ] ) ; const wipeAllAndUnlockTokentTx = await aliceProxyWallet [ "execute(address,bytes)" ] ( alpacaStablecoinProxyActions . address , wipeAllAndUnlockTokenCall ) ; const alpacaStablecoinAfter = await alpacaStablecoin . balanceOf ( aliceAddress ) ; const [ lockedCollateralAfter , debtShareAfter ] = await bookKeeper . positions ( formatBytes32String ( "ALPACA" ) , positionAddress ) ; expect ( alpacaStablecoinBefore . sub ( alpacaStablecoinAfter ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; expect ( debtShareBefore . sub ( debtShareAfter ) ) . to . be . equal ( WeiPerWad . mul ( 50 ) ) ; expect ( lockedCollateralBefore . sub ( lockedCollateralAfter ) ) . to . be . equal ( WeiPerWad . mul ( 4 ) ) ; } ) ; } ) ; } ) ; } ) ; } ) ;