diff --git a/scripts/constant/abis.js b/scripts/constant/abis.js index cb62ccfb..bc1fd8f1 100644 --- a/scripts/constant/abis.js +++ b/scripts/constant/abis.js @@ -8,6 +8,7 @@ module.exports = { basic: require("./abi/connectors/basic.json"), auth: require("./abi/connectors/auth.json"), "INSTAPOOL-A": require("./abi/connectors/instapool.json"), + "BASIC-A": require("./abi/connectors/basic.json") }, basic: { erc20: require("./abi/basics/erc20.json"), diff --git a/test/notional/notional.contracts.js b/test/notional/notional.contracts.js new file mode 100644 index 00000000..f752ee06 --- /dev/null +++ b/test/notional/notional.contracts.js @@ -0,0 +1,109 @@ + +const NOTIONAL_CONTRACT_ADDRESS = '0x1344A36A1B56144C3Bc62E7757377D288fDE0369'; +const NOTIONAL_CONTRACT_ABI = [ + { + "inputs": [ + { + "internalType": "uint16", + "name": "currencyId", + "type": "uint16" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountBalance", + "outputs": [ + { + "internalType": "int256", + "name": "cashBalance", + "type": "int256" + }, + { + "internalType": "int256", + "name": "nTokenBalance", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "lastClaimTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountPortfolio", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currencyId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maturity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetType", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "notional", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "storageSlot", + "type": "uint256" + }, + { + "internalType": "enum AssetStorageState", + "name": "storageState", + "type": "uint8" + } + ], + "internalType": "struct PortfolioAsset[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + } +]; + +const WETH_TOKEN_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; +const DAI_TOKEN_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; +const CDAI_TOKEN_ADDRESS = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643"; +const CETH_TOKEN_ADDRESS = "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5"; +const ERC20_TOKEN_ABI = [ + "function transfer(address _to, uint256 _value) public returns (bool success)", + "function balanceOf(address account) external view returns (uint256)", + "function approve(address spender, uint256 amount) external returns (bool)", +]; + +module.exports = { + NOTIONAL_CONTRACT_ADDRESS, + NOTIONAL_CONTRACT_ABI, + WETH_TOKEN_ADDRESS, + DAI_TOKEN_ADDRESS, + CDAI_TOKEN_ADDRESS, + CETH_TOKEN_ADDRESS, + ERC20_TOKEN_ABI +}; \ No newline at end of file diff --git a/test/notional/notional.helpers.js b/test/notional/notional.helpers.js new file mode 100644 index 00000000..a82e989e --- /dev/null +++ b/test/notional/notional.helpers.js @@ -0,0 +1,167 @@ +const encodeSpells = require("../../scripts/encodeSpells.js") + +const depositCollteral = async (dsa, authority, referrer, currencyId, amount, underlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositCollateral", + args: [currencyId, underlying, amount, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const depositAndMintNToken = async (dsa, authority, referrer, currencyId, amount, underlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositAndMintNToken", + args: [currencyId, amount, underlying, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + +const depositAndLend = async (dsa, authority, referrer, currencyId, underlying, amount, market, fcash, minRate) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositAndLend", + args: [currencyId, amount, underlying, market, fcash, minRate, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const withdrawCollateral = async (dsa, authority, referrer, currencyId, amount, underlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "withdrawCollateral", + args: [currencyId, underlying, amount, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const redeemNTokenRaw = async (dsa, authority, referrer, currencyId, sellTokenAssets, tokensToRedeem) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "redeemNTokenRaw", + args: [currencyId, sellTokenAssets, tokensToRedeem, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const redeemNTokenAndWithdraw = async (dsa, authority, referrer, currencyId, tokensToRedeem, amountToWithdraw, redeemToUnderlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "redeemNTokenAndWithdraw", + args: [currencyId, tokensToRedeem, amountToWithdraw, redeemToUnderlying, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const redeemNTokenAndDeleverage = async (dsa, authority, referrer, currencyId, tokensToRedeem, marketIndex, fCashAmount, minLendRate) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "redeemNTokenAndDeleverage", + args: [currencyId, tokensToRedeem, marketIndex, fCashAmount, minLendRate, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const depositCollateralBorrowAndWithdraw = async ( + dsa, + authority, + referrer, + depositCurrencyId, + depositUnderlying, + depositAmount, + borrowCurrencyId, + marketIndex, + fCashAmount, + maxBorrowRate, + redeedmUnderlying +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositCollateralBorrowAndWithdraw", + args: [ + depositCurrencyId, + depositUnderlying, + depositAmount, + borrowCurrencyId, + marketIndex, + fCashAmount, + maxBorrowRate, + redeedmUnderlying, + 0, + 0 + ] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const withdrawLend = async (dsa, authority, referrer, currencyId, marketIndex, fCashAmount, maxBorrowRate) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "withdrawLend", + args: [currencyId, marketIndex, fCashAmount, maxBorrowRate, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const depositERC20 = async (dsa, authority, referrer, token, amount) => { + const spells = [ + { + connector: "BASIC-A", + method: "deposit", + args: [token, amount, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +module.exports = { + depositCollteral, + depositAndMintNToken, + depositAndLend, + withdrawCollateral, + withdrawLend, + redeemNTokenRaw, + redeemNTokenAndWithdraw, + redeemNTokenAndDeleverage, + depositCollateralBorrowAndWithdraw, + depositERC20 +}; diff --git a/test/notional/notional.test.js b/test/notional/notional.test.js new file mode 100644 index 00000000..f7a3a841 --- /dev/null +++ b/test/notional/notional.test.js @@ -0,0 +1,422 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle + +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") +const buildDSAv2 = require("../../scripts/buildDSAv2") +const encodeSpells = require("../../scripts/encodeSpells.js") +const getMasterSigner = require("../../scripts/getMasterSigner") + +const addresses = require("../../scripts/constant/addresses"); +const abis = require("../../scripts/constant/abis"); +const constants = require("../../scripts/constant/constant"); +const tokens = require("../../scripts/constant/tokens"); + +const contracts = require("./notional.contracts"); +const helpers = require("./notional.helpers"); + +const connectV2NotionalArtifacts = require("../../artifacts/contracts/mainnet/connectors/notional/main.sol/ConnectV2Notional.json"); +const { BigNumber } = require("ethers"); + +const DAI_WHALE = "0x6dfaf865a93d3b0b5cfd1b4db192d1505676645b"; +const CDAI_WHALE = "0x33b890d6574172e93e58528cd99123a88c0756e9"; +const ETH_WHALE = "0x7D24796f7dDB17d73e8B1d0A3bbD103FBA2cb2FE"; +const CETH_WHALE = "0x1a1cd9c606727a7400bb2da6e4d5c70db5b4cade"; +const MaxUint96 = BigNumber.from("0xffffffffffffffffffffffff"); +const DEPOSIT_ASSET = 1; +const DEPOSIT_UNDERLYING = 2; +const DEPOSIT_ASSET_MINT_NTOKEN = 3; +const DEPOSIT_UNDERLYING_MINT_NTOKEN = 4; +const ETH_ID = 1; +const DAI_ID = 2; +const MARKET_3M = 1; + +describe("Notional", function () { + const connectorName = "NOTIONAL-TEST-A" + + let dsaWallet0 + let masterSigner; + let instaConnectorsV2; + let connector; + let notional; + let daiToken; + let cdaiToken; + let cethToken; + let daiWhale; + let cdaiWhale; + let cethWhale; + + const wallets = provider.getWallets() + const [wallet0, wallet1, wallet2, wallet3] = wallets + beforeEach(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 13798624, + }, + }, + ], + }); + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [DAI_WHALE] + }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [CDAI_WHALE] + }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [ETH_WHALE] + }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [CETH_WHALE] + }) + + masterSigner = await getMasterSigner(wallet3) + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: connectV2NotionalArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }) + notional = new ethers.Contract( + contracts.NOTIONAL_CONTRACT_ADDRESS, + contracts.NOTIONAL_CONTRACT_ABI, + ethers.provider + ); + daiToken = new ethers.Contract( + contracts.DAI_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + daiWhale = await ethers.getSigner(DAI_WHALE); + cdaiToken = new ethers.Contract( + contracts.CDAI_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + cdaiWhale = await ethers.getSigner(CDAI_WHALE); + cethToken = new ethers.Contract( + contracts.CETH_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + cethWhale = await ethers.getSigner(CETH_WHALE); + weth = new ethers.Contract( + contracts.WETH_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + dsaWallet0 = await buildDSAv2(wallet0.address) + }); + + describe("Deposit Tests", function () { + it("test_deposit_ETH_underlying", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, true); + const bal = await notional.callStatic.getAccountBalance(ETH_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 49 cETH").to.be.gte(ethers.utils.parseUnits("4900000000", 0)); + expect(bal[1], "expect 0 nETH").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_ETH_asset", async function () { + const depositAmount = ethers.utils.parseUnits("1", 8); + await cethToken.connect(cethWhale).transfer(wallet0.address, depositAmount); + await cethToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cethToken.address, depositAmount); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, false); + const bal = await notional.callStatic.getAccountBalance(ETH_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 1 cETH").to.be.gte(ethers.utils.parseUnits("100000000", 0)); + expect(bal[1], "expect 0 nETH").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_DAI_underlying", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, true); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 45000 cDAI").to.be.gte(ethers.utils.parseUnits("4500000000000", 0)); + expect(bal[1], "expect 0 nDAI").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_DAI_asset", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 1000 cDAI").to.be.gte(ethers.utils.parseUnits("100000000000", 0)); + expect(bal[1], "expect 0 nDAI").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_ETH_underlying_and_mint_ntoken", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, true); + const bal = await notional.callStatic.getAccountBalance(ETH_ID, dsaWallet0.address); + expect(bal[0], "expect 0 balance").to.be.equal(ethers.utils.parseUnits("0", 0)); + expect(bal[1], "expect at least 49 nETH").to.be.gte(ethers.utils.parseUnits("4900000000", 0)); + }); + }); + + describe("Lend Tests", function () { + it("test_deposit_ETH_underlying_and_lend", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, 9e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 9 fETH").to.be.gte(ethers.utils.parseUnits("900000000", 0)); + }); + + it("test_deposit_ETH_asset_and_lend", async function () { + const depositAmount = ethers.utils.parseUnits("1", 8); + await cethToken.connect(cethWhale).transfer(wallet0.address, depositAmount); + await cethToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cethToken.address, depositAmount); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, false, depositAmount, MARKET_3M, 0.01e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 0.01 fETH").to.be.gte(ethers.utils.parseUnits("1000000", 0)); + }); + + it("test_deposit_DAI_underlying_and_lend", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, true, depositAmount, MARKET_3M, 100e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 100 fDAI").to.be.gte(ethers.utils.parseUnits("10000000000", 0)); + }); + + it("test_deposit_DAI_asset_and_lend", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, false, depositAmount, MARKET_3M, 10e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 10 fDAI").to.be.gte(ethers.utils.parseUnits("1000000000", 0)); + }); + + it("test_withdraw_lend_ETH", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, 9e8, 0); + const before = await notional.getAccountPortfolio(dsaWallet0.address); + expect(before.length, "expect 1 lending position").to.be.equal(1); + expect(before[0][3], "expect 9 fETH").to.be.gte(ethers.utils.parseUnits("900000000", 0)); + await helpers.withdrawLend(dsaWallet0, wallet0, wallet1, ETH_ID, MARKET_3M, 9e8, 0); + const after = await notional.getAccountPortfolio(dsaWallet0.address); + expect(after.length, "expect lending position to be closed out").to.be.equal(0); + }); + }); + + describe("Borrow Tests", function () { + it("test_deposit_ETH_and_borrow_DAI_underlying", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, 1000e8, 0, true + ); + expect( + await daiToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("990")); + }); + + it("test_deposit_ETH_and_borrow_DAI_asset", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, 1000e8, 0, false + ); + expect( + await cdaiToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseUnits("4500000000000", 0)); + }); + + it("test_deposit_DAI_underlying_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("20000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + + it("test_deposit_DAI_asset_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("1000000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + + it("test_mint_nDAI_underlying_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("20000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + + it("test_mint_nDAI_asset_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("1000000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + }); + + describe("Withdraw Tests", function () { + it("test_withdraw_ETH_underlying", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, 1, depositAmount, true); + await helpers.withdrawCollateral(dsaWallet0, wallet0, wallet1, 1, ethers.constants.MaxUint256, true); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain underlying funds" + ).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("test_withdraw_ETH_asset", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, true); + await helpers.withdrawCollateral(dsaWallet0, wallet0, wallet1, ETH_ID, ethers.constants.MaxUint256, false); + expect( + await cethToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain cToken funds" + ).to.be.gte(ethers.utils.parseUnits("4900000000", 0)); + }); + + it("test_redeem_DAI_raw", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + await helpers.redeemNTokenRaw(dsaWallet0, wallet0, wallet1, DAI_ID, true, MaxUint96) + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + expect(bal[0], "expect cDAI balance after redemption").to.be.gte(ethers.utils.parseUnits("99000000000", 0)); + expect(bal[1], "expect 0 nDAI").to.be.equal(ethers.utils.parseEther("0")); + }); + + it("test_redeem_DAI_and_withdraw_redeem", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + await helpers.redeemNTokenAndWithdraw(dsaWallet0, wallet0, wallet1, DAI_ID, MaxUint96, ethers.constants.MaxUint256, true); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + expect(bal[0], "expect 0 cDAI balance").to.be.equal(ethers.utils.parseEther("0")); + expect(bal[1], "expect 0 nDAI balance").to.be.equal(ethers.utils.parseEther("0")); + }); + + it("test_redeem_DAI_and_withdraw_no_redeem", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + expect(await cdaiToken.balanceOf(dsaWallet0.address)).to.be.equal(ethers.utils.parseEther("0")); + await helpers.redeemNTokenAndWithdraw(dsaWallet0, wallet0, wallet1, DAI_ID, MaxUint96, ethers.constants.MaxUint256, false); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + expect(bal[0], "expect 0 cDAI balance").to.be.equal(ethers.utils.parseEther("0")); + expect(bal[1], "expect 0 nDAI balance").to.be.equal(ethers.utils.parseEther("0")); + expect( + await cdaiToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain cToken funds" + ).to.be.gte(ethers.utils.parseUnits("99000000000", 0)); + }); + + it("test_redeem_DAI_and_deleverage", async function () { + const depositAmount = ethers.utils.parseUnits("20000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + const bal = await ethers.provider.getBalance(dsaWallet0.address); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, ETH_ID, bal, true); + const before = await notional.getAccountPortfolio(dsaWallet0.address); + expect(before.length, "expect 1 fDAI debt position").to.be.equal(1); + expect(before[0][3], "expect fDAI debt position to equal borrow amount").to.be.lte(ethers.utils.parseUnits("-100000000", 0)); + await helpers.redeemNTokenAndDeleverage(dsaWallet0, wallet0, wallet1, ETH_ID, MaxUint96, MARKET_3M, 0.98e8, 0); + const after = await notional.getAccountPortfolio(dsaWallet0.address); + expect(after.length, "expect 1 fDAI debt position after deleverage").to.be.equal(1); + expect(after[0][3], "expect fDAI debt balance to go down after deleverage").to.be.lte(ethers.utils.parseUnits("-2000000", 0)); + }); + }); +});