diff --git a/src/views/Bond/hooks/useBondV3.ts b/src/views/Bond/hooks/useBondV3.ts index 274992003a..a384b937d7 100644 --- a/src/views/Bond/hooks/useBondV3.ts +++ b/src/views/Bond/hooks/useBondV3.ts @@ -1,7 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { BigNumber } from "ethers"; import { NetworkId } from "src/constants"; -import { BOND_AGGREGATOR_CONTRACT, BOND_FIXED_EXPIRY_TELLER } from "src/constants/contracts"; +import { RANGE_OPERATOR_ADDRESSES } from "src/constants/addresses"; +import { BOND_AGGREGATOR_CONTRACT, BOND_FIXED_EXPIRY_TELLER, RANGE_PRICE_CONTRACT } from "src/constants/contracts"; import { OHM_TOKEN } from "src/constants/tokens"; import { getTokenByAddress } from "src/helpers/contracts/getTokenByAddress"; import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber"; @@ -73,7 +74,12 @@ export const fetchBondV3 = async ({ id, isInverseBond, networkId }: UseBondOptio const quoteTokenPerBaseToken = new DecimalBigNumber(bondMarketPrice.mul(shift), 36); const bondTeller = BOND_FIXED_EXPIRY_TELLER.getEthersContract(networkId); const bondToken = await bondTeller.getBondTokenForMarket(id); - const priceInUsd = quoteTokenPerUsd.mul(quoteTokenPerBaseToken); + const rbsBond = market.owner === RANGE_OPERATOR_ADDRESSES[networkId]; + const rangePriceContract = RANGE_PRICE_CONTRACT.getEthersContract(networkId); + const priceInUsd = rbsBond + ? new DecimalBigNumber(await rangePriceContract.getCurrentPrice(), 18).mul(quoteTokenPerBaseToken) + : quoteTokenPerUsd.mul(quoteTokenPerBaseToken); + const discount = baseTokenPerUsd.sub(priceInUsd).div(baseTokenPerUsd); /** diff --git a/src/views/Range/__mocks__/mockRangeCalls.tsx b/src/views/Range/__mocks__/mockRangeCalls.tsx index 1b8f579f44..db0aa79d85 100644 --- a/src/views/Range/__mocks__/mockRangeCalls.tsx +++ b/src/views/Range/__mocks__/mockRangeCalls.tsx @@ -26,7 +26,7 @@ export const RangeData: OlympusRange.RangeStruct = { threshold: BigNumber.from("100000000000000000000000"), }, wall: { - low: { price: BigNumber.from("16117147092410245645") }, + low: { price: BigNumber.from("13117147092410245645") }, high: { price: BigNumber.from("24175720638615368468") }, spread: BigNumber.from(2000), }, diff --git a/src/views/Range/__tests__/Range.tsx b/src/views/Range/__tests__/Range.tsx index 7accf33ec9..006c81bb3e 100644 --- a/src/views/Range/__tests__/Range.tsx +++ b/src/views/Range/__tests__/Range.tsx @@ -162,32 +162,32 @@ describe("Sell Tab Main Range View", () => { expect(await screen.findByTestId("max-row")).toHaveTextContent("Max You Can Sell"); }); - it("Should Display Premium instead of Discount", async () => { + it("Should Display Discount instead of Premium", async () => { const { container } = render(); fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); - expect(await screen.findByTestId("premium-discount")).toHaveTextContent("Premium"); + expect(await screen.findByTestId("premium-discount")).toHaveTextContent("Discount"); }); - it("Should populate DAI Value automatically with 100 when 6.204572026713784 DAI amount is entered", async () => { + it("Should populate DAI Value automatically with 81.38628391985866 when 6.204572026713784 DAI amount is entered", async () => { const { container } = render(); fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); fireEvent.input(await screen.findByTestId("ohm-amount"), { target: { value: "6.204572026713784" } }); - expect(await screen.findByTestId("reserve-amount")).toHaveValue("100"); + expect(await screen.findByTestId("reserve-amount")).toHaveValue("81.38628391985866"); }); - it("Should populate OHM Value automatically with 6.204572026713784 OHM when 100 DAI is entered", async () => { + it("Should populate OHM Value automatically with 7.623608952122014 OHM when 100 DAI is entered", async () => { const { container } = render(); fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); fireEvent.input(await screen.findByTestId("reserve-amount"), { target: { value: "100" } }); - expect(await screen.findByTestId("ohm-amount")).toHaveValue("6.204572026713784"); + expect(await screen.findByTestId("ohm-amount")).toHaveValue("7.623608952122014"); }); it("Should change the OHM Value when switching back to the Buy Tab", async () => { const { container } = render(); fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); fireEvent.input(await screen.findByTestId("ohm-amount"), { target: { value: "6.204572026713784" } }); - expect(await screen.findByTestId("reserve-amount")).toHaveValue("100"); + expect(await screen.findByTestId("reserve-amount")).toHaveValue("81.38628391985866"); fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); - expect(await screen.findByTestId("ohm-amount")).toHaveValue("4.136381351142522"); + expect(await screen.findByTestId("ohm-amount")).toHaveValue("3.366447070448939"); }); it("Should display Amount exceeds balance when OHM amount entered exceeds balance", async () => { @@ -209,10 +209,10 @@ describe("Sell Tab Main Range View", () => { expect(await screen.findByTestId("ohm-amount")).toHaveValue("10"); }); - it("Should render with Bid price of $16.12 on chart", async () => { + it("Should render with Bid price of $13.12 on chart", async () => { const { container } = render(); fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); - expect(await screen.findByText("Bid: $16.12")); + expect(await screen.findByText("Bid: $13.12")); }); }); diff --git a/src/views/Range/__tests__/RangeBondsUpper.tsx b/src/views/Range/__tests__/RangeBondsUpper.tsx index 1193528d73..c5a9392304 100644 --- a/src/views/Range/__tests__/RangeBondsUpper.tsx +++ b/src/views/Range/__tests__/RangeBondsUpper.tsx @@ -44,7 +44,7 @@ describe("Upper Wall Active Bond Market", () => { Balance.useBalance = jest.fn().mockReturnValue({ 1: { data: new DecimalBigNumber("10", 9) } }); //@ts-expect-error rangeData.mockReturnValueOnce({ - range: jest.fn().mockReturnValueOnce({ ...RangeData, high: { ...RangeData.high, market: BigNumber.from("1") } }), + range: jest.fn().mockReturnValueOnce({ ...RangeData, high: { ...RangeData.high } }), reserve: jest.fn().mockReturnValue("address"), }); //@ts-expect-error @@ -108,8 +108,9 @@ describe("Upper Wall Active Bond Market", () => { it("Should have a disclaimer notifying a buy above current market price ($13.20)", async () => { //@ts-ignore - RangeHooks.DetermineRangePrice = jest.fn().mockReturnValue({ data: { price: "14.12" } }); - render(); + RangeHooks.DetermineRangePrice = jest.fn().mockReturnValue({ data: { price: "14.20" } }); + const { container } = render(); + fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); fireEvent.input(await screen.findByTestId("reserve-amount"), { target: { value: "6" } }); fireEvent.click(screen.getByTestId("range-submit")); expect(screen.getByTestId("disclaimer")).toHaveTextContent( @@ -119,12 +120,9 @@ describe("Upper Wall Active Bond Market", () => { it("Should successfully complete buy regular bond transaction", async () => { //@ts-ignore - RangeHooks.DetermineRangePrice = jest.fn().mockReturnValue({ data: { price: "14.12" } }); - render( - <> - - , - ); + RangeHooks.DetermineRangePrice = jest.fn().mockReturnValue({ data: { price: "14.20" } }); + const { container } = render(); + fireEvent.click(container.getElementsByClassName("arrow-wrapper")[0]); fireEvent.input(await screen.findByTestId("reserve-amount"), { target: { value: "6" } }); fireEvent.click(screen.getByTestId("range-submit")); fireEvent.click(screen.getByTestId("disclaimer-checkbox")); diff --git a/src/views/Range/hooks.tsx b/src/views/Range/hooks.tsx index 54aac02fd2..0415c05e82 100644 --- a/src/views/Range/hooks.tsx +++ b/src/views/Range/hooks.tsx @@ -23,7 +23,7 @@ import { assert } from "src/helpers/types/assert"; import { useTestableNetworks } from "src/hooks/useTestableNetworks"; import { BondFixedTermSDA__factory, BondTeller__factory, IERC20__factory } from "src/typechain"; import { RANGEv1 as OlympusRange } from "src/typechain/Range"; -import { useNetwork, useSigner } from "wagmi"; +import { useSigner } from "wagmi"; /**Chainlink Price Feed. Retrieves OHMETH and ETH/{RESERVE} feed **/ export const OHMPriceHistory = (assetPair = "OHMv2/ETH") => { @@ -105,10 +105,10 @@ export const PriceHistory = (reserveToken: string) => { * Returns the current price of the Operator at the given address */ export const OperatorPrice = () => { - const { chain = { id: 1 } } = useNetwork(); + const networks = useTestableNetworks(); - const contract = RANGE_PRICE_CONTRACT.getEthersContract(chain.id); - const { data, isFetched, isLoading } = useQuery(["getOperatorPrice", chain], async () => { + const contract = RANGE_PRICE_CONTRACT.getEthersContract(networks.MAINNET); + const { data, isFetched, isLoading } = useQuery(["getOperatorPrice", networks.MAINNET], async () => { return parseBigNumber(await contract.getCurrentPrice(), 18); }); return { data, isFetched, isLoading }; @@ -118,14 +118,14 @@ export const OperatorPrice = () => { * Returns the current price of the Operator at the given address */ export const OperatorMovingAverage = () => { - const { chain = { id: 1 } } = useNetwork(); + const networks = useTestableNetworks(); - const contract = RANGE_PRICE_CONTRACT.getEthersContract(chain.id); + const contract = RANGE_PRICE_CONTRACT.getEthersContract(networks.MAINNET); const { data = { movingAverage: 0, days: 30 }, isFetched, isLoading, - } = useQuery(["getOperatorMovingAverage", chain], async () => { + } = useQuery(["getOperatorMovingAverage", networks.MAINNET], async () => { const movingAverage = parseBigNumber(await contract.getMovingAverage(), 18); const movingAverageSeconds = await contract.movingAverageDuration(); const days = movingAverageSeconds / 60 / 60 / 24; //seconds to days; @@ -138,14 +138,14 @@ export const OperatorMovingAverage = () => { * Returns the reserve contract address on the Operator */ export const OperatorReserveSymbol = () => { - const { chain = { id: 1 } } = useNetwork(); - const contract = RANGE_CONTRACT.getEthersContract(chain.id); + const networks = useTestableNetworks(); + const contract = RANGE_CONTRACT.getEthersContract(networks.MAINNET); const { data = { symbol: "", reserveAddress: "" }, isFetched, isLoading, - } = useQuery(["getOperatorReserveSymbol", chain], async () => { - const provider = Providers.getStaticProvider(chain.id); + } = useQuery(["getOperatorReserveSymbol", networks.MAINNET], async () => { + const provider = Providers.getStaticProvider(networks.MAINNET); const reserveAddress = await contract.reserve(); const TokenContract = IERC20__factory.connect(reserveAddress, provider); const symbol = await TokenContract.symbol(); @@ -159,8 +159,8 @@ export const OperatorReserveSymbol = () => { */ export const RangeData = () => { - const { chain = { id: 1 } } = useNetwork(); - const contract = RANGE_CONTRACT.getEthersContract(chain.id); + const networks = useTestableNetworks(); + const contract = RANGE_CONTRACT.getEthersContract(networks.MAINNET); const { data = { @@ -171,7 +171,7 @@ export const RangeData = () => { } as OlympusRange.RangeStructOutput, isFetched, isLoading, - } = useQuery(["getRangeData", chain.id], async () => { + } = useQuery(["getRangeData", networks.MAINNET], async () => { const range = await contract.range(); return range; }); @@ -200,24 +200,25 @@ const band: OlympusRange.BandStruct = { * @param id Bond Market ID */ export const RangeBondPrice = (id: BigNumber, side: "low" | "high") => { - const { chain = { id: 1 } } = useNetwork(); - const contract = BOND_AGGREGATOR_CONTRACT.getEthersContract(chain.id); + const networks = useTestableNetworks(); + const contract = BOND_AGGREGATOR_CONTRACT.getEthersContract(networks.MAINNET); const { data, isFetched, isLoading } = useQuery( - ["getRangeBondPrice", id, chain, side], + ["getRangeBondPrice", id, networks.MAINNET, side], async () => { const bondPrice = await contract.marketPrice(id); const auctioneerAddress = await contract.getAuctioneer(id); const auctioneerContract = BondFixedTermSDA__factory.connect(auctioneerAddress, contract.provider); const market = await auctioneerContract.markets(id); const inverse = - market.payoutToken.toLowerCase() !== OHM_ADDRESSES[chain.id as keyof typeof OHM_ADDRESSES].toLowerCase(); + market.payoutToken.toLowerCase() !== + OHM_ADDRESSES[networks.MAINNET as keyof typeof OHM_ADDRESSES].toLowerCase(); const baseToken = inverse - ? await getTokenByAddress({ address: market.payoutToken, networkId: chain.id }) + ? await getTokenByAddress({ address: market.payoutToken, networkId: networks.MAINNET }) : OHM_TOKEN; assert(baseToken, `Unknown base token address: ${market.payoutToken}`); const quoteToken = inverse ? OHM_TOKEN - : await getTokenByAddress({ address: market.quoteToken, networkId: chain.id }); + : await getTokenByAddress({ address: market.quoteToken, networkId: networks.MAINNET }); assert(quoteToken, `Unknown quote token address: ${market.quoteToken}`); const scale = await contract.marketScale(id); @@ -237,11 +238,11 @@ export const RangeBondPrice = (id: BigNumber, side: "low" | "high") => { }; export const RangeBondMaxPayout = (id: BigNumber) => { - const { chain = { id: 1 } } = useNetwork(); - const aggregatorContract = BOND_AGGREGATOR_CONTRACT.getEthersContract(chain.id); + const networks = useTestableNetworks(); + const aggregatorContract = BOND_AGGREGATOR_CONTRACT.getEthersContract(networks.MAINNET); const { data, isFetched, isLoading } = useQuery( - ["getRangeBondMaxPayout", id, chain], + ["getRangeBondMaxPayout", id, networks.MAINNET], async () => { const auctioneerAddress = await aggregatorContract.getAuctioneer(id); const contract = BondFixedTermSDA__factory.connect(auctioneerAddress, aggregatorContract.provider); @@ -256,10 +257,10 @@ export const RangeBondMaxPayout = (id: BigNumber) => { }; export const BondTellerAddress = (id: BigNumber) => { - const { chain = { id: 1 } } = useNetwork(); - const contract = BOND_AGGREGATOR_CONTRACT.getEthersContract(chain.id); + const networks = useTestableNetworks(); + const contract = BOND_AGGREGATOR_CONTRACT.getEthersContract(networks.MAINNET); const { data, isFetched, isLoading } = useQuery( - ["getRangeBondTeller", id, chain], + ["getRangeBondTeller", id, networks.MAINNET], async () => { const tellerAddress = await contract.getTeller(id); return tellerAddress; @@ -351,7 +352,6 @@ type RangeContracts = "swap" | "bond"; export const RangeSwap = () => { const networks = useTestableNetworks(); const { data: signer } = useSigner(); - const { chain = { id: 1 } } = useNetwork(); const referrer = DAO_TREASURY_ADDRESSES[networks.MAINNET]; return useMutation< @@ -369,8 +369,8 @@ export const RangeSwap = () => { } >( async ({ market, tokenAddress, swapType, amount, receiveAmount, sellActive, slippage, recipientAddress }) => { - const decimals = tokenAddress === OHM_ADDRESSES[chain.id as keyof typeof OHM_ADDRESSES] ? 9 : 18; - const receiveDecimals = tokenAddress === OHM_ADDRESSES[chain.id as keyof typeof OHM_ADDRESSES] ? 18 : 9; //opposite of send + const decimals = tokenAddress === OHM_ADDRESSES[networks.MAINNET as keyof typeof OHM_ADDRESSES] ? 9 : 18; + const receiveDecimals = tokenAddress === OHM_ADDRESSES[networks.MAINNET as keyof typeof OHM_ADDRESSES] ? 18 : 9; //opposite of send if (!signer) throw new Error(t`Please connect a wallet to Range Swap`); if (!isValidAddress(recipientAddress) || recipientAddress === "") throw new Error(t`Invalid address`); diff --git a/src/views/Range/index.tsx b/src/views/Range/index.tsx index ae89d897cf..860336b49a 100644 --- a/src/views/Range/index.tsx +++ b/src/views/Range/index.tsx @@ -69,14 +69,21 @@ export const Range = () => { const buyAsset = sellActive ? reserveSymbol : "OHM"; const sellAsset = sellActive ? "OHM" : reserveSymbol; + const { data: bidPrice } = DetermineRangePrice("bid"); + const { data: askPrice } = DetermineRangePrice("ask"); + useEffect(() => { if (reserveAmount && ohmAmount) { handleChangeReserveAmount(reserveAmount); } }, [sellActive]); - const { data: bidPrice } = DetermineRangePrice("bid"); - const { data: askPrice } = DetermineRangePrice("ask"); + useEffect(() => { + const sellDiscount = (currentPrice - bidPrice.price) / -currentPrice; + if (sellDiscount > 0) { + setSellActive(true); + } + }, [bidPrice, currentPrice]); const maxBalanceString = `${maxCapacity.toFixed(2)} ${buyAsset} (${(sellActive ? maxCapacity / bidPrice.price @@ -149,22 +156,20 @@ export const Range = () => { )}
- - setSellActive(!sellActive)} - sellActive={sellActive} - reserveBalance={reserveBalance} - ohmBalance={ohmBalance} - onFormSubmit={handleSubmit} - onChangeReserveAmount={handleChangeReserveAmount} - onChangeOhmAmount={handleChangeOhmAmount} - ohmAmount={ohmAmount} - reserveAmount={reserveAmount} - capacity={maxCapacity} - hasPrice={hasPrice} - /> - + setSellActive(!sellActive)} + sellActive={sellActive} + reserveBalance={reserveBalance} + ohmBalance={ohmBalance} + onFormSubmit={handleSubmit} + onChangeReserveAmount={handleChangeReserveAmount} + onChangeOhmAmount={handleChangeOhmAmount} + ohmAmount={ohmAmount} + reserveAmount={reserveAmount} + capacity={maxCapacity} + hasPrice={hasPrice} + /> {hasPrice && ( @@ -212,25 +217,27 @@ export const Range = () => { - - {amountAboveCapacity - ? `Amount exceeds capacity` - : amountAboveBalance - ? `Amount exceeds balance` - : swapButtonText} - + + + {amountAboveCapacity + ? `Amount exceeds capacity` + : amountAboveBalance + ? `Amount exceeds balance` + : swapButtonText} + +