diff --git a/src/abi/CoolerConsolidation.json b/src/abi/CoolerConsolidation.json index 075b2e934..74190aaaa 100644 --- a/src/abi/CoolerConsolidation.json +++ b/src/abi/CoolerConsolidation.json @@ -3,13 +3,41 @@ { "type": "constructor", "inputs": [ - { "name": "gohm_", "type": "address", "internalType": "address" }, - { "name": "sdai_", "type": "address", "internalType": "address" }, - { "name": "dai_", "type": "address", "internalType": "address" }, - { "name": "owner_", "type": "address", "internalType": "address" }, - { "name": "lender_", "type": "address", "internalType": "address" }, - { "name": "collector_", "type": "address", "internalType": "address" }, - { "name": "feePercentage_", "type": "uint256", "internalType": "uint256" } + { + "name": "gohm_", + "type": "address", + "internalType": "address" + }, + { + "name": "sdai_", + "type": "address", + "internalType": "address" + }, + { + "name": "dai_", + "type": "address", + "internalType": "address" + }, + { + "name": "owner_", + "type": "address", + "internalType": "address" + }, + { + "name": "lender_", + "type": "address", + "internalType": "address" + }, + { + "name": "collector_", + "type": "address", + "internalType": "address" + }, + { + "name": "feePercentage_", + "type": "uint256", + "internalType": "uint256" + } ], "stateMutability": "nonpayable" }, @@ -17,25 +45,109 @@ "type": "function", "name": "ONE_HUNDRED_PERCENT", "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "collateralRequired", + "inputs": [ + { + "name": "clearinghouse_", + "type": "address", + "internalType": "address" + }, + { + "name": "cooler_", + "type": "address", + "internalType": "address" + }, + { + "name": "ids_", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "outputs": [ + { + "name": "consolidatedLoanCollateral", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "existingLoanCollateral", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "additionalCollateral", + "type": "uint256", + "internalType": "uint256" + } + ], "stateMutability": "view" }, { "type": "function", "name": "collector", "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], "stateMutability": "view" }, { "type": "function", "name": "consolidateWithFlashLoan", "inputs": [ - { "name": "clearinghouse_", "type": "address", "internalType": "address" }, - { "name": "cooler_", "type": "address", "internalType": "address" }, - { "name": "ids_", "type": "uint256[]", "internalType": "uint256[]" }, - { "name": "useFunds_", "type": "uint256", "internalType": "uint256" }, - { "name": "sdai_", "type": "bool", "internalType": "bool" } + { + "name": "clearinghouse_", + "type": "address", + "internalType": "address" + }, + { + "name": "cooler_", + "type": "address", + "internalType": "address" + }, + { + "name": "ids_", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "useFunds_", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "sdai_", + "type": "bool", + "internalType": "bool" + } ], "outputs": [], "stateMutability": "nonpayable" @@ -44,71 +156,171 @@ "type": "function", "name": "dai", "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "contract IERC20" }], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IERC20" + } + ], "stateMutability": "view" }, { "type": "function", "name": "feePercentage", "inputs": [], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], "stateMutability": "view" }, { "type": "function", "name": "getProtocolFee", - "inputs": [{ "name": "totalDebt_", "type": "uint256", "internalType": "uint256" }], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "inputs": [ + { + "name": "totalDebt_", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], "stateMutability": "view" }, { "type": "function", "name": "gohm", "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "contract IERC20" }], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IERC20" + } + ], "stateMutability": "view" }, { "type": "function", "name": "lender", "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "contract IERC3156FlashLender" }], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IERC3156FlashLender" + } + ], "stateMutability": "view" }, { "type": "function", "name": "onFlashLoan", "inputs": [ - { "name": "initiator_", "type": "address", "internalType": "address" }, - { "name": "", "type": "address", "internalType": "address" }, - { "name": "amount_", "type": "uint256", "internalType": "uint256" }, - { "name": "lenderFee_", "type": "uint256", "internalType": "uint256" }, - { "name": "params_", "type": "bytes", "internalType": "bytes" } + { + "name": "initiator_", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "amount_", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "lenderFee_", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "params_", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], - "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], "stateMutability": "nonpayable" }, { "type": "function", "name": "owner", "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], "stateMutability": "view" }, { "type": "function", "name": "requiredApprovals", "inputs": [ - { "name": "clearinghouse_", "type": "address", "internalType": "address" }, - { "name": "cooler_", "type": "address", "internalType": "address" }, - { "name": "ids_", "type": "uint256[]", "internalType": "uint256[]" } + { + "name": "clearinghouse_", + "type": "address", + "internalType": "address" + }, + { + "name": "cooler_", + "type": "address", + "internalType": "address" + }, + { + "name": "ids_", + "type": "uint256[]", + "internalType": "uint256[]" + } ], "outputs": [ - { "name": "", "type": "address", "internalType": "address" }, - { "name": "", "type": "uint256", "internalType": "uint256" }, - { "name": "", "type": "uint256", "internalType": "uint256" }, - { "name": "", "type": "uint256", "internalType": "uint256" }, - { "name": "", "type": "uint256", "internalType": "uint256" } + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], "stateMutability": "view" }, @@ -116,27 +328,51 @@ "type": "function", "name": "sdai", "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "contract IERC4626" }], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IERC4626" + } + ], "stateMutability": "view" }, { "type": "function", "name": "setCollector", - "inputs": [{ "name": "collector_", "type": "address", "internalType": "address" }], + "inputs": [ + { + "name": "collector_", + "type": "address", + "internalType": "address" + } + ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "setFeePercentage", - "inputs": [{ "name": "feePercentage_", "type": "uint256", "internalType": "uint256" }], + "inputs": [ + { + "name": "feePercentage_", + "type": "uint256", + "internalType": "uint256" + } + ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "transferOwnership", - "inputs": [{ "name": "newOwner", "type": "address", "internalType": "address" }], + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], "outputs": [], "stateMutability": "nonpayable" }, @@ -144,17 +380,55 @@ "type": "event", "name": "OwnershipTransferred", "inputs": [ - { "name": "user", "type": "address", "indexed": true, "internalType": "address" }, - { "name": "newOwner", "type": "address", "indexed": true, "internalType": "address" } + { + "name": "user", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } ], "anonymous": false }, - { "type": "error", "name": "InsufficientCoolerCount", "inputs": [] }, - { "type": "error", "name": "OnlyCoolerOwner", "inputs": [] }, - { "type": "error", "name": "OnlyLender", "inputs": [] }, - { "type": "error", "name": "OnlyThis", "inputs": [] }, - { "type": "error", "name": "Params_FeePercentageOutOfRange", "inputs": [] }, - { "type": "error", "name": "Params_InvalidAddress", "inputs": [] }, - { "type": "error", "name": "Params_UseFundsOutOfBounds", "inputs": [] } + { + "type": "error", + "name": "InsufficientCoolerCount", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyCoolerOwner", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyLender", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyThis", + "inputs": [] + }, + { + "type": "error", + "name": "Params_FeePercentageOutOfRange", + "inputs": [] + }, + { + "type": "error", + "name": "Params_InvalidAddress", + "inputs": [] + }, + { + "type": "error", + "name": "Params_UseFundsOutOfBounds", + "inputs": [] + } ] } diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts index a867c59d5..67e8b266f 100644 --- a/src/constants/addresses.ts +++ b/src/constants/addresses.ts @@ -284,6 +284,6 @@ export const COOLER_CLEARING_HOUSE_V2_ADDRESSES = { }; export const COOLER_CONSOLIDATION_ADDRESSES = { - [NetworkId.MAINNET]: "0x52616f2730EF07e41d5c7cb209927f40b9A2d6Da", + [NetworkId.MAINNET]: "0xB15bcb1b6593d85890f5287Baa2245B8A29F464a", [NetworkId.TESTNET_GOERLI]: "", }; diff --git a/src/views/Lending/Cooler/hooks/useGetCollateralRequired.tsx b/src/views/Lending/Cooler/hooks/useGetCollateralRequired.tsx new file mode 100644 index 000000000..7651d7e65 --- /dev/null +++ b/src/views/Lending/Cooler/hooks/useGetCollateralRequired.tsx @@ -0,0 +1,37 @@ +import { useQuery } from "@tanstack/react-query"; +import { COOLER_CONSOLIDATION_CONTRACT } from "src/constants/contracts"; +import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber"; +import { useTestableNetworks } from "src/hooks/useTestableNetworks"; +import { CoolerConsolidation__factory } from "src/typechain"; +import { useSigner } from "wagmi"; + +export const useGetCollateralRequired = ({ + coolerAddress, + clearingHouseAddress, + loanIds, +}: { + coolerAddress?: string; + clearingHouseAddress?: string; + loanIds?: number[]; +}) => { + const networks = useTestableNetworks(); + const { data: signer } = useSigner(); + + const { data, isFetched, isLoading } = useQuery( + ["getCollateralRequired", networks.MAINNET, coolerAddress, clearingHouseAddress, loanIds], + async () => { + try { + if (!coolerAddress || !clearingHouseAddress || !loanIds || !signer) return new DecimalBigNumber("0"); + const contractAddress = COOLER_CONSOLIDATION_CONTRACT.addresses[networks.MAINNET]; + const contract = CoolerConsolidation__factory.connect(contractAddress, signer); + + const approvals = await contract.collateralRequired(clearingHouseAddress, coolerAddress, loanIds); + return new DecimalBigNumber(approvals.additionalCollateral, 18); + } catch (e) { + return new DecimalBigNumber("0"); + } + }, + { enabled: !!coolerAddress && !!clearingHouseAddress && !!loanIds && !!signer }, + ); + return { data, isFetched, isLoading }; +}; diff --git a/src/views/Lending/Cooler/positions/ConsolidateLoan.tsx b/src/views/Lending/Cooler/positions/ConsolidateLoan.tsx index 1670dac9f..933d60234 100644 --- a/src/views/Lending/Cooler/positions/ConsolidateLoan.tsx +++ b/src/views/Lending/Cooler/positions/ConsolidateLoan.tsx @@ -1,5 +1,5 @@ import { Box, SvgIcon, Typography } from "@mui/material"; -import { InfoNotification, Modal, PrimaryButton } from "@olympusdao/component-library"; +import { ErrorNotification, InfoNotification, Modal, PrimaryButton } from "@olympusdao/component-library"; import { BigNumber, ethers } from "ethers"; import { formatEther } from "ethers/lib/utils.js"; import { useState } from "react"; @@ -8,7 +8,10 @@ import { TokenAllowanceGuard } from "src/components/TokenAllowanceGuard/TokenAll import { COOLER_CONSOLIDATION_ADDRESSES, DAI_ADDRESSES, GOHM_ADDRESSES } from "src/constants/addresses"; import { formatNumber } from "src/helpers"; import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber"; +import { useBalance } from "src/hooks/useBalance"; +import { useTestableNetworks } from "src/hooks/useTestableNetworks"; import { useConsolidateCooler } from "src/views/Lending/Cooler/hooks/useConsolidateCooler"; +import { useGetCollateralRequired } from "src/views/Lending/Cooler/hooks/useGetCollateralRequired"; import { useGetCoolerLoans } from "src/views/Lending/Cooler/hooks/useGetCoolerLoans"; export const ConsolidateLoans = ({ @@ -16,16 +19,26 @@ export const ConsolidateLoans = ({ clearingHouseAddress, loans, duration, + collateralAddress, }: { coolerAddress: string; clearingHouseAddress: string; loans: NonNullable["data"]>; duration: string; + collateralAddress: string; }) => { const coolerMutation = useConsolidateCooler(); const [open, setOpen] = useState(false); const loanIds = loans.map(loan => loan.loanId); - + const networks = useTestableNetworks(); + const { data: collateralBalance, isFetched: balanceFetched } = useBalance({ + [networks.MAINNET]: collateralAddress || "", + })[networks.MAINNET]; + const { data: requiredCollateral, isFetched: requiredCollateralFetched } = useGetCollateralRequired({ + coolerAddress, + clearingHouseAddress, + loanIds, + }); const totals = loans.reduce( (acc, loan) => { acc.principal = acc.principal.add(loan.principal); @@ -38,6 +51,10 @@ export const ConsolidateLoans = ({ const maturityDate = new Date(); maturityDate.setDate(maturityDate.getDate() + Number(duration || 0)); + const collateralNeeded = collateralBalance && requiredCollateral && requiredCollateral.gt(collateralBalance); + const collateralAmount = collateralNeeded ? requiredCollateral.toString() : "0"; + + console.log(requiredCollateral, collateralBalance, collateralNeeded, collateralAmount); return ( <> setOpen(!open)}>Consolidate Loans @@ -123,45 +140,60 @@ export const ConsolidateLoans = ({ - Approve DAI for Spending on the Consolidation Contract} - spendAmount={new DecimalBigNumber(ethers.constants.MaxUint256, 18)} - approvalText="Approve DAI for Spending" - > - Approve gOHM for Spending on the Consolidation Contract} - spendAmount={new DecimalBigNumber(totals.collateral, 18)} - approvalText="Approve gOHM for Spending" - > - { - coolerMutation.mutate( - { - coolerAddress, - clearingHouseAddress, - loanIds, - }, - { - onSuccess: () => { - setOpen(false); - }, - }, - ); - }} - loading={coolerMutation.isLoading} - disabled={coolerMutation.isLoading} - fullWidth - > - Consolidate Loans - - - + {balanceFetched && requiredCollateralFetched && ( + <> + {collateralNeeded ? ( + <> + + You need {collateralAmount} more gOHM in your wallet to consolidate these loans + + + Insufficent Collateral Balance + + + ) : ( + Approve DAI for Spending on the Consolidation Contract} + spendAmount={new DecimalBigNumber(ethers.constants.MaxUint256, 18)} + approvalText="Approve DAI for Spending" + > + Approve gOHM for Spending on the Consolidation Contract} + spendAmount={new DecimalBigNumber(totals.collateral, 18)} + approvalText="Approve gOHM for Spending" + > + { + coolerMutation.mutate( + { + coolerAddress, + clearingHouseAddress, + loanIds, + }, + { + onSuccess: () => { + setOpen(false); + }, + }, + ); + }} + loading={coolerMutation.isLoading} + disabled={coolerMutation.isLoading} + fullWidth + > + Consolidate Loans + + + + )} + + )} diff --git a/src/views/Lending/Cooler/positions/Positions.tsx b/src/views/Lending/Cooler/positions/Positions.tsx index 71cdf0d75..56186fadf 100644 --- a/src/views/Lending/Cooler/positions/Positions.tsx +++ b/src/views/Lending/Cooler/positions/Positions.tsx @@ -248,6 +248,7 @@ export const CoolerPositions = () => { clearingHouseAddress={clearingHouse.clearingHouseAddress} loans={loans} duration={clearingHouse.duration} + collateralAddress={clearingHouse.collateralAddress} /> )}