diff --git a/contracts/masset/liquidator/Liquidator.sol b/contracts/masset/liquidator/Liquidator.sol index ba3ce13d..143733dd 100644 --- a/contracts/masset/liquidator/Liquidator.sol +++ b/contracts/masset/liquidator/Liquidator.sol @@ -350,27 +350,27 @@ contract Liquidator is ILiquidator, Initializable, ModuleKeysStorage, ImmutableM ); uniswapRouter.exactInput(param); - // address mAsset = liquidation.mAsset; - // // If the integration contract is connected to a mAsset like mUSD or mBTC - // if (mAsset != address(0)) { - // // 4a. Mint mAsset using purchased bAsset - // uint256 minted = _mint(bAsset, mAsset); - - // // 5a. Send to SavingsManager - // address savings = _savingsManager(); - // ISavingsManager(savings).depositLiquidation(mAsset, minted); - - // emit Liquidated(sellToken, mAsset, minted, bAsset); - // } else { - // // If a feeder pool like alUSD - // // 4b. transfer bAsset directly to the integration contract. - // // this will then increase the boosted savings vault price. - // IERC20 bAssetToken = IERC20(bAsset); - // uint256 bAssetBal = bAssetToken.balanceOf(address(this)); - // bAssetToken.transfer(_integration, bAssetBal); - - // emit Liquidated(aaveToken, mAsset, bAssetBal, bAsset); - // } + address mAsset = liquidation.mAsset; + // If the integration contract is connected to a mAsset like mUSD or mBTC + if (mAsset != address(0)) { + // 4a. Mint mAsset using purchased bAsset + uint256 minted = _mint(bAsset, mAsset); + + // 5a. Send to SavingsManager + address savings = _savingsManager(); + ISavingsManager(savings).depositLiquidation(mAsset, minted); + + emit Liquidated(sellToken, mAsset, minted, bAsset); + } else { + // If a feeder pool like alUSD + // 4b. transfer bAsset directly to the integration contract. + // this will then increase the boosted savings vault price. + IERC20 bAssetToken = IERC20(bAsset); + uint256 bAssetBal = bAssetToken.balanceOf(address(this)); + bAssetToken.transfer(_integration, bAssetBal); + + emit Liquidated(sellToken, mAsset, bAssetBal, bAsset); + } } /** diff --git a/package.json b/package.json index 6806dd89..4e32a223 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lint:fix": "yarn pretty-quick --pattern '**/*.*(sol|json)' --staged --verbose", "lint-ts": "yarn eslint ./test --ext .ts --fix --quiet", "lint-sol": "solhint 'contracts/**/*.sol'", - "compile-abis": "typechain --target=ethers-v5 --out-dir types/generated --out-dir dist/types/generated \"?(contracts|build)/!(build-info)/**/+([a-zA-Z0-9]).json\"", + "compile-abis": "typechain --target=ethers-v5 --out-dir types/generated \"?(contracts|build)/!(build-info)/**/+([a-zA-Z0-9]).json\"", "compile-ts": "npx tsc", "compile": "yarn hardhat compile --force && yarn run compile-abis", "flatten": "npx sol-merger \"./contracts/**/*.sol\" ./_flat", @@ -37,7 +37,8 @@ "test:file:fork": "yarn hardhat --config hardhat-fork.config.ts test", "test:file": "yarn hardhat test", "test:file:long": "LONG_TESTS=true yarn hardhat test", - "prepublishOnly": "yarn run compile" + "copy-types": "cp -R types/generated dist/types", + "prepublishOnly": "yarn run compile && yarn run copy-types" }, "repository": { "type": "git", diff --git a/test-fork/feeders/feeders-musd-alchemix.spec.ts b/test-fork/feeders/feeders-musd-alchemix.spec.ts index 865752ec..44eeb0f9 100644 --- a/test-fork/feeders/feeders-musd-alchemix.spec.ts +++ b/test-fork/feeders/feeders-musd-alchemix.spec.ts @@ -9,7 +9,7 @@ import { ethers, network } from "hardhat" import { deployContract } from "tasks/utils/deploy-utils" import { deployFeederPool, deployVault, FeederData, VaultData } from "tasks/utils/feederUtils" import { getChainAddress } from "tasks/utils/networkAddressFactory" -import { AAVE, ALCX, alUSD, Chain, COMP, DAI, MTA, mUSD, stkAAVE } from "tasks/utils/tokens" +import { AAVE, ALCX, alUSD, Chain, COMP, DAI, MTA, mUSD, stkAAVE, USDC } from "tasks/utils/tokens" import { AlchemixIntegration, BoostedVault, @@ -29,13 +29,6 @@ import { RewardsDistributorEth__factory } from "types/generated/factories/Reward import { IAlchemixStakingPools } from "types/generated/IAlchemixStakingPools" import { RewardsDistributorEth } from "types/generated/RewardsDistributorEth" -const governorAddress = "0xF6FF1F7FCEB2cE6d26687EaaB5988b445d0b94a2" -const deployerAddress = "0xb81473f20818225302b8fffb905b53d58a793d84" -const ethWhaleAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" -const mUsdWhaleAddress = "0x69E0E2b3d523D3b247d798a49C3fa022a46DD6bd" -const alUsdWhaleAddress = "0xf9a0106251467fff1ff03e8609aa74fc55a2a45e" -const fundManagerAddress = "0x437e8c54db5c66bb3d80d2ff156e9bfe31a017db" - const chain = Chain.mainnet const nexusAddress = getChainAddress("Nexus", chain) const delayedProxyAdminAddress = getChainAddress("DelayedProxyAdmin", chain) @@ -46,6 +39,13 @@ const uniswapRouterAddress = getChainAddress("UniswapRouterV3", chain) const uniswapQuoterAddress = getChainAddress("UniswapQuoterV3", chain) const uniswapEthToken = getChainAddress("UniswapEthToken", Chain.mainnet) +const governorAddress = getChainAddress("Governor", chain) +const fundManagerAddress = getChainAddress("FundManager", chain) +const deployerAddress = "0xb81473f20818225302b8fffb905b53d58a793d84" +const ethWhaleAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" +const mUsdWhaleAddress = "0x69E0E2b3d523D3b247d798a49C3fa022a46DD6bd" +const alUsdWhaleAddress = "0xf9a0106251467fff1ff03e8609aa74fc55a2a45e" + context("alUSD Feeder Pool integration to Alchemix", () => { let admin: Signer let deployer: Signer @@ -71,14 +71,14 @@ context("alUSD Feeder Pool integration to Alchemix", () => { const secondMintAmount = simpleToExactAmount(2000) const approveAmount = firstMintAmount.add(secondMintAmount) - before("reset block number", async () => { + const setup = async (blockNumber: number) => { await network.provider.request({ method: "hardhat_reset", params: [ { forking: { jsonRpcUrl: process.env.NODE_URL, - blockNumber: 12810000, // After Feeder Pool deployer + blockNumber, }, }, ], @@ -110,329 +110,435 @@ context("alUSD Feeder Pool integration to Alchemix", () => { poolId = (await alchemixStakingPools.tokenPoolIds(alUSD.address)).sub(1) liquidator = await Liquidator__factory.connect(liquidatorAddress, governor) rewardsDistributor = await RewardsDistributorEth__factory.connect(rewardsDistributorAddress, fundManager) - }) - it("Test connectivity", async () => { - const currentBlock = await ethers.provider.getBlockNumber() - console.log(`Current block ${currentBlock}`) - const startEther = await deployer.getBalance() - console.log(`Deployer ${deployerAddress} has ${startEther} Ether`) - }) - it("deploy alUSD Feeder Pool", async () => { - const config = { - a: BN.from(50), - limits: { - min: simpleToExactAmount(10, 16), - max: simpleToExactAmount(90, 16), - }, - } - const fpData: FeederData = { - mAsset: mUSD, - fAsset: alUSD, - name: "mUSD/alUSD Feeder Pool", - symbol: "fPmUSD/alUSD", - config, - } - alUsdFp = alUSD.feederPool - ? FeederPool__factory.connect(alUSD.feederPool, deployer) - : await deployFeederPool(deployer, fpData, chain) - - expect(await alUsdFp.name(), "name").to.eq(fpData.name) - expect(await alUsdFp.symbol(), "symbol").to.eq(fpData.symbol) - }) - it("Mint some mUSD/alUSD in the Feeder Pool", async () => { - const alUsdBassetBefore = await alUsdFp.getBasset(alusdToken.address) - const mUsdBassetBefore = await alUsdFp.getBasset(mUSD.address) - - expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD bal before").to.eq(0) - expect(await musdToken.balanceOf(alUsdFp.address), "mUSD bal before").to.eq(0) - expect(await alUsdFp.balanceOf(alUsdWhaleAddress), "whale fp bal before").to.eq(0) - - // Transfer some mUSD to the alUSD whale so they can do a mintMulti (to get the pool started) - await musdToken.connect(mUsdWhale).transfer(alUsdWhaleAddress, approveAmount) - expect(await musdToken.balanceOf(alUsdWhaleAddress), "alUsdWhale's mUSD bal after").to.gte(approveAmount) - - await alusdToken.connect(alUsdWhale).approve(alUsdFp.address, constants.MaxUint256) - await musdToken.connect(alUsdWhale).approve(alUsdFp.address, constants.MaxUint256) - expect(await alusdToken.allowance(alUsdWhaleAddress, alUsdFp.address), "alUsdWhale's alUSD bal after").to.eq(constants.MaxUint256) - expect(await musdToken.allowance(alUsdWhaleAddress, alUsdFp.address), "alUsdWhale's mUSD bal after").to.eq(constants.MaxUint256) - expect(await alusdToken.balanceOf(alUsdWhaleAddress), "alUsd whale alUSD bal before").gte(approveAmount) - expect(await musdToken.balanceOf(alUsdWhaleAddress), "alUsd whale mUSD bal before").gte(approveAmount) - - await alUsdFp - .connect(alUsdWhale) - .mintMulti( - [alusdToken.address, mUSD.address], - [firstMintAmount, firstMintAmount], - firstMintAmount.mul(2).sub(1), - alUsdWhaleAddress, - ) - - const alUsdBassetAfter = await alUsdFp.getBasset(alusdToken.address) - const mUsdBassetAfter = await alUsdFp.getBasset(mUSD.address) - expect(alUsdBassetAfter.vaultData.vaultBalance, "alUSD vault balance").to.eq( - alUsdBassetBefore.vaultData.vaultBalance.add(firstMintAmount), - ) - expect(mUsdBassetAfter.vaultData.vaultBalance, "mUSD vault balance").to.eq( - mUsdBassetBefore.vaultData.vaultBalance.add(firstMintAmount), - ) - expect(await alUsdFp.balanceOf(alUsdWhaleAddress), "whale fp bal after").to.eq(firstMintAmount.mul(2).add(1)) - }) - describe("Boosted vault for fPmUSD/alUSD Feeder Pool", () => { - it("deploy boosted staking vault", async () => { - const vaultData: VaultData = { - boosted: true, - name: "v-mUSD/alUSD fPool Vault", - symbol: "v-fPmUSD/alUSD", - priceCoeff: simpleToExactAmount(1), - stakingToken: alUsdFp.address, - rewardToken: MTA.address, - } - - vault = (await deployVault(deployer, vaultData, chain)) as BoostedVault - }) - it("Distribute MTA rewards to vault", async () => { - const distributionAmount = simpleToExactAmount(20000) - const fundManagerMtaBalBefore = await mtaToken.balanceOf(fundManagerAddress) - expect(fundManagerMtaBalBefore, "fund manager mta bal before").to.gt(distributionAmount) - - await mtaToken.connect(fundManager).approve(rewardsDistributor.address, distributionAmount) - await rewardsDistributor.connect(fundManager).distributeRewards([vault.address], [distributionAmount]) - - expect(await mtaToken.balanceOf(fundManagerAddress), "fund manager mta bal before").to.eq( - fundManagerMtaBalBefore.sub(distributionAmount), - ) - }) - it("stake fPmUSD/alUSD in vault", async () => { - const stakeAmount = simpleToExactAmount(1000) - expect(await vault.balanceOf(alUsdWhaleAddress), "whale v-fp bal before").to.eq(0) - - await alUsdFp.connect(alUsdWhale).approve(vault.address, stakeAmount) - await vault.connect(alUsdWhale)["stake(uint256)"](stakeAmount) + } - expect(await vault.balanceOf(alUsdWhaleAddress), "whale v-fp bal after").to.eq(stakeAmount) + context("After Feeder Pool deployed but not integration or vault", () => { + before("reset block number", async () => { + // After Feeder Pool deployed but before the Alchemix integration and vault contracts were deployed + await setup(12810000) }) - it("whale claims MTA from vault", async () => { - await increaseTime(ONE_DAY.mul(5)) - expect(await mtaToken.balanceOf(alUsdWhaleAddress), "whale mta bal before").to.eq(0) - - await vault.connect(alUsdWhale).claimReward() - - expect(await mtaToken.balanceOf(alUsdWhaleAddress), "whale mta bal after").to.gt(0) + it("Test connectivity", async () => { + const currentBlock = await ethers.provider.getBlockNumber() + console.log(`Current block ${currentBlock}`) + const startEther = await deployer.getBalance() + console.log(`Deployer ${deployerAddress} has ${startEther} Ether`) }) - }) - describe("Integration", () => { - it("deploy Alchemix integration", async () => { - alchemixIntegration = await deployContract( - new AlchemixIntegration__factory(deployer), - "Alchemix alUSD Integration", - [nexusAddress, alUsdFp.address, ALCX.address, alchemixStakingPoolsAddress, alUSD.address], - ) + it("deploy alUSD Feeder Pool", async () => { + const config = { + a: BN.from(50), + limits: { + min: simpleToExactAmount(10, 16), + max: simpleToExactAmount(90, 16), + }, + } + const fpData: FeederData = { + mAsset: mUSD, + fAsset: alUSD, + name: "mUSD/alUSD Feeder Pool", + symbol: "fPmUSD/alUSD", + config, + } + alUsdFp = alUSD.feederPool + ? FeederPool__factory.connect(alUSD.feederPool, deployer) + : await deployFeederPool(deployer, fpData, chain) - expect(await alchemixIntegration.nexus(), "nexus").to.eq(nexusAddress) - expect(await alchemixIntegration.lpAddress(), "lp (feeder pool)").to.eq(alUsdFp.address) - expect(await alchemixIntegration.rewardToken(), "rewards token").to.eq(ALCX.address) - expect(await alchemixIntegration.stakingPools(), "Alchemix staking pools").to.eq(alchemixStakingPoolsAddress) - expect(await alchemixIntegration.poolId(), "pool id").to.eq(0) - expect(await alchemixIntegration.bAsset(), "bAsset").to.eq(alUSD.address) - }) - it("initialize Alchemix integration", async () => { - expect( - await alusdToken.allowance(alchemixIntegration.address, alchemixStakingPools.address), - "integration alUSD allowance before", - ).to.eq(0) - expect(await alcxToken.allowance(alchemixIntegration.address, liquidatorAddress), "integration ALCX allowance before").to.eq(0) - - await alchemixIntegration.initialize() - - expect( - await alusdToken.allowance(alchemixIntegration.address, alchemixStakingPools.address), - "integration alUSD allowance after", - ).to.eq(MAX_UINT256) - expect(await alcxToken.allowance(alchemixIntegration.address, liquidatorAddress), "integration ALCX allowance after").to.eq( - MAX_UINT256, - ) - }) - it("Migrate alUSD Feeder Pool to the Alchemix integration", async () => { - expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD bal before").to.eq(firstMintAmount) - expect(await alusdToken.balanceOf(alchemixIntegration.address), "alUSD integration bal before").to.eq(0) - expect(await musdToken.balanceOf(alUsdFp.address), "mUSD bal before").to.eq(firstMintAmount) - - await alUsdFp.connect(governor).migrateBassets([alusdToken.address], alchemixIntegration.address) - - // The migration just moves the alUSD to the integration contract. It is not deposited into the staking pool yet. - expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD fp bal after").to.eq(0) - expect(await alusdToken.balanceOf(alchemixIntegration.address), "alUSD integration bal after").to.eq(firstMintAmount) - expect(await musdToken.balanceOf(alUsdFp.address), "mUSD bal after").to.eq(firstMintAmount) - expect( - await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), - "integration's alUSD deposited after", - ).to.eq(0) - expect( - await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), - "integration's accrued ALCX after", - ).to.eq(0) + expect(await alUsdFp.name(), "name").to.eq(fpData.name) + expect(await alUsdFp.symbol(), "symbol").to.eq(fpData.symbol) }) it("Mint some mUSD/alUSD in the Feeder Pool", async () => { const alUsdBassetBefore = await alUsdFp.getBasset(alusdToken.address) const mUsdBassetBefore = await alUsdFp.getBasset(mUSD.address) - expect( - await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), - "integration's alUSD deposited before", - ).to.eq(0) - expect( - await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), - "integration's accrued ALCX before", - ).to.eq(0) + expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD bal before").to.eq(0) + expect(await musdToken.balanceOf(alUsdFp.address), "mUSD bal before").to.eq(0) + expect(await alUsdFp.balanceOf(alUsdWhaleAddress), "whale fp bal before").to.eq(0) + + // Transfer some mUSD to the alUSD whale so they can do a mintMulti (to get the pool started) + await musdToken.connect(mUsdWhale).transfer(alUsdWhaleAddress, approveAmount) + expect(await musdToken.balanceOf(alUsdWhaleAddress), "alUsdWhale's mUSD bal after").to.gte(approveAmount) + + await alusdToken.connect(alUsdWhale).approve(alUsdFp.address, constants.MaxUint256) + await musdToken.connect(alUsdWhale).approve(alUsdFp.address, constants.MaxUint256) + expect(await alusdToken.allowance(alUsdWhaleAddress, alUsdFp.address), "alUsdWhale's alUSD bal after").to.eq( + constants.MaxUint256, + ) + expect(await musdToken.allowance(alUsdWhaleAddress, alUsdFp.address), "alUsdWhale's mUSD bal after").to.eq(constants.MaxUint256) + expect(await alusdToken.balanceOf(alUsdWhaleAddress), "alUsd whale alUSD bal before").gte(approveAmount) + expect(await musdToken.balanceOf(alUsdWhaleAddress), "alUsd whale mUSD bal before").gte(approveAmount) await alUsdFp .connect(alUsdWhale) .mintMulti( [alusdToken.address, mUSD.address], - [secondMintAmount, secondMintAmount], - secondMintAmount.mul(2).sub(1), + [firstMintAmount, firstMintAmount], + firstMintAmount.mul(2).sub(1), alUsdWhaleAddress, ) const alUsdBassetAfter = await alUsdFp.getBasset(alusdToken.address) const mUsdBassetAfter = await alUsdFp.getBasset(mUSD.address) - expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD fp bal after").to.eq(0) - expect(alUsdBassetAfter.vaultData.vaultBalance, "alUSD vault balance after").to.eq(approveAmount) - expect(mUsdBassetAfter.vaultData.vaultBalance, "mUSD vault balance after").to.eq(approveAmount) - const cacheAmount = simpleToExactAmount(1000) - expect(await alusdToken.balanceOf(alchemixIntegration.address), "alUSD integration bal after").to.eq(cacheAmount) - expect( - await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), - "integration's alUSD deposited after", - ).to.eq(mUsdBassetBefore.vaultData.vaultBalance.add(secondMintAmount).sub(cacheAmount)) - expect( - await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), - "integration's accrued ALCX after", - ).to.eq(0) - }) - it("accrue ALCX", async () => { - expect( - await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), - "integration's accrued ALCX before", - ).to.eq(0) - - await increaseTime(ONE_WEEK) - - expect( - await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), - "integration's accrued ALCX after", - ).to.gt(simpleToExactAmount(1, 12)) + expect(alUsdBassetAfter.vaultData.vaultBalance, "alUSD vault balance").to.eq( + alUsdBassetBefore.vaultData.vaultBalance.add(firstMintAmount), + ) + expect(mUsdBassetAfter.vaultData.vaultBalance, "mUSD vault balance").to.eq( + mUsdBassetBefore.vaultData.vaultBalance.add(firstMintAmount), + ) + expect(await alUsdFp.balanceOf(alUsdWhaleAddress), "whale fp bal after").to.eq(firstMintAmount.mul(2).add(1)) }) - it("redeem a lot of alUSD", async () => { - expect( - await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), - "integration's accrued ALCX before", - ).to.gt(simpleToExactAmount(1, 12)) - expect(await alcxToken.balanceOf(alchemixIntegration.address), "integration ALCX bal before").to.eq(0) + describe("Boosted vault for fPmUSD/alUSD Feeder Pool", () => { + it("deploy boosted staking vault", async () => { + const vaultData: VaultData = { + boosted: true, + name: "v-mUSD/alUSD fPool Vault", + symbol: "v-fPmUSD/alUSD", + priceCoeff: simpleToExactAmount(1), + stakingToken: alUsdFp.address, + rewardToken: MTA.address, + } + + vault = (await deployVault(deployer, vaultData, chain)) as BoostedVault + }) + it("Distribute MTA rewards to vault", async () => { + const distributionAmount = simpleToExactAmount(20000) + const fundManagerMtaBalBefore = await mtaToken.balanceOf(fundManagerAddress) + expect(fundManagerMtaBalBefore, "fund manager mta bal before").to.gt(distributionAmount) + + await mtaToken.connect(fundManager).approve(rewardsDistributor.address, distributionAmount) + await rewardsDistributor.connect(fundManager).distributeRewards([vault.address], [distributionAmount]) + + expect(await mtaToken.balanceOf(fundManagerAddress), "fund manager mta bal before").to.eq( + fundManagerMtaBalBefore.sub(distributionAmount), + ) + }) + it("stake fPmUSD/alUSD in vault", async () => { + const stakeAmount = simpleToExactAmount(1000) + expect(await vault.balanceOf(alUsdWhaleAddress), "whale v-fp bal before").to.eq(0) - const redeemAmount = simpleToExactAmount(8000) - await alUsdFp.connect(alUsdWhale).redeemExactBassets([alUSD.address], [redeemAmount], firstMintAmount, alUsdWhaleAddress) + await alUsdFp.connect(alUsdWhale).approve(vault.address, stakeAmount) + await vault.connect(alUsdWhale)["stake(uint256)"](stakeAmount) - const alUsdBassetAfter = await alUsdFp.getBasset(alusdToken.address) - expect(alUsdBassetAfter.vaultData.vaultBalance, "alUSD vault balance").to.eq(approveAmount.sub(redeemAmount)) - const integrationAlusdBalance = await alusdToken.balanceOf(alchemixIntegration.address) - expect(integrationAlusdBalance, "alUSD in cache").to.gt(0) - expect( - await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), - "integration's alUSD deposited after", - ).to.eq(approveAmount.sub(redeemAmount).sub(integrationAlusdBalance)) - // The withdraw from the staking pool sends accrued ALCX rewards to the integration contract - expect( - await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), - "integration's accrued ALCX after", - ).to.eq(0) - expect(await alcxToken.balanceOf(alchemixIntegration.address), "integration ALCX bal after").to.gt(simpleToExactAmount(1, 12)) - }) - }) - describe("liquidator", () => { - let newLiquidatorImpl: Liquidator - it("deploy new liquidator", async () => { - newLiquidatorImpl = await deployContract(new Liquidator__factory(deployer), "Liquidator", [ - nexusAddress, - stkAAVE.address, - AAVE.address, - uniswapRouterAddress, - uniswapQuoterAddress, - COMP.address, - ALCX.address, - ]) - - expect(await newLiquidatorImpl.nexus(), "nexus").to.eq(nexusAddress) - expect(await newLiquidatorImpl.stkAave(), "stkAave").to.eq(stkAAVE.address) - expect(await newLiquidatorImpl.aaveToken(), "aaveToken").to.eq(AAVE.address) - expect(await newLiquidatorImpl.uniswapRouter(), "uniswapRouter").to.eq(uniswapRouterAddress) - expect(await newLiquidatorImpl.uniswapQuoter(), "uniswapQuoter").to.eq(uniswapQuoterAddress) - expect(await newLiquidatorImpl.compToken(), "compToken").to.eq(COMP.address) - expect(await newLiquidatorImpl.alchemixToken(), "alchemixToken").to.eq(ALCX.address) - }) - it("Update the Liquidator proxy", async () => { - const liquidatorProxy = LiquidatorProxy__factory.connect(liquidatorAddress, admin) - expect(await liquidatorProxy.callStatic.admin(), "proxy admin before").to.eq(delayedProxyAdminAddress) - expect(await liquidatorProxy.callStatic.implementation(), "liquidator impl address before").to.not.eq(newLiquidatorImpl.address) - expect(await alcxToken.allowance(liquidator.address, uniswapRouterAddress), "ALCX allowance before").to.eq(0) - - // Update the Liquidator proxy to point to the new implementation using the delayed proxy admin - const data = newLiquidatorImpl.interface.encodeFunctionData("upgrade") - await delayedProxyAdmin.proposeUpgrade(liquidatorAddress, newLiquidatorImpl.address, data) - await increaseTime(ONE_WEEK.add(60)) - await delayedProxyAdmin.acceptUpgradeRequest(liquidatorAddress) - - expect(await liquidatorProxy.callStatic.implementation(), "liquidator impl address after").to.eq(newLiquidatorImpl.address) - expect(await alcxToken.allowance(liquidator.address, uniswapRouterAddress), "ALCX allowance after").to.eq(MAX_UINT256) - }) - it("create liquidation of ALCX", async () => { - const uniswapPath = encodeUniswapPath([ALCX.address, uniswapEthToken, DAI.address, alUSD.address], [10000, 3000, 500]) - await liquidator.createLiquidation( - alchemixIntegration.address, - ALCX.address, - alUSD.address, - uniswapPath.encoded, - uniswapPath.encodedReversed, - simpleToExactAmount(5000), - simpleToExactAmount(200), - ZERO_ADDRESS, - false, - ) + expect(await vault.balanceOf(alUsdWhaleAddress), "whale v-fp bal after").to.eq(stakeAmount) + }) + it("whale claims MTA from vault", async () => { + await increaseTime(ONE_DAY.mul(5)) + expect(await mtaToken.balanceOf(alUsdWhaleAddress), "whale mta bal before").to.eq(0) + + await vault.connect(alUsdWhale).claimReward() + + expect(await mtaToken.balanceOf(alUsdWhaleAddress), "whale mta bal after").to.gt(0) + }) }) - it("Claim accrued ALCX using integration contract", async () => { - await increaseTime(ONE_WEEK) - - const unclaimedAlcxBefore = await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId) - expect(unclaimedAlcxBefore, "some ALCX before").to.gt(0) - const integrationAlcxBalanceBefore = await alcxToken.balanceOf(alchemixIntegration.address) - - await alchemixIntegration.claimRewards() - - expect(await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), "unclaimed ALCX after").to.eq(0) - const integrationAlcxBalanceAfter = await alcxToken.balanceOf(alchemixIntegration.address) - expect(integrationAlcxBalanceAfter, "more ALCX").to.gt(integrationAlcxBalanceBefore) - // TODO why can't I get the correct amount? - // console.log( - // `${await alcxToken.balanceOf( - // alchemixIntegration.address, - // )} integration after = ${integrationAlcxBalanceBefore} integration before + ${unclaimedAlcxBefore}`, - // ) - // expect(await alcxToken.balanceOf(alchemixIntegration.address), "claimed ALCX").to.eq( - // integrationAlcxBalanceBefore.add(unclaimedAlcxBefore), - // ) + describe("Integration", () => { + it("deploy Alchemix integration", async () => { + alchemixIntegration = await deployContract( + new AlchemixIntegration__factory(deployer), + "Alchemix alUSD Integration", + [nexusAddress, alUsdFp.address, ALCX.address, alchemixStakingPoolsAddress, alUSD.address], + ) + + expect(await alchemixIntegration.nexus(), "nexus").to.eq(nexusAddress) + expect(await alchemixIntegration.lpAddress(), "lp (feeder pool)").to.eq(alUsdFp.address) + expect(await alchemixIntegration.rewardToken(), "rewards token").to.eq(ALCX.address) + expect(await alchemixIntegration.stakingPools(), "Alchemix staking pools").to.eq(alchemixStakingPoolsAddress) + expect(await alchemixIntegration.poolId(), "pool id").to.eq(0) + expect(await alchemixIntegration.bAsset(), "bAsset").to.eq(alUSD.address) + }) + it("initialize Alchemix integration", async () => { + expect( + await alusdToken.allowance(alchemixIntegration.address, alchemixStakingPools.address), + "integration alUSD allowance before", + ).to.eq(0) + expect( + await alcxToken.allowance(alchemixIntegration.address, liquidatorAddress), + "integration ALCX allowance before", + ).to.eq(0) + + await alchemixIntegration.initialize() + + expect( + await alusdToken.allowance(alchemixIntegration.address, alchemixStakingPools.address), + "integration alUSD allowance after", + ).to.eq(MAX_UINT256) + expect(await alcxToken.allowance(alchemixIntegration.address, liquidatorAddress), "integration ALCX allowance after").to.eq( + MAX_UINT256, + ) + }) + it("Migrate alUSD Feeder Pool to the Alchemix integration", async () => { + expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD bal before").to.eq(firstMintAmount) + expect(await alusdToken.balanceOf(alchemixIntegration.address), "alUSD integration bal before").to.eq(0) + expect(await musdToken.balanceOf(alUsdFp.address), "mUSD bal before").to.eq(firstMintAmount) + + await alUsdFp.connect(governor).migrateBassets([alusdToken.address], alchemixIntegration.address) + + // The migration just moves the alUSD to the integration contract. It is not deposited into the staking pool yet. + expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD fp bal after").to.eq(0) + expect(await alusdToken.balanceOf(alchemixIntegration.address), "alUSD integration bal after").to.eq(firstMintAmount) + expect(await musdToken.balanceOf(alUsdFp.address), "mUSD bal after").to.eq(firstMintAmount) + expect( + await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), + "integration's alUSD deposited after", + ).to.eq(0) + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "integration's accrued ALCX after", + ).to.eq(0) + }) + it("Mint some mUSD/alUSD in the Feeder Pool", async () => { + const alUsdBassetBefore = await alUsdFp.getBasset(alusdToken.address) + const mUsdBassetBefore = await alUsdFp.getBasset(mUSD.address) + + expect( + await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), + "integration's alUSD deposited before", + ).to.eq(0) + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "integration's accrued ALCX before", + ).to.eq(0) + + await alUsdFp + .connect(alUsdWhale) + .mintMulti( + [alusdToken.address, mUSD.address], + [secondMintAmount, secondMintAmount], + secondMintAmount.mul(2).sub(1), + alUsdWhaleAddress, + ) + + const alUsdBassetAfter = await alUsdFp.getBasset(alusdToken.address) + const mUsdBassetAfter = await alUsdFp.getBasset(mUSD.address) + expect(await alusdToken.balanceOf(alUsdFp.address), "alUSD fp bal after").to.eq(0) + expect(alUsdBassetAfter.vaultData.vaultBalance, "alUSD vault balance after").to.eq(approveAmount) + expect(mUsdBassetAfter.vaultData.vaultBalance, "mUSD vault balance after").to.eq(approveAmount) + const cacheAmount = simpleToExactAmount(1000) + expect(await alusdToken.balanceOf(alchemixIntegration.address), "alUSD integration bal after").to.eq(cacheAmount) + expect( + await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), + "integration's alUSD deposited after", + ).to.eq(mUsdBassetBefore.vaultData.vaultBalance.add(secondMintAmount).sub(cacheAmount)) + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "integration's accrued ALCX after", + ).to.eq(0) + }) + it("accrue ALCX", async () => { + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "integration's accrued ALCX before", + ).to.eq(0) + + await increaseTime(ONE_WEEK) + + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "integration's accrued ALCX after", + ).to.gt(simpleToExactAmount(1, 12)) + }) + it("redeem a lot of alUSD", async () => { + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "integration's accrued ALCX before", + ).to.gt(simpleToExactAmount(1, 12)) + expect(await alcxToken.balanceOf(alchemixIntegration.address), "integration ALCX bal before").to.eq(0) + + const redeemAmount = simpleToExactAmount(8000) + await alUsdFp.connect(alUsdWhale).redeemExactBassets([alUSD.address], [redeemAmount], firstMintAmount, alUsdWhaleAddress) + + const alUsdBassetAfter = await alUsdFp.getBasset(alusdToken.address) + expect(alUsdBassetAfter.vaultData.vaultBalance, "alUSD vault balance").to.eq(approveAmount.sub(redeemAmount)) + const integrationAlusdBalance = await alusdToken.balanceOf(alchemixIntegration.address) + expect(integrationAlusdBalance, "alUSD in cache").to.gt(0) + expect( + await alchemixStakingPools.getStakeTotalDeposited(alchemixIntegration.address, poolId), + "integration's alUSD deposited after", + ).to.eq(approveAmount.sub(redeemAmount).sub(integrationAlusdBalance)) + // The withdraw from the staking pool sends accrued ALCX rewards to the integration contract + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "integration's accrued ALCX after", + ).to.eq(0) + expect(await alcxToken.balanceOf(alchemixIntegration.address), "integration ALCX bal after").to.gt( + simpleToExactAmount(1, 12), + ) + }) }) - it("trigger ALCX liquidation", async () => { - await liquidator.triggerLiquidation(alchemixIntegration.address) + describe("liquidator", () => { + let newLiquidatorImpl: Liquidator + it("deploy new liquidator", async () => { + newLiquidatorImpl = await deployContract(new Liquidator__factory(deployer), "Liquidator", [ + nexusAddress, + stkAAVE.address, + AAVE.address, + uniswapRouterAddress, + uniswapQuoterAddress, + COMP.address, + ALCX.address, + ]) + + expect(await newLiquidatorImpl.nexus(), "nexus").to.eq(nexusAddress) + expect(await newLiquidatorImpl.stkAave(), "stkAave").to.eq(stkAAVE.address) + expect(await newLiquidatorImpl.aaveToken(), "aaveToken").to.eq(AAVE.address) + expect(await newLiquidatorImpl.uniswapRouter(), "uniswapRouter").to.eq(uniswapRouterAddress) + expect(await newLiquidatorImpl.uniswapQuoter(), "uniswapQuoter").to.eq(uniswapQuoterAddress) + expect(await newLiquidatorImpl.compToken(), "compToken").to.eq(COMP.address) + expect(await newLiquidatorImpl.alchemixToken(), "alchemixToken").to.eq(ALCX.address) + }) + it("Upgrade the Liquidator proxy", async () => { + const liquidatorProxy = LiquidatorProxy__factory.connect(liquidatorAddress, admin) + expect(await liquidatorProxy.callStatic.admin(), "proxy admin before").to.eq(delayedProxyAdminAddress) + expect(await liquidatorProxy.callStatic.implementation(), "liquidator impl address before").to.not.eq( + newLiquidatorImpl.address, + ) + expect(await alcxToken.allowance(liquidator.address, uniswapRouterAddress), "ALCX allowance before").to.eq(0) + + // Update the Liquidator proxy to point to the new implementation using the delayed proxy admin + const data = newLiquidatorImpl.interface.encodeFunctionData("upgrade") + await delayedProxyAdmin.proposeUpgrade(liquidatorAddress, newLiquidatorImpl.address, data) + await increaseTime(ONE_WEEK.add(60)) + await delayedProxyAdmin.acceptUpgradeRequest(liquidatorAddress) + + expect(await liquidatorProxy.callStatic.implementation(), "liquidator impl address after").to.eq(newLiquidatorImpl.address) + expect(await alcxToken.allowance(liquidator.address, uniswapRouterAddress), "ALCX allowance after").to.eq(MAX_UINT256) + }) + it("create liquidation of ALCX", async () => { + const uniswapPath = encodeUniswapPath([ALCX.address, uniswapEthToken, DAI.address, alUSD.address], [10000, 3000, 500]) + await liquidator.createLiquidation( + alchemixIntegration.address, + ALCX.address, + alUSD.address, + uniswapPath.encoded, + uniswapPath.encodedReversed, + simpleToExactAmount(5000), + simpleToExactAmount(200), + ZERO_ADDRESS, + false, + ) + }) + it("Claim accrued ALCX using integration contract", async () => { + await increaseTime(ONE_WEEK) + + const unclaimedAlcxBefore = await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId) + expect(unclaimedAlcxBefore, "some ALCX before").to.gt(0) + const integrationAlcxBalanceBefore = await alcxToken.balanceOf(alchemixIntegration.address) + + await alchemixIntegration.claimRewards() + + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "unclaimed ALCX after", + ).to.eq(0) + const integrationAlcxBalanceAfter = await alcxToken.balanceOf(alchemixIntegration.address) + expect(integrationAlcxBalanceAfter, "more ALCX").to.gt(integrationAlcxBalanceBefore) + expect(await alcxToken.balanceOf(alchemixIntegration.address), "claimed ALCX").to.gte( + integrationAlcxBalanceBefore.add(unclaimedAlcxBefore), + ) + }) + it("trigger ALCX liquidation", async () => { + await liquidator.triggerLiquidation(alchemixIntegration.address) + }) + it("trigger COMP liquidation", async () => { + await liquidator.triggerLiquidation(USDC.integrator) + }) + it("claim and liquidate stkAAVE", async () => { + await liquidator.claimStakedAave() + await increaseTime(ONE_DAY.mul(11)) + await liquidator.triggerLiquidationAave() + }) }) - it("trigger COMP liquidation", async () => { - const compIntegrationAddress = "0xD55684f4369040C12262949Ff78299f2BC9dB735" - await liquidator.triggerLiquidation(compIntegrationAddress) + }) + context("Before liquidator upgrade", () => { + before("reset block number", async () => { + // 14 July after alUSD Feeder Pool and integration is live + await setup(12823000) + + alchemixIntegration = AlchemixIntegration__factory.connect(alUSD.integrator, deployer) }) - it("claim and liquidate stkAAVE", async () => { - await liquidator.claimStakedAave() - await increaseTime(ONE_DAY.mul(11)) - await liquidator.triggerLiquidationAave() + describe("liquidator", () => { + let newLiquidatorImpl: Liquidator + it("deploy new liquidator", async () => { + newLiquidatorImpl = await deployContract(new Liquidator__factory(deployer), "Liquidator", [ + nexusAddress, + stkAAVE.address, + AAVE.address, + uniswapRouterAddress, + uniswapQuoterAddress, + COMP.address, + ALCX.address, + ]) + + expect(await newLiquidatorImpl.nexus(), "nexus").to.eq(nexusAddress) + expect(await newLiquidatorImpl.stkAave(), "stkAave").to.eq(stkAAVE.address) + expect(await newLiquidatorImpl.aaveToken(), "aaveToken").to.eq(AAVE.address) + expect(await newLiquidatorImpl.uniswapRouter(), "uniswapRouter").to.eq(uniswapRouterAddress) + expect(await newLiquidatorImpl.uniswapQuoter(), "uniswapQuoter").to.eq(uniswapQuoterAddress) + expect(await newLiquidatorImpl.compToken(), "compToken").to.eq(COMP.address) + expect(await newLiquidatorImpl.alchemixToken(), "alchemixToken").to.eq(ALCX.address) + }) + it("Upgrade the Liquidator proxy", async () => { + const liquidatorProxy = LiquidatorProxy__factory.connect(liquidatorAddress, admin) + expect(await liquidatorProxy.callStatic.admin(), "proxy admin before").to.eq(delayedProxyAdminAddress) + expect(await liquidatorProxy.callStatic.implementation(), "liquidator impl address before").to.not.eq( + newLiquidatorImpl.address, + ) + expect(await alcxToken.allowance(liquidator.address, uniswapRouterAddress), "ALCX allowance before").to.eq(0) + + // Update the Liquidator proxy to point to the new implementation using the delayed proxy admin + const data = newLiquidatorImpl.interface.encodeFunctionData("upgrade") + await delayedProxyAdmin.proposeUpgrade(liquidatorAddress, newLiquidatorImpl.address, data) + await increaseTime(ONE_WEEK.add(60)) + await delayedProxyAdmin.acceptUpgradeRequest(liquidatorAddress) + + expect(await liquidatorProxy.callStatic.implementation(), "liquidator impl address after").to.eq(newLiquidatorImpl.address) + expect(await alcxToken.allowance(liquidator.address, uniswapRouterAddress), "ALCX allowance after").to.eq(MAX_UINT256) + }) + it("create liquidation of ALCX", async () => { + const uniswapPath = encodeUniswapPath([ALCX.address, uniswapEthToken, DAI.address, alUSD.address], [10000, 3000, 500]) + await liquidator.createLiquidation( + alchemixIntegration.address, + ALCX.address, + alUSD.address, + uniswapPath.encoded, + uniswapPath.encodedReversed, + simpleToExactAmount(5000), + simpleToExactAmount(200), + ZERO_ADDRESS, + false, + ) + }) + it("Claim accrued ALCX using integration contract", async () => { + const unclaimedAlcxBefore = await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId) + expect(unclaimedAlcxBefore, "some ALCX before").to.gt(0) + const integrationAlcxBalanceBefore = await alcxToken.balanceOf(alchemixIntegration.address) + + await alchemixIntegration.claimRewards() + + expect( + await alchemixStakingPools.getStakeTotalUnclaimed(alchemixIntegration.address, poolId), + "unclaimed ALCX after", + ).to.eq(0) + const integrationAlcxBalanceAfter = await alcxToken.balanceOf(alchemixIntegration.address) + expect(integrationAlcxBalanceAfter, "more ALCX").to.gt(integrationAlcxBalanceBefore) + // TODO why can't I get the correct amount? + console.log( + `${await alcxToken.balanceOf( + alchemixIntegration.address, + )} integration after = ${integrationAlcxBalanceBefore} integration before + ${unclaimedAlcxBefore}`, + ) + expect(await alcxToken.balanceOf(alchemixIntegration.address), "claimed ALCX").to.gte( + integrationAlcxBalanceBefore.add(unclaimedAlcxBefore), + ) + }) + it("trigger ALCX liquidation", async () => { + await liquidator.triggerLiquidation(alchemixIntegration.address) + }) + it("trigger COMP liquidation", async () => { + await liquidator.triggerLiquidation(USDC.integrator) + }) + it("claim and liquidate stkAAVE", async () => { + // Have already waited 7 days for the proxy upgrade so the stkAAVE should be ready to redeem + await liquidator.triggerLiquidationAave() + await liquidator.claimStakedAave() + }) }) }) - // Add update of liquidator after alUSD FP and Alchemic integration has already been deployed }) diff --git a/test/masset/liquidator.spec.ts b/test/masset/liquidator.spec.ts index 3feda73b..524ce4a0 100644 --- a/test/masset/liquidator.spec.ts +++ b/test/masset/liquidator.spec.ts @@ -26,6 +26,7 @@ import { } from "types/generated" import { increaseTime } from "@utils/time" import { EncodedPaths, encodeUniswapPath } from "@utils/peripheral/uniswap" +import { assertBNClose } from "@utils/assertions" import { shouldBehaveLikeModule, IModuleBehaviourContext } from "../shared/Module.behaviour" describe("Liquidator", () => { @@ -65,6 +66,7 @@ describe("Liquidator", () => { interface Data { sellTokenBalance: Balance + buyTokenBalance: Balance savingsManagerBal: BN liquidation: Liquidation } @@ -144,6 +146,10 @@ describe("Liquidator", () => { integration: sellBalIntegration, liquidator: sellBalLiquidator, }, + buyTokenBalance: { + integration: await bAsset.balanceOf(compIntegration.address), + liquidator: await bAsset.balanceOf(liquidator.address), + }, savingsManagerBal, liquidation, } @@ -478,7 +484,7 @@ describe("Liquidator", () => { }) it("should update the bAsset successfully", async () => { const validPath = encodeUniswapPath([compToken.address, DEAD_ADDRESS, bAsset2.address], [3000, 3000]) - // update uniswap path, bAsset, tranch amount + // update uniswap path, bAsset, tranche amount const tx = liquidator .connect(sa.governor.signer) .updateBasset( @@ -495,6 +501,25 @@ describe("Liquidator", () => { expect(liquidation.bAsset).eq(bAsset2.address) expect(liquidation.trancheAmount).eq(simpleToExactAmount(123, 18)) }) + it("should update with longer uniswap path", async () => { + const longerPath = encodeUniswapPath([compToken.address, DEAD_ADDRESS, bAsset.address, bAsset2.address], [10000, 3000, 500]) + // update uniswap path + const tx = liquidator + .connect(sa.governor.signer) + .updateBasset( + compIntegration.address, + bAsset2.address, + longerPath.encoded, + longerPath.encodedReversed, + simpleToExactAmount(123, 18), + simpleToExactAmount(70, 18), + ) + await expect(tx).to.emit(liquidator, "LiquidationModified").withArgs(compIntegration.address) + const liquidation = await liquidator.liquidations(compIntegration.address) + expect(liquidation.sellToken).eq(compToken.address) + expect(liquidation.bAsset).eq(bAsset2.address) + expect(liquidation.trancheAmount).eq(simpleToExactAmount(123, 18)) + }) }) describe("removing the liquidation altogether", () => { it("should fail if liquidation doesn't exist", async () => { @@ -511,7 +536,7 @@ describe("Liquidator", () => { }) }) }) - context("triggering a Compound liquidation", () => { + context("triggering a liquidation for a mAsset", () => { beforeEach(async () => { await redeployLiquidator() await liquidator @@ -540,22 +565,30 @@ describe("Liquidator", () => { }) it("should sell everything if the liquidator has less balance than tranche size", async () => { const s0 = await snapshotData() - await liquidator - .connect(sa.governor.signer) - .updateBasset( - compIntegration.address, - bAsset.address, - uniswapCompBassetPaths.encoded, - uniswapCompBassetPaths.encodedReversed, - simpleToExactAmount(1, 30), - simpleToExactAmount(70, 18), - ) - // set tranche size to 1e30 - await liquidator.triggerLiquidation(compIntegration.address) + expect(s0.sellTokenBalance.integration, "integration COMP bal before").to.eq(simpleToExactAmount(10)) + expect(s0.sellTokenBalance.liquidator, "liquidator COMP bal before").to.eq(0) + await liquidator.connect(sa.governor.signer).updateBasset( + compIntegration.address, + bAsset.address, + uniswapCompBassetPaths.encoded, + uniswapCompBassetPaths.encodedReversed, + simpleToExactAmount(1, 30), // set tranche size to 1e30 + simpleToExactAmount(70, 18), + ) + + const tx = await liquidator.triggerLiquidation(compIntegration.address) + + // 10 COMP liquidated at 440 COMP/USD with 0.3% fee + // Swap bAsset output = 10 * 440 * (100 - 0.3) / 100 = 4,386.8 + // 4,386.8 bAsset is then minted for mUSD which costs 2% + // mUSD in Savings = 4,386.8 * (100 - 2) / 100 = 4,299.064 + const mAssetsExpected = simpleToExactAmount(4299064, 15) + await expect(tx).to.emit(liquidator, "Liquidated").withArgs(compToken.address, mUSD.address, mAssetsExpected, bAsset.address) const s1 = await snapshotData() - // 10 COMP liquidated for > 1000 mUSD - expect(s1.savingsManagerBal.sub(s0.savingsManagerBal)).gt(simpleToExactAmount(1000, 18)) + expect(s1.sellTokenBalance.integration, "integration COMP bal after").to.eq(0) + expect(s1.sellTokenBalance.liquidator, "liquidator COMP bal after").to.eq(0) + expect(s1.savingsManagerBal, "savings manager COMP bal after").to.eq(s0.savingsManagerBal.add(mAssetsExpected)) await increaseTime(ONE_WEEK.add(1)) await expect(liquidator.triggerLiquidation(compIntegration.address)).to.be.revertedWith("No sell tokens to liquidate") @@ -581,6 +614,83 @@ describe("Liquidator", () => { await liquidator.triggerLiquidation(compIntegration.address) }) }) + context("triggering a liquidation for Feeder Pool", () => { + beforeEach(async () => { + await redeployLiquidator() + await liquidator.connect(sa.governor.signer).createLiquidation( + compIntegration.address, + compToken.address, + bAsset.address, + uniswapCompBassetPaths.encoded, + uniswapCompBassetPaths.encodedReversed, + simpleToExactAmount(10000, 18), + simpleToExactAmount(70, 18), + ZERO_ADDRESS, // no mAsset. This is a Feeder Pool integration + false, + ) + await compIntegration.connect(sa.governor.signer).approveRewardToken() + }) + context("send purchased asset to integration contract", () => { + it("should sell all COMP", async () => { + const s0 = await snapshotData() + expect(s0.sellTokenBalance.integration, "integration COMP bal before").to.eq(simpleToExactAmount(10)) + expect(s0.sellTokenBalance.liquidator, "liquidator COMP bal before").to.eq(0) + expect(s0.buyTokenBalance.integration, "integration bAsset bal before").to.eq(0) + expect(s0.buyTokenBalance.liquidator, "liquidator bAsset bal before").to.eq(0) + + const tx = await liquidator.triggerLiquidation(compIntegration.address) + + // 10 COMP liquidated at 440 COMP/USD with 0.3% fee + // Swap bAsset output = 10 * 440 * (100 - 0.3) / 100 = 4,386.8 + // 4,386.8 bAsset is then minted for mUSD which costs 2% + const purchasedBassetsExpected = simpleToExactAmount(43868, 17) + + await expect(tx) + .to.emit(liquidator, "Liquidated") + .withArgs(compToken.address, ZERO_ADDRESS, purchasedBassetsExpected, bAsset.address) + + const s1 = await snapshotData() + expect(s1.sellTokenBalance.integration, "integration COMP bal after").to.eq(0) + expect(s1.sellTokenBalance.liquidator, "liquidator COMP bal after").to.eq(0) + expect(s1.buyTokenBalance.integration, "integration bAsset bal after").to.eq(purchasedBassetsExpected) + expect(s1.buyTokenBalance.liquidator, "liquidator bAsset bal after").to.eq(0) + }) + it("should partially sell COMP", async () => { + await liquidator + .connect(sa.governor.signer) + .updateBasset( + compIntegration.address, + bAsset.address, + uniswapCompBassetPaths.encoded, + uniswapCompBassetPaths.encodedReversed, + simpleToExactAmount(1000, 18), + simpleToExactAmount(70, 18), + ) + + const s0 = await snapshotData() + expect(s0.sellTokenBalance.integration, "integration COMP bal before").to.eq(simpleToExactAmount(10)) + expect(s0.sellTokenBalance.liquidator, "liquidator COMP bal before").to.eq(0) + expect(s0.buyTokenBalance.integration, "integration bAsset bal before").to.eq(0) + expect(s0.buyTokenBalance.liquidator, "liquidator bAsset bal before").to.eq(0) + + const tx = await liquidator.triggerLiquidation(compIntegration.address) + + // purchased bAssets close to 1000 but not quite do to Uniswap calcs + const purchasedBassetsExpected = BN.from("999999999999999999926") + assertBNClose(purchasedBassetsExpected, simpleToExactAmount(1000, 18), 100) + + await expect(tx) + .to.emit(liquidator, "Liquidated") + .withArgs(compToken.address, ZERO_ADDRESS, purchasedBassetsExpected, bAsset.address) + + const s1 = await snapshotData() + expect(s1.sellTokenBalance.integration, "integration COMP bal after").to.eq(0) + expect(s1.sellTokenBalance.liquidator, "liquidator COMP bal after").to.gt(0) + expect(s1.buyTokenBalance.integration, "integration bAsset bal after").to.eq(purchasedBassetsExpected) + expect(s1.buyTokenBalance.liquidator, "liquidator bAsset bal after").to.eq(0) + }) + }) + }) context("Aave claim rewards", () => { before(async () => { await redeployLiquidator() diff --git a/test/masset/peripheral/aavev2.spec.ts b/test/masset/peripheral/aavev2.spec.ts index f60ac97e..3a213afc 100644 --- a/test/masset/peripheral/aavev2.spec.ts +++ b/test/masset/peripheral/aavev2.spec.ts @@ -18,9 +18,9 @@ import { MockERC20, MockATokenV2, } from "types/generated" -import { BassetIntegrationDetails } from "types" +import { BassetIntegrationDetails , Account } from "types" import { shouldBehaveLikeModule, IModuleBehaviourContext } from "../../shared/Module.behaviour" -import { Account } from "types" + describe("AaveIntegration", async () => { let sa: StandardAccounts diff --git a/test/masset/peripheral/compound.spec.ts b/test/masset/peripheral/compound.spec.ts index c1c04373..10a2e4ed 100644 --- a/test/masset/peripheral/compound.spec.ts +++ b/test/masset/peripheral/compound.spec.ts @@ -8,10 +8,10 @@ import { ethers } from "hardhat" import { CompoundIntegration } from "types/generated/CompoundIntegration" import { expect } from "chai" import { CompoundIntegration__factory } from "types/generated/factories/CompoundIntegration__factory" -import { BassetIntegrationDetails } from "types" +import { BassetIntegrationDetails , Account } from "types" import { assertBNClose, assertBNSlightlyGT, assertBNSlightlyGTPercent } from "@utils/assertions" import { shouldBehaveLikeModule, IModuleBehaviourContext } from "../../shared/Module.behaviour" -import { Account } from "types" + const convertUnderlyingToCToken = async (cToken: MockCToken, underlyingAmount: BN): Promise => { const exchangeRate = await cToken.exchangeRateStored() diff --git a/test/rewards/boosted-dual-vault.spec.ts b/test/rewards/boosted-dual-vault.spec.ts index 0d25c867..cb342d32 100644 --- a/test/rewards/boosted-dual-vault.spec.ts +++ b/test/rewards/boosted-dual-vault.spec.ts @@ -23,11 +23,11 @@ import { BoostDirector__factory, BoostDirector, } from "types/generated" +import { Account } from "types" import { shouldBehaveLikeDistributionRecipient, IRewardsDistributionRecipientContext, } from "../shared/RewardsDistributionRecipient.behaviour" -import { Account } from "types" interface StakingBalance { raw: BN diff --git a/test/rewards/boosted-vault.spec.ts b/test/rewards/boosted-vault.spec.ts index d22d8b0c..0088a526 100644 --- a/test/rewards/boosted-vault.spec.ts +++ b/test/rewards/boosted-vault.spec.ts @@ -25,11 +25,11 @@ import { MockBoostedVault, MockBoostedVault__factory, } from "types/generated" +import { Account } from "types" import { shouldBehaveLikeDistributionRecipient, IRewardsDistributionRecipientContext, } from "../shared/RewardsDistributionRecipient.behaviour" -import { Account } from "types" interface StakingBalance { raw: BN diff --git a/test/savings/savings-manager.spec.ts b/test/savings/savings-manager.spec.ts index 6acf38dd..35458946 100644 --- a/test/savings/savings-manager.spec.ts +++ b/test/savings/savings-manager.spec.ts @@ -20,8 +20,8 @@ import { MockERC20, MockRevenueRecipient__factory, } from "types/generated" -import { shouldBehaveLikePausableModule, IPausableModuleBehaviourContext } from "../shared/PausableModule.behaviour" import { Account } from "types" +import { shouldBehaveLikePausableModule, IPausableModuleBehaviourContext } from "../shared/PausableModule.behaviour" describe("SavingsManager", async () => { const TEN = BN.from(10)