diff --git a/contracts/mainnet/connectors/crvusd/events.sol b/contracts/mainnet/connectors/crvusd/events.sol new file mode 100644 index 00000000..ab12236a --- /dev/null +++ b/contracts/mainnet/connectors/crvusd/events.sol @@ -0,0 +1,11 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogCreateLoan(address indexed collateral, uint256 amt, uint256 debt, uint256 indexed numBands, uint256 controllerVersion, uint256 getId, uint256 setId); + event LogAddCollateral(address indexed collateral, uint256 indexed amt, uint256 controllerVersion, uint256 getId, uint256 setId); + event LogRemoveCollateral(address indexed collateral, uint256 indexed amt, uint256 getId, uint256 setId); + event LogBorrowMore(address indexed collateral, uint256 indexed amt, uint256 controllerVersion, uint256 indexed debt); + event LogRepay(address indexed collateral, uint256 indexed amt, uint256 controllerVersion, uint256 getId, uint256 setId); + event LogLiquidate(address indexed collateral, uint256 indexed min_x, uint256 controllerVersion, uint256 getId, uint256 setId); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/crvusd/helpers.sol b/contracts/mainnet/connectors/crvusd/helpers.sol new file mode 100644 index 00000000..5d2c403c --- /dev/null +++ b/contracts/mainnet/connectors/crvusd/helpers.sol @@ -0,0 +1,24 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; +import "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + + address internal constant CRV_USD = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E; + /** + * @dev ControllerFactory Interface + */ + IControllerFactory internal constant CONTROLLER_FACTORY = + IControllerFactory(0xC9332fdCB1C491Dcc683bAe86Fe3cb70360738BC); + + /** + * @dev Get controller address by given collateral asset + */ + function getController(address collateral, uint256 i) internal view returns(IController controller) { + controller = IController(CONTROLLER_FACTORY.get_controller(collateral, i)); + } +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/crvusd/interface.sol b/contracts/mainnet/connectors/crvusd/interface.sol new file mode 100644 index 00000000..85d09cdd --- /dev/null +++ b/contracts/mainnet/connectors/crvusd/interface.sol @@ -0,0 +1,21 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import { TokenInterface } from "../../common/interfaces.sol"; + +interface IControllerFactory { + function get_controller(address collateral, uint256 index) external view returns (address); +} + +interface IController { + function create_loan(uint256 collateral, uint256 debt, uint256 N) payable external; + function add_collateral(uint256 collateral, address _for) payable external; + function remove_collateral(uint256 collateral, bool use_eth) external; + function borrow_more(uint256 collateral, uint256 debt) payable external; + function repay(uint256 _d_debt, address _for, int256 max_active_band, bool use_eth) payable external; + function repay(uint256 _d_debt) payable external; + function liquidate(address user, uint256 min_x, bool use_eth) external; + function max_borrowable(uint256 collateral, uint256 N) external view returns(uint256); + function min_collateral(uint256 debt, uint256 N) external view returns(uint256); + function user_state(address user) external view returns(uint256[4] memory); +} diff --git a/contracts/mainnet/connectors/crvusd/main.sol b/contracts/mainnet/connectors/crvusd/main.sol new file mode 100644 index 00000000..cf9bbe50 --- /dev/null +++ b/contracts/mainnet/connectors/crvusd/main.sol @@ -0,0 +1,281 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title Curve USD. + * @dev Collateralized Borrowing. + */ + +import { TokenInterface, AccountInterface } from "../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import "./interface.sol"; + +abstract contract CurveUSDResolver is Helpers, Events { + /** + * @dev Create loan + * @dev If a user already has an existing loan, the function will revert. + * @param collateral Collateral token address.(For ETH: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) + * @param amt Amount of collateral (For max: `uint256(-1)`) + * @param debtAmt Stablecoin debt to take (For max: `uint256(-1)`) + * @param numBands Number of bands to deposit into (to do autoliquidation-deliquidation), can only be from MIN_TICKS(4) to MAX_TICKS(50) + * @param controllerVersion Controller version, + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of debt borrowed. + */ + function createLoan( + address collateral, + uint256 amt, + uint256 debtAmt, + uint256 numBands, + uint256 controllerVersion, + uint256 getId, + uint256 setId + ) external returns (string memory _eventName, bytes memory _eventParam) { + uint256 _amt = getUint(getId, amt); + + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + TokenInterface collateralContract = TokenInterface(_collateralAddress); + + // Get controller address of collateral. + IController controller = getController(_collateralAddress, controllerVersion); + + if (_isEth) { + _amt = _amt == uint256(-1) ? address(this).balance : _amt; + convertEthToWeth(_isEth, collateralContract, _amt); + } else { + _amt = _amt == uint256(-1) ? collateralContract.balanceOf(address(this)) : _amt; + } + + approve(collateralContract, address(controller), _amt); + + uint256 _debtAmt = debtAmt == uint256(-1) ? controller.max_borrowable(_amt, numBands) : debtAmt; + + controller.create_loan(_amt, _debtAmt, numBands); + + setUint(setId, _debtAmt); + _eventName = "LogCreateLoan(address,uint256,uint256,uint256,uint256,uin256,uin256)"; + _eventParam = abi.encode(collateral, _amt, _debtAmt, numBands, controllerVersion, getId, setId); + } + + /** + * @dev Add collateral + * @notice Add extra collateral to avoid bad liqidations + * @param collateral Collateral token address.(For ETH: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) + * @param amt Amount of collateral (For max: `uint256(-1)`) + * @param controllerVersion Controller version, + * @param getId ID to retrieve amt. + * @param setId ID stores the collateral amount of tokens added. + */ + function addCollateral( + address collateral, + uint256 amt, + uint256 controllerVersion, + uint256 getId, + uint256 setId + ) external returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + + // Get controller address of collateral. + IController controller = getController(_collateralAddress, controllerVersion); + TokenInterface collateralContract = TokenInterface(_collateralAddress); + + if (_isEth) { + _amt = _amt == uint(-1) ? address(this).balance : _amt; + convertEthToWeth(_isEth, collateralContract, _amt); + } else { + _amt = _amt == uint(-1) ? collateralContract.balanceOf(address(this)) : _amt; + } + + approve(collateralContract, address(controller), _amt); + controller.add_collateral(_amt, address(this)); + + setUint(setId, _amt); + + _eventName = "LogAddCollateral(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(collateral, amt, controllerVersion, getId, setId); + } + + /** + * @dev Remove ETH/ERC20_Token Collateral. + * @notice Remove some collateral without repaying the debt + * @param collateral Collateral token address.(For ETH: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) + * @param amt Remove collateral amount (For max: `uint256(-1)`) + * @param controllerVersion controller version + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function removeCollateral( + address collateral, + uint256 amt, + uint256 controllerVersion, + uint256 getId, + uint256 setId + ) external returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + + IController controller = getController(_collateralAddress, controllerVersion); + + // remove_collateral will unwrap the eth. + controller.remove_collateral(_amt, _isEth); + + setUint(setId, _amt); + _eventName = "LogRemoveCollateral(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(collateral, amt, controllerVersion, getId, setId); + } + + /** + * @dev Borrow more stablecoins while adding more collateral (not necessary) + * @param collateral Collateral token address.(For ETH: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) + * @param debtAmt Stablecoin debt to take for borrow more (For max: `uint256(-1)`) + * @param controllerVersion controller version + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function borrowMore( + address collateral, + uint256 debtAmt, + uint256 controllerVersion, + uint256 getId, + uint256 setId + ) external returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, debtAmt); + + bool _isEth = collateral == ethAddr; + + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); + + uint256[4] memory res = controller.user_state(address(this)); + uint256 _debtAmt = debtAmt == uint(-1) + ? controller.max_borrowable(res[0], res[3]) - res[2] + : debtAmt; + + controller.borrow_more(0, _debtAmt); + + setUint(setId, _amt); + _eventName = "LogBorrowMore(address,uint256,uint256,uin256,uin256)"; + _eventParam = abi.encode(collateral, debtAmt, controllerVersion, getId, setId); + } + + /** + * @dev Borrow more stablecoins while adding more collateral (not necessary) + * @param collateral Collateral token address.(For ETH: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) + * @param colAmt Collateral amount for borrow more (For max: `uint256(-1)`) + * @param debtAmt Stablecoin debt to take for borrow more (For max: `uint256(-1)`) + * @param controllerVersion controller version + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function addCollateralAndBorrowMore( + address collateral, + uint256 colAmt, + uint256 debtAmt, + uint256 controllerVersion, + uint256 getId, + uint256 setId + ) external returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, colAmt); + + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + TokenInterface collateralContract = TokenInterface(_collateralAddress); + + IController controller = getController(_collateralAddress, controllerVersion); + + if (_isEth) { + _amt = _amt == uint(-1) ? address(this).balance : _amt; + convertEthToWeth(_isEth, collateralContract, _amt); + } else { + _amt = _amt == uint(-1) ? collateralContract.balanceOf(address(this)) : _amt; + } + + approve(collateralContract, address(controller), _amt); + + uint256[4] memory res = controller.user_state(address(this)); + uint256 _debtAmt = debtAmt == uint(-1) + ? controller.max_borrowable(_amt + res[0], res[3]) - res[2] + : debtAmt; + + controller.borrow_more(_amt, _debtAmt); + + setUint(setId, _amt); + _eventName = "LogAddCollateralAndBorrowMore(address,uint256,uint256,uint256,uin256,uin256)"; + _eventParam = abi.encode(collateral, colAmt, debtAmt, controllerVersion, getId, setId); + } + + /** + * @dev Repay Curve-USD. + * @param collateral Collateral token address.(For ETH: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) + * @param amt repay amount (For max: `uint256(-1)`) + * @param controllerVersion Controller version. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of debt borrowed. + */ + function repay( + address collateral, + uint256 amt, + uint256 controllerVersion, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); + + TokenInterface stableCoin = TokenInterface(CRV_USD); + _amt = _amt == uint(-1) ? stableCoin.balanceOf(address(this)) : _amt; + + approve(stableCoin, address(controller), _amt); + + controller.repay(_amt); + + setUint(setId, _amt); + _eventName = "LogRepay(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(collateral, amt, controllerVersion, getId, setId); + } + + /** + * @dev Peform a bad liquidation (or self-liquidation) of user if health is not good + * @param collateral collateral token address + * @param minReceiveAmt Minimal amount of stablecoin to receive (to avoid liquidators being sandwiched) + * @param controllerVersion controller version. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of debt borrowed. + */ + function selfLiquidate( + address collateral, + uint256 minReceiveAmt, + uint256 controllerVersion, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _minReceiveAmt = getUint(getId, minReceiveAmt); + + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); + + TokenInterface stableCoin = TokenInterface(CRV_USD); + approve(stableCoin, address(controller), _minReceiveAmt); + + controller.liquidate(address(this), _minReceiveAmt, _isEth); + + setUint(setId, _minReceiveAmt); + _eventName = "LogLiquidate(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(collateral, _minReceiveAmt, controllerVersion, getId, setId); + } +} + +contract ConnectV2CurveUSD is CurveUSDResolver { + string public constant name = "CurveUSD-v1.0"; +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index bce32b39..b3d1da1b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -39,7 +39,7 @@ const PRIVATE_KEY = process.env.PRIVATE_KEY; const mnemonic = process.env.MNEMONIC ?? "test test test test test test test test test test test junk"; const networkGasPriceConfig: Record = { - mainnet: 100, + mainnet: 37, polygon: 50, avalanche: 40, arbitrum: 1, diff --git a/scripts/tests/mainnet/tokens.ts b/scripts/tests/mainnet/tokens.ts index 41f31237..a8a06e50 100644 --- a/scripts/tests/mainnet/tokens.ts +++ b/scripts/tests/mainnet/tokens.ts @@ -76,7 +76,35 @@ export const tokens = { aTokenAddress: "0xB9D7CB55f463405CDfBe4E90a6D2Df01C2B92BF1", cTokenAddress: "0x35A18000230DA775CAc24873d00Ff85BccdeD550", decimals: 18 - } + }, + crvusd: { + type: "token", + symbol: "crvUSD", + name: "Curve.Fi USD Stablecoin", + address: "0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E", + decimals: 18 + }, + sfrxeth: { + type: "token", + symbol: "sfrxETH", + name: "Staked Frax Ether", + address: "0xac3E018457B222d93114458476f3E3416Abbe38F", + decimals: 18 + }, + wsteth: { + type: "token", + symbol: "wstETH", + name: "Wrapped liquid staked Ether 2.0", + address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + decimals: 18 + }, + wbtc: { + type: "token", + symbol: "WBTC", + name: "Wrapped BTC", + address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + decimals: 8 + }, }; export const dsaMaxValue = "115792089237316195423570985008687907853269984665640564039457584007913129639935"; diff --git a/test/mainnet/crv_usd/crv_usd.test.ts b/test/mainnet/crv_usd/crv_usd.test.ts new file mode 100644 index 00000000..039217cc --- /dev/null +++ b/test/mainnet/crv_usd/crv_usd.test.ts @@ -0,0 +1,445 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers } = hre; +const { provider, deployContract } = waffle; + +import { Signer, Contract } from "ethers"; +import { BigNumber } from "bignumber.js"; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/mainnet/addresses"; +import { tokens, dsaMaxValue } from "../../../scripts/tests/mainnet/tokens"; +import { abis } from "../../../scripts/constant/abis"; +import { constants } from "../../../scripts/constant/constant"; +import { ConnectV2CurveUSD__factory, IERC20Minimal__factory } from "../../../typechain"; + +// import ABI_Ctr from "./ABI.json" + +describe("CRV USD", function () { + const connectorName = "CRV_USD-TEST-A"; + const market = "0xc3d688B66703497DAA19211EEdff47f25384cdc3"; + const base = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + const wst_whale = "0x78bB3aEC3d855431bd9289fD98dA13F9ebB7ef15"; + const wethWhale = "0x78bB3aEC3d855431bd9289fD98dA13F9ebB7ef15"; + + const wethContract = new ethers.Contract( + tokens.weth.address, + IERC20Minimal__factory.abi, + ethers.provider + ); + const baseContract = new ethers.Contract( + base, + IERC20Minimal__factory.abi, + ethers.provider + ); + const linkContract = new ethers.Contract( + tokens.wbtc.address, + IERC20Minimal__factory.abi, + ethers.provider + ); + const crvUSD = new ethers.Contract( + tokens.crvusd.address, + IERC20Minimal__factory.abi, + ethers.provider + ); + const sfrxEth = new ethers.Contract( + tokens.sfrxeth.address, + IERC20Minimal__factory.abi, + ethers.provider + ); + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let dsaWallet3: any; + let wallet: any; + let dsa0Signer: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + let signer: any; + let sfrxSigner: any; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + //@ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + // blockNumber: 17811076 + } + } + ] + }); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2CurveUSD__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + + await hre.network.provider.send("hardhat_setBalance", [wst_whale, ethers.utils.parseEther("10").toHexString()]); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [wst_whale] + }); + + signer = await ethers.getSigner(wst_whale); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + dsaWallet1 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet1.address).to.be.true; + dsaWallet2 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet2.address).to.be.true; + dsaWallet3 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet3.address).to.be.true; + wallet = await ethers.getSigner(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + }); + + it("Deposit ETH into DSA wallet", async function () { + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [wallet.address] + }); + + dsa0Signer = await ethers.getSigner(wallet.address); + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + await wallet0.sendTransaction({ + to: dsaWallet3.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + + let txRes = await sfrxEth.connect(signer).transfer(dsaWallet0.address, ethers.utils.parseEther("10000")); + await txRes.wait(); + txRes = await sfrxEth.connect(signer).transfer(dsaWallet1.address, ethers.utils.parseEther("1000")); + await txRes.wait(); + txRes = await sfrxEth.connect(signer).transfer(dsaWallet2.address, ethers.utils.parseEther("1000")); + await txRes.wait(); + }); + }); + + describe("Main", function () { + //deposit asset + it("Create Loan", async function () { + const spells = [ + { + connector: connectorName, + method: "createLoan", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), ethers.utils.parseEther('1000'), "10", "1", "0", "0"] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await crvUSD.balanceOf(dsaWallet0.address)).to.be.eq( + ethers.utils.parseEther("1000") + ); + }); + + it("add Collateral", async function () { + const balanceBefore = await sfrxEth.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "addCollateral", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), "1", 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await sfrxEth.balanceOf(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balanceBefore).sub(ethers.utils.parseEther('1')) + ); + }); + + it("remove Collateral", async function () { + const balance = await sfrxEth.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "removeCollateral", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), "1", 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await sfrxEth.balanceOf(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balance).add(ethers.utils.parseEther('1')) + ); + }); + + it("borrow more", async function () { + const balance = await crvUSD.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "borrowMore", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('50'), "1", 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await crvUSD.balanceOf(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balance).add(ethers.utils.parseEther('50')) + ); + }); + + it("addCollateralAndBorrowMore with maximum value", async function () { + const balance = await crvUSD.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "addCollateralAndBorrowMore", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('2'), dsaMaxValue, 1, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await crvUSD.balanceOf(dsaWallet0.address)).to.be.gt( + ethers.BigNumber.from(balance).add(ethers.utils.parseEther('100')) + ); + }); + + it("Revert when loan exists", async function () { + const spells = [ + { + connector: connectorName, + method: "createLoan", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), dsaMaxValue, 10, "1", "0", "0"] + } + ]; + await expect(dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address)).to.be.revertedWith('Loan already created'); + }); + + + it("create loan with maximum debt", async function () { + const spells = [ + { + connector: connectorName, + method: "createLoan", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), dsaMaxValue, 10, "1", "0", "0"] + } + ]; + + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await crvUSD.balanceOf(dsaWallet1.address)).to.be.gt( + ethers.utils.parseEther("1000") + ); + + console.log("maximum debt amount: ", (await crvUSD.balanceOf(dsaWallet1.address)).toString() ) + }); + + it("Repay loans", async function () { + const balance = await crvUSD.balanceOf(dsaWallet1.address) + const spells = [ + { + connector: connectorName, + method: "repay", + args: [tokens.sfrxeth.address, ethers.utils.parseEther('100').toString(), "1", "0", "0"] + } + ]; + const tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + expect(await crvUSD.balanceOf(dsaWallet1.address)).to.be.eq( + ethers.BigNumber.from(balance).sub(ethers.utils.parseEther('100')) + ); + }); + + it("Repay loans with max value", async function () { + const balance = await crvUSD.balanceOf(dsaWallet1.address) + const spells = [ + { + connector: connectorName, + method: "repay", + args: [tokens.sfrxeth.address, dsaMaxValue, "1", "0", "0"] + } + ]; + await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + expect(await crvUSD.balanceOf(dsaWallet1.address)).to.be.eq(0); + }); + + it("Create Loan with maximum collateral and maximum debt", async function () { + const spells = [ + { + connector: connectorName, + method: "createLoan", + args: [tokens.sfrxeth.address, dsaMaxValue, dsaMaxValue, 10, "1", "0", "0"] + } + ]; + + const tx = await dsaWallet2.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await crvUSD.balanceOf(dsaWallet2.address)).to.be.gt( + ethers.utils.parseEther("1000").toString() + ); + expect(await sfrxEth.balanceOf(dsaWallet2.address)).to.be.eq( + '0' + ); + console.log("maximum debt amount after maximum collateral: ", (await crvUSD.balanceOf(dsaWallet2.address)).toString() ) + }); + + it("Create Loan with eth", async function () { + const balance = await ethers.provider.getBalance(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "createLoan", + args: [tokens.eth.address, ethers.utils.parseEther('2').toString(), dsaMaxValue, 10, "0", "0", "0"] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balance).sub(ethers.utils.parseEther('2')) + ); + console.log("maximum debt amount after create loan with 2 eth: ", (await crvUSD.balanceOf(dsaWallet0.address)).toString() ) + }); + + it("add Collateral eth", async function () { + const balance = await ethers.provider.getBalance(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "addCollateral", + args: [tokens.eth.address, ethers.utils.parseEther('3').toString(), 0, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balance).sub(ethers.utils.parseEther('3')) + ); + }); + + it("remove Collateral eth", async function () { + const balance = await ethers.provider.getBalance(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "removeCollateral", + args: [tokens.eth.address, ethers.utils.parseEther('1').toString(), 0, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balance).add(ethers.utils.parseEther('1')) + ); + }); + + it("borrow more", async function () { + const balance = await crvUSD.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "borrowMore", + args: [tokens.eth.address, ethers.utils.parseEther('10'), 0, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + + expect(await crvUSD.balanceOf(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balance).add(ethers.utils.parseEther('10')) + ); + }); + + it("borrow more", async function () { + const balance = await crvUSD.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "borrowMore", + args: [tokens.eth.address, dsaMaxValue, 0, 0, 0] + } + ]; + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + await tx.wait(); + expect(await crvUSD.balanceOf(dsaWallet0.address)).to.be.gt( + ethers.BigNumber.from(balance).add(ethers.utils.parseEther('1000')) + ); + }); + + + it("Repay loans", async function () { + const balance = await crvUSD.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "repay", + args: [tokens.eth.address, ethers.utils.parseEther('100').toString(), "0", "0", "0"] + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + expect(await crvUSD.balanceOf(dsaWallet0.address)).to.be.eq( + ethers.BigNumber.from(balance).sub(ethers.utils.parseEther('100')) + ); + }); + + it("Repay loans with max value", async function () { + const balance = await crvUSD.balanceOf(dsaWallet0.address) + const spells = [ + { + connector: connectorName, + method: "repay", + args: [tokens.eth.address, dsaMaxValue, "0", "0", "0"] + } + ]; + await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address); + console.log("crv balance after repay with max value: ",await crvUSD.balanceOf(dsaWallet0.address)) + }); + + }); +});