diff --git a/contracts/mainnet/connectors/crv_USD/events.sol b/contracts/mainnet/connectors/crv_USD/events.sol index 64937640..ab12236a 100644 --- a/contracts/mainnet/connectors/crv_USD/events.sol +++ b/contracts/mainnet/connectors/crv_USD/events.sol @@ -2,10 +2,10 @@ pragma solidity ^0.7.0; contract Events { - event LogCreateLoan(address indexed collateral, uint256 amt, uint256 debt, uint256 indexed N); - event LogAddCollateral(address indexed collateral, uint256 indexed amt, uint256 getId, uint256 setId); + 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 indexed debt); - event LogRepay(address indexed collateral, uint256 indexed amt, uint256 getId, uint256 setId); - event LogLiquidate(address indexed collateral, uint256 indexed min_x); + 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/crv_USD/interface.sol b/contracts/mainnet/connectors/crv_USD/interface.sol index 3f4075bf..85d09cdd 100644 --- a/contracts/mainnet/connectors/crv_USD/interface.sol +++ b/contracts/mainnet/connectors/crv_USD/interface.sol @@ -13,6 +13,7 @@ interface IController { 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); diff --git a/contracts/mainnet/connectors/crv_USD/main.sol b/contracts/mainnet/connectors/crv_USD/main.sol index 648457b6..7cb3a8b9 100644 --- a/contracts/mainnet/connectors/crv_USD/main.sol +++ b/contracts/mainnet/connectors/crv_USD/main.sol @@ -55,160 +55,179 @@ abstract contract CurveUSDResolver is Helpers, Events { controller.create_loan(_amt, _debt, numBands); setUint(setId, _debt); - _eventName = "LogCreateLoan(address,uint256,uint256,uint256)"; - _eventParam = abi.encode(collateral, _amt, debt, numBands, getId, setId); + _eventName = "LogCreateLoan(address,uint256,uint256,uint256,uint256,uin256,uin256)"; + _eventParam = abi.encode(collateral, _amt, debt, numBands, controllerVersion, getId, setId); } /** * @dev Add collateral * @notice Add extra collateral to avoid bad liqidations - * @param collateral collateral asset address - * @param version controller version - * @param amt Amount of collateral to add + * @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 version, uint256 amt, + uint256 controllerVersion, uint256 getId, uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { - address _collateral = collateral == ethAddr ? wethAddr : collateral; - IController controller = getController(_collateral, version); + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); + TokenInterface collateralContract = TokenInterface(_collateralAddress); uint _amt = getUint(getId, amt); uint ethAmt; - if (collateral == ethAddr) { + if (_isEth) { _amt = _amt == uint(-1) ? address(this).balance : _amt; ethAmt = _amt; + convertEthToWeth(_isEth, collateralContract, _amt); } else { - TokenInterface collateralContract = TokenInterface(_collateral); _amt = _amt == uint(-1) ? collateralContract.balanceOf(address(this)) : _amt; - approve(collateralContract, address(controller), _amt); } - controller.add_collateral{value: ethAmt}(_amt, address(this)); + approve(collateralContract, address(controller), _amt); + controller.add_collateral(_amt, address(this)); setUint(setId, _amt); - _eventName = "LogAddCollateral(address,uint256,uint256,uint256)"; - _eventParam = abi.encode(collateral, amt, getId, setId); + _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 asset address - * @param version controller version - * @param amt Amount of collateral to add + * @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 version, uint256 amt, + uint256 controllerVersion, uint256 getId, uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { - address _collateral = collateral == ethAddr ? wethAddr : collateral; - IController controller = getController(_collateral, version); + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); uint _amt = getUint(getId, amt); controller.remove_collateral(_amt, collateral == ethAddr); setUint(setId, _amt); - _eventName = "LogRemoveCollateral(address,uint256,uint256,uint256)"; - _eventParam = abi.encode(collateral, amt, getId, setId); + _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 - * @param version controller version - * @param amt Amount of collateral to add - * @param debt Amount of stablecoin debt to take + * @param collateral Collateral token address.(For ETH: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) + * @param amt Collateral amount for borrow more (For max: `uint256(-1)`) + * @param debt 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 version, uint256 amt, - uint256 debt + uint256 debt, + uint256 controllerVersion, + uint256 getId, + uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { - address _collateral = collateral == ethAddr ? wethAddr : collateral; - IController controller = getController(_collateral, version); - uint _amt = amt; + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); + TokenInterface collateralContract = TokenInterface(_collateralAddress); + uint _amt = getUint(getId, amt); uint ethAmt; - if (collateral == ethAddr) { + if (_isEth) { _amt = _amt == uint(-1) ? address(this).balance : _amt; ethAmt = _amt; + convertEthToWeth(_isEth, collateralContract, _amt); } else { - TokenInterface collateralContract = TokenInterface(_collateral); _amt = _amt == uint(-1) ? collateralContract.balanceOf(address(this)) : _amt; - approve(collateralContract, address(controller), _amt); } + + approve(collateralContract, address(controller), _amt); uint256[4] memory res = controller.user_state(address(this)); uint256 _debt = debt == uint(-1) ? controller.max_borrowable(_amt + res[0], res[3]) - res[2] : debt; - controller.borrow_more{value: ethAmt}(_amt, _debt); - - _eventName = "LogBorrowMore(address,uint256,uint256)"; - _eventParam = abi.encode(collateral, amt, debt); + controller.borrow_more(_amt, _debt); + + setUint(setId, _amt); + _eventName = "LogBorrowMore(address,uint256,uint256,uint256,uin256,uin256)"; + _eventParam = abi.encode(collateral, amt, debt, controllerVersion, getId, setId); } /** - * @dev Borrow DAI. - * @notice Borrow DAI using a MakerDAO vault - * @param collateral collateral token address - * @param version controller version - * @param amt The amount of debt to repay. If higher than the current debt - will do full repayment + * @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 DAI borrowed. + * @param setId ID stores the amount of debt borrowed. */ function repay( address collateral, - uint256 version, uint256 amt, + uint256 controllerVersion, uint256 getId, uint256 setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - address _collateral = collateral == ethAddr ? wethAddr : collateral; - IController controller = getController(_collateral, version); - uint _amt = amt; + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); + uint _amt = getUint(getId, amt); TokenInterface stableCoin = TokenInterface(CRV_USD); _amt = _amt == uint(-1) ? stableCoin.balanceOf(address(this)) : _amt; - TokenInterface collateralContract = TokenInterface(_collateral); - approve(collateralContract, address(controller), _amt); - controller.repay(_amt, address(this), 2**255-1, true); + approve(stableCoin, address(controller), _amt); - _eventName = "LogRepay(address,uint256,uint256,uint256)"; - _eventParam = abi.encode(collateral, amt, getId, setId); + 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 version controller version * @param min_x 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 liquidate( address collateral, - uint256 version, - uint256 min_x + uint256 min_x, + uint256 controllerVersion, + uint256 getId, + uint256 setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - address _collateral = collateral == ethAddr ? wethAddr : collateral; - IController controller = getController(_collateral, version); + bool _isEth = collateral == ethAddr; + address _collateralAddress = _isEth ? wethAddr : collateral; + IController controller = getController(_collateralAddress, controllerVersion); + uint _min_x = getUint(getId, min_x); - controller.liquidate(address(this), min_x, collateral == ethAddr); + controller.liquidate(address(this), _min_x, _isEth); - _eventName = "LogLiquidate(address,uint256)"; - _eventParam = abi.encode(collateral, min_x); + setUint(setId, _min_x); + _eventName = "LogLiquidate(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(collateral, _min_x, controllerVersion, getId, setId); } } diff --git a/test/mainnet/crv_usd/crv_usd.test.ts b/test/mainnet/crv_usd/crv_usd.test.ts index 265308e2..fab52797 100644 --- a/test/mainnet/crv_usd/crv_usd.test.ts +++ b/test/mainnet/crv_usd/crv_usd.test.ts @@ -14,9 +14,7 @@ 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 { ConnectV2CRV__factory, IERC20Minimal__factory } from "../../../typechain"; -import { MaxUint256 } from "@uniswap/sdk-core"; -import { USDC_OPTIMISTIC_KOVAN } from "@uniswap/smart-order-router"; +import { ConnectV2CurveUSD__factory, IERC20Minimal__factory } from "../../../typechain"; // import ABI_Ctr from "./ABI.json" @@ -87,7 +85,7 @@ describe("CRV USD", function () { instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); connector = await deployAndEnableConnector({ connectorName, - contractArtifact: ConnectV2CRV__factory, + contractArtifact: ConnectV2CurveUSD__factory, signer: masterSigner, connectors: instaConnectorsV2 }); @@ -174,7 +172,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "createLoan", - args: [tokens.sfrxeth.address, "0", ethers.utils.parseEther('1').toString(), ethers.utils.parseEther('1000'), 10] + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), ethers.utils.parseEther('1000'), "10", "1", "0", "0"] } ]; @@ -192,7 +190,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "addCollateral", - args: [tokens.sfrxeth.address, "0", ethers.utils.parseEther('1').toString(), 0, 0] + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), "1", 0, 0] } ]; @@ -210,7 +208,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "removeCollateral", - args: [tokens.sfrxeth.address, "0", ethers.utils.parseEther('1').toString(), 0, 0] + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), "1", 0, 0] } ]; @@ -228,7 +226,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "borrowMore", - args: [tokens.sfrxeth.address, "0", '0', ethers.utils.parseEther('50')] + args: [tokens.sfrxeth.address, '0', ethers.utils.parseEther('50'), "1", 0, 0] } ]; @@ -240,13 +238,13 @@ describe("CRV USD", function () { ); }); - it("borrow more", async function () { + it("borrow more with maximum value", async function () { const balance = await crvUSD.balanceOf(dsaWallet0.address) const spells = [ { connector: connectorName, method: "borrowMore", - args: [tokens.sfrxeth.address, "0", ethers.utils.parseEther('2'), dsaMaxValue] + args: [tokens.sfrxeth.address, ethers.utils.parseEther('2'), dsaMaxValue, 1, 0, 0] } ]; @@ -263,19 +261,19 @@ describe("CRV USD", function () { { connector: connectorName, method: "createLoan", - args: [tokens.sfrxeth.address, "0", ethers.utils.parseEther('1').toString(), dsaMaxValue, 10] + 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, "0", ethers.utils.parseEther('1').toString(), dsaMaxValue, 10] + args: [tokens.sfrxeth.address, ethers.utils.parseEther('1').toString(), dsaMaxValue, 10, "1", "0", "0"] } ]; @@ -289,12 +287,40 @@ describe("CRV USD", function () { 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, "0", dsaMaxValue, dsaMaxValue, 10] + args: [tokens.sfrxeth.address, dsaMaxValue, dsaMaxValue, 10, "1", "0", "0"] } ]; @@ -316,7 +342,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "createLoan", - args: [tokens.eth.address, "0", ethers.utils.parseEther('2').toString(), dsaMaxValue, 10] + args: [tokens.eth.address, ethers.utils.parseEther('2').toString(), dsaMaxValue, 10, "0", "0", "0"] } ]; @@ -335,7 +361,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "addCollateral", - args: [tokens.eth.address, "0", ethers.utils.parseEther('3').toString(), 0, 0] + args: [tokens.eth.address, ethers.utils.parseEther('3').toString(), 0, 0, 0] } ]; @@ -353,7 +379,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "removeCollateral", - args: [tokens.eth.address, "0", ethers.utils.parseEther('1').toString(), 0, 0] + args: [tokens.eth.address, ethers.utils.parseEther('1').toString(), 0, 0, 0] } ]; @@ -371,7 +397,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "borrowMore", - args: [tokens.eth.address, "0", '0', ethers.utils.parseEther('10')] + args: [tokens.eth.address, '0', ethers.utils.parseEther('10'), 0, 0, 0] } ]; @@ -389,7 +415,7 @@ describe("CRV USD", function () { { connector: connectorName, method: "borrowMore", - args: [tokens.eth.address, "0", '0', dsaMaxValue] + args: [tokens.eth.address, '0', dsaMaxValue, 0, 0, 0] } ]; @@ -400,5 +426,34 @@ describe("CRV USD", function () { ); }); + + 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)) + }); + }); });