diff --git a/spot-vaults/package.json b/spot-vaults/package.json index b2915325..1368665c 100644 --- a/spot-vaults/package.json +++ b/spot-vaults/package.json @@ -62,7 +62,7 @@ "ethers": "^6.6.0", "ethers-v5": "npm:ethers@^5.7.0", "ganache-cli": "latest", - "hardhat": "^2.22.8", + "hardhat": "^2.22.10", "hardhat-gas-reporter": "latest", "lodash": "^4.17.21", "prettier": "^2.7.1", diff --git a/spot-vaults/test/BillBroker.ts b/spot-vaults/test/BillBroker.ts index 230b794e..195e29a7 100644 --- a/spot-vaults/test/BillBroker.ts +++ b/spot-vaults/test/BillBroker.ts @@ -1,7 +1,7 @@ import { ethers, upgrades } from "hardhat"; import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; import { expect } from "chai"; -import { DMock, usdFP, perpFP, percentageFP, priceFP } from "./helpers"; +import { DMock, usdFP, perpFP, percFP, priceFP } from "./helpers"; describe("BillBroker", function () { async function setupContracts() { @@ -13,11 +13,12 @@ describe("BillBroker", function () { await usd.init("USD token", "usd", 6); const perp = await Token.deploy(); await perp.init("Perp token", "perp", 9); - const pricingStrategy = new DMock("SpotAppraiser"); + const pricingStrategy = new DMock("IPerpPricer"); await pricingStrategy.deploy(); await pricingStrategy.mockMethod("decimals()", [18]); - await pricingStrategy.mockMethod("perpPrice()", [0, false]); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [0, false]); await pricingStrategy.mockMethod("usdPrice()", [0, false]); + await pricingStrategy.mockMethod("perpDiscountFactor()", [percFP("1")]); const BillBroker = await ethers.getContractFactory("BillBroker"); const billBroker = await upgrades.deployProxy( @@ -54,10 +55,10 @@ describe("BillBroker", function () { const fees = await billBroker.fees(); expect(fees.mintFeePerc).to.eq(0); expect(fees.burnFeePerc).to.eq(0); - expect(fees.perpToUSDSwapFeePercs.lower).to.eq(percentageFP("1")); - expect(fees.perpToUSDSwapFeePercs.upper).to.eq(percentageFP("1")); - expect(fees.usdToPerpSwapFeePercs.lower).to.eq(percentageFP("1")); - expect(fees.usdToPerpSwapFeePercs.upper).to.eq(percentageFP("1")); + expect(fees.perpToUSDSwapFeePercs.lower).to.eq(percFP("1")); + expect(fees.perpToUSDSwapFeePercs.upper).to.eq(percFP("1")); + expect(fees.usdToPerpSwapFeePercs.lower).to.eq(percFP("1")); + expect(fees.usdToPerpSwapFeePercs.upper).to.eq(percFP("1")); expect(fees.protocolSwapSharePerc).to.eq(0); expect(await billBroker.usdBalance()).to.eq(0n); @@ -99,7 +100,7 @@ describe("BillBroker", function () { describe("when pricing strategy is not valid", function () { it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - const pricingStrategy = new DMock("SpotAppraiser"); + const pricingStrategy = new DMock("SpotPricer"); await pricingStrategy.deploy(); await pricingStrategy.mockMethod("decimals()", [17]); await expect( @@ -108,7 +109,7 @@ describe("BillBroker", function () { }); it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - const pricingStrategy = new DMock("SpotAppraiser"); + const pricingStrategy = new DMock("SpotPricer"); await pricingStrategy.deploy(); await pricingStrategy.mockMethod("decimals()", [18]); @@ -122,17 +123,17 @@ describe("BillBroker", function () { let fees: any; beforeEach(async function () { fees = { - mintFeePerc: percentageFP("0.005"), - burnFeePerc: percentageFP("0.025"), + mintFeePerc: percFP("0.005"), + burnFeePerc: percFP("0.025"), perpToUSDSwapFeePercs: { - lower: percentageFP("0.01"), - upper: percentageFP("0.1"), + lower: percFP("0.01"), + upper: percFP("0.1"), }, usdToPerpSwapFeePercs: { - lower: percentageFP("0.02"), - upper: percentageFP("0.2"), + lower: percFP("0.02"), + upper: percFP("0.2"), }, - protocolSwapSharePerc: percentageFP("0.05"), + protocolSwapSharePerc: percFP("0.05"), }; }); @@ -149,7 +150,7 @@ describe("BillBroker", function () { describe("when parameters are invalid", function () { it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - fees.mintFeePerc = percentageFP("1.01"); + fees.mintFeePerc = percFP("1.01"); await expect(billBroker.updateFees(fees)).to.be.revertedWithCustomError( billBroker, "InvalidPerc", @@ -160,7 +161,7 @@ describe("BillBroker", function () { describe("when parameters are invalid", function () { it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - fees.burnFeePerc = percentageFP("1.01"); + fees.burnFeePerc = percFP("1.01"); await expect(billBroker.updateFees(fees)).to.be.revertedWithCustomError( billBroker, "InvalidPerc", @@ -168,8 +169,8 @@ describe("BillBroker", function () { }); it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - fees.perpToUSDSwapFeePercs.lower = percentageFP("0.2"); - fees.perpToUSDSwapFeePercs.upper = percentageFP("0.1"); + fees.perpToUSDSwapFeePercs.lower = percFP("0.2"); + fees.perpToUSDSwapFeePercs.upper = percFP("0.1"); await expect(billBroker.updateFees(fees)).to.be.revertedWithCustomError( billBroker, "InvalidPerc", @@ -177,8 +178,8 @@ describe("BillBroker", function () { }); it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - fees.usdToPerpSwapFeePercs.lower = percentageFP("0.2"); - fees.usdToPerpSwapFeePercs.upper = percentageFP("0.1"); + fees.usdToPerpSwapFeePercs.lower = percFP("0.2"); + fees.usdToPerpSwapFeePercs.upper = percFP("0.1"); await expect(billBroker.updateFees(fees)).to.be.revertedWithCustomError( billBroker, "InvalidPerc", @@ -186,7 +187,7 @@ describe("BillBroker", function () { }); it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); - fees.protocolSwapSharePerc = percentageFP("1.01"); + fees.protocolSwapSharePerc = percFP("1.01"); await expect(billBroker.updateFees(fees)).to.be.revertedWithCustomError( billBroker, "InvalidPerc", @@ -217,8 +218,8 @@ describe("BillBroker", function () { await billBroker.renounceOwnership(); await expect( billBroker.updateARBounds( - [percentageFP("0.9"), percentageFP("1.1")], - [percentageFP("0.8"), percentageFP("1.2")], + [percFP("0.9"), percFP("1.1")], + [percFP("0.8"), percFP("1.2")], ), ).to.be.revertedWith("Ownable: caller is not the owner"); }); @@ -229,8 +230,8 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await expect( billBroker.updateARBounds( - [percentageFP("1.1"), percentageFP("1.0")], - [percentageFP("0.8"), percentageFP("1.2")], + [percFP("1.1"), percFP("1.0")], + [percFP("0.8"), percFP("1.2")], ), ).to.be.revertedWithCustomError(billBroker, "InvalidARBound"); }); @@ -239,8 +240,8 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await expect( billBroker.updateARBounds( - [percentageFP("0.9"), percentageFP("1.1")], - [percentageFP("1.2"), percentageFP("0.8")], + [percFP("0.9"), percFP("1.1")], + [percFP("1.2"), percFP("0.8")], ), ).to.be.revertedWithCustomError(billBroker, "InvalidARBound"); }); @@ -249,8 +250,8 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await expect( billBroker.updateARBounds( - [percentageFP("0.9"), percentageFP("0.8")], - [percentageFP("1.1"), percentageFP("1.2")], + [percFP("0.9"), percFP("0.8")], + [percFP("1.1"), percFP("1.2")], ), ).to.be.revertedWithCustomError(billBroker, "InvalidARBound"); }); @@ -259,8 +260,8 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await expect( billBroker.updateARBounds( - [percentageFP("0.8"), percentageFP("1.2")], - [percentageFP("0.9"), percentageFP("1.1")], + [percFP("0.8"), percFP("1.2")], + [percFP("0.9"), percFP("1.1")], ), ).to.be.revertedWithCustomError(billBroker, "InvalidARBound"); }); @@ -270,16 +271,16 @@ describe("BillBroker", function () { it("should update bound", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( - [percentageFP("0.9"), percentageFP("1.1")], - [percentageFP("0.8"), percentageFP("1.2")], + [percFP("0.9"), percFP("1.1")], + [percFP("0.8"), percFP("1.2")], ); const b1 = await billBroker.arSoftBound(); - expect(b1.lower).to.eq(percentageFP("0.9")); - expect(b1.upper).to.eq(percentageFP("1.1")); + expect(b1.lower).to.eq(percFP("0.9")); + expect(b1.upper).to.eq(percFP("1.1")); const b2 = await billBroker.arHardBound(); - expect(b2.lower).to.eq(percentageFP("0.8")); - expect(b2.upper).to.eq(percentageFP("1.2")); + expect(b2.lower).to.eq(percFP("0.8")); + expect(b2.upper).to.eq(percFP("1.2")); }); }); }); @@ -368,7 +369,7 @@ describe("BillBroker", function () { describe("when the price is invalid", function () { it("should revert", async function () { const { billBroker, pricingStrategy } = await loadFixture(setupContracts); - await pricingStrategy.mockMethod("perpPrice()", [priceFP("1.17"), false]); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [priceFP("1.17"), false]); await expect(billBroker.perpPrice()).to.be.revertedWithCustomError( billBroker, "UnreliablePrice", @@ -379,12 +380,20 @@ describe("BillBroker", function () { describe("when the price is valid", function () { it("should return strategy price", async function () { const { billBroker, pricingStrategy } = await loadFixture(setupContracts); - await pricingStrategy.mockMethod("perpPrice()", [priceFP("1.17"), true]); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [priceFP("1.17"), true]); expect(await billBroker.perpPrice.staticCall()).to.eq(priceFP("1.17")); }); }); }); + describe("#perpDiscountFactor", function () { + it("should return strategy discount", async function () { + const { billBroker, pricingStrategy } = await loadFixture(setupContracts); + await pricingStrategy.mockMethod("perpDiscountFactor()", [priceFP("1.2")]); + expect(await billBroker.perpDiscountFactor.staticCall()).to.eq(priceFP("1.2")); + }); + }); + describe("#usdBalance", function () { it("should return the reserve balance", async function () { const { billBroker, usd } = await loadFixture(setupContracts); @@ -409,17 +418,12 @@ describe("BillBroker", function () { await usd.mint(billBroker.target, usdFP("115")); await perp.mint(billBroker.target, perpFP("100")); await pricingStrategy.mockMethod("usdPrice()", [priceFP("1"), true]); - await pricingStrategy.mockMethod("perpPrice()", [priceFP("1.3"), true]); - const r = { - usdBalance: await billBroker.usdBalance(), - perpBalance: await billBroker.perpBalance(), - usdPrice: await billBroker.usdPrice.staticCall(), - perpPrice: await billBroker.perpPrice.staticCall(), - }; - expect(r.usdBalance).to.eq(usdFP("115")); - expect(r.perpBalance).to.eq(perpFP("100")); - expect(r.usdPrice).to.eq(priceFP("1")); - expect(r.perpPrice).to.eq(priceFP("1.3")); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [priceFP("1.3"), true]); + const r = await billBroker.reserveState.staticCall(); + expect(r[0]).to.eq(usdFP("115")); + expect(r[1]).to.eq(perpFP("100")); + expect(r[2]).to.eq(priceFP("1")); + expect(r[3]).to.eq(priceFP("1.3")); }); }); @@ -427,8 +431,8 @@ describe("BillBroker", function () { it("should compute the right fee perc", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( - [percentageFP("0.75"), percentageFP("1.25")], - [percentageFP("0.5"), percentageFP("1.5")], + [percFP("0.75"), percFP("1.25")], + [percFP("0.5"), percFP("1.5")], ); await billBroker.updateFees({ @@ -439,77 +443,50 @@ describe("BillBroker", function () { upper: 0n, }, usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("1.5"), + lower: percFP("0.05"), + upper: percFP("1.5"), }, protocolSwapSharePerc: 0n, }); await expect( - billBroker.computeUSDToPerpSwapFeePerc(percentageFP("1.5"), percentageFP("0.5")), + billBroker.computeUSDToPerpSwapFeePerc(percFP("1.5"), percFP("0.5")), ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); await expect( - billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("1.25"), - percentageFP("1.249"), - ), + billBroker.computeUSDToPerpSwapFeePerc(percFP("1.25"), percFP("1.249")), ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("0.25"), - percentageFP("1.2"), - ), - ).to.eq(percentageFP("0.05")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("0.25"), percFP("1.2")), + ).to.eq(percFP("0.05")); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("0.25"), - percentageFP("1.25"), - ), - ).to.eq(percentageFP("0.05")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("0.25"), percFP("1.25")), + ).to.eq(percFP("0.05")); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("1.2"), - percentageFP("1.3"), - ), - ).to.eq(percentageFP("0.1225")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.2"), percFP("1.3")), + ).to.eq(percFP("0.1225")); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("1.3"), - percentageFP("1.45"), - ), - ).to.eq(percentageFP("0.775")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("1.45")), + ).to.eq(percFP("0.775")); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("1.3"), - percentageFP("1.5"), - ), - ).to.eq(percentageFP("0.92")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("1.5")), + ).to.eq(percFP("0.92")); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("0.5"), - percentageFP("1.5"), - ), - ).to.eq(percentageFP("0.23125")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("0.5"), percFP("1.5")), + ).to.eq(percFP("0.23125")); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("1.3"), - percentageFP("1.501"), - ), - ).to.eq(percentageFP("1")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("1.501")), + ).to.eq(percFP("1")); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("1.3"), - percentageFP("2"), - ), - ).to.eq(percentageFP("1")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("1.3"), percFP("2")), + ).to.eq(percFP("1")); }); it("should compute the right fee perc when outside bounds", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( - [percentageFP("0.75"), percentageFP("1.25")], - [percentageFP("0"), percentageFP("10")], + [percFP("0.75"), percFP("1.25")], + [percFP("0"), percFP("10")], ); await billBroker.updateFees({ @@ -520,18 +497,15 @@ describe("BillBroker", function () { upper: 0n, }, usdToPerpSwapFeePercs: { - lower: percentageFP("1.01"), - upper: percentageFP("2"), + lower: percFP("1.01"), + upper: percFP("2"), }, protocolSwapSharePerc: 0n, }); expect( - await billBroker.computeUSDToPerpSwapFeePerc( - percentageFP("1"), - percentageFP("1.25"), - ), - ).to.eq(percentageFP("1")); + await billBroker.computeUSDToPerpSwapFeePerc(percFP("1"), percFP("1.25")), + ).to.eq(percFP("1")); }); }); @@ -539,16 +513,16 @@ describe("BillBroker", function () { it("should compute the right fee perc", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( - [percentageFP("0.75"), percentageFP("1.25")], - [percentageFP("0.5"), percentageFP("1.5")], + [percFP("0.75"), percFP("1.25")], + [percFP("0.5"), percFP("1.5")], ); await billBroker.updateFees({ mintFeePerc: 0n, burnFeePerc: 0n, perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, usdToPerpSwapFeePercs: { lower: 0n, @@ -558,66 +532,45 @@ describe("BillBroker", function () { }); await expect( - billBroker.computePerpToUSDSwapFeePerc(percentageFP("0.5"), percentageFP("1.5")), + billBroker.computePerpToUSDSwapFeePerc(percFP("0.5"), percFP("1.5")), ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); await expect( - billBroker.computePerpToUSDSwapFeePerc( - percentageFP("1.25"), - percentageFP("1.251"), - ), + billBroker.computePerpToUSDSwapFeePerc(percFP("1.25"), percFP("1.251")), ).to.be.revertedWithCustomError(billBroker, "UnexpectedARDelta"); expect( - await billBroker.computePerpToUSDSwapFeePerc( - percentageFP("2"), - percentageFP("0.8"), - ), - ).to.eq(percentageFP("0.1")); + await billBroker.computePerpToUSDSwapFeePerc(percFP("2"), percFP("0.8")), + ).to.eq(percFP("0.1")); expect( - await billBroker.computePerpToUSDSwapFeePerc( - percentageFP("1.45"), - percentageFP("0.8"), - ), - ).to.eq(percentageFP("0.1")); + await billBroker.computePerpToUSDSwapFeePerc(percFP("1.45"), percFP("0.8")), + ).to.eq(percFP("0.1")); expect( - await billBroker.computePerpToUSDSwapFeePerc( - percentageFP("0.8"), - percentageFP("0.7"), - ), - ).to.eq(percentageFP("0.12")); + await billBroker.computePerpToUSDSwapFeePerc(percFP("0.8"), percFP("0.7")), + ).to.eq(percFP("0.12")); expect( - await billBroker.computePerpToUSDSwapFeePerc( - percentageFP("0.8"), - percentageFP("0.5"), - ), - ).to.eq(percentageFP("0.266666666666666666")); + await billBroker.computePerpToUSDSwapFeePerc(percFP("0.8"), percFP("0.5")), + ).to.eq(percFP("0.266666666666666666")); expect( - await billBroker.computePerpToUSDSwapFeePerc( - percentageFP("1.5"), - percentageFP("0.5"), - ), - ).to.eq(percentageFP("0.15")); + await billBroker.computePerpToUSDSwapFeePerc(percFP("1.5"), percFP("0.5")), + ).to.eq(percFP("0.15")); expect( - await billBroker.computePerpToUSDSwapFeePerc( - percentageFP("1.0"), - percentageFP("0.49"), - ), - ).to.eq(percentageFP("1")); + await billBroker.computePerpToUSDSwapFeePerc(percFP("1.0"), percFP("0.49")), + ).to.eq(percFP("1")); }); it("should compute the right fee perc when outside bounds", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( - [percentageFP("0.75"), percentageFP("1.25")], - [percentageFP("0"), percentageFP("10")], + [percFP("0.75"), percFP("1.25")], + [percFP("0"), percFP("10")], ); await billBroker.updateFees({ mintFeePerc: 0n, burnFeePerc: 0n, perpToUSDSwapFeePercs: { - lower: percentageFP("1.01"), - upper: percentageFP("2"), + lower: percFP("1.01"), + upper: percFP("2"), }, usdToPerpSwapFeePercs: { lower: 0n, @@ -627,11 +580,8 @@ describe("BillBroker", function () { }); expect( - await billBroker.computePerpToUSDSwapFeePerc( - percentageFP("1.25"), - percentageFP("1.11"), - ), - ).to.eq(percentageFP("1")); + await billBroker.computePerpToUSDSwapFeePerc(percFP("1.25"), percFP("1.11")), + ).to.eq(percFP("1")); }); }); }); diff --git a/spot-vaults/test/BillBroker_deposit_redeem.ts b/spot-vaults/test/BillBroker_deposit_redeem.ts index 784f65a9..03049433 100644 --- a/spot-vaults/test/BillBroker_deposit_redeem.ts +++ b/spot-vaults/test/BillBroker_deposit_redeem.ts @@ -1,7 +1,7 @@ import { ethers, upgrades } from "hardhat"; import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; import { expect } from "chai"; -import { DMock, usdFP, perpFP, lpAmtFP, percentageFP, priceFP } from "./helpers"; +import { DMock, usdFP, perpFP, lpAmtFP, percFP, priceFP } from "./helpers"; describe("BillBroker", function () { async function setupContracts() { @@ -14,11 +14,12 @@ describe("BillBroker", function () { await usd.init("USD token", "usd", 6); const perp = await Token.deploy(); await perp.init("Perp token", "perp", 9); - const pricingStrategy = new DMock("SpotAppraiser"); + const pricingStrategy = new DMock("IPerpPricer"); await pricingStrategy.deploy(); await pricingStrategy.mockMethod("decimals()", [18]); - await pricingStrategy.mockMethod("perpPrice()", [priceFP("1.15"), true]); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [priceFP("1.15"), true]); await pricingStrategy.mockMethod("usdPrice()", [priceFP("1"), true]); + await pricingStrategy.mockMethod("perpDiscountFactor()", [percFP("1")]); const BillBroker = await ethers.getContractFactory("BillBroker"); const billBroker = await upgrades.deployProxy( @@ -145,7 +146,7 @@ describe("BillBroker", function () { perpFP("100"), ); await billBroker.updateFees({ - mintFeePerc: percentageFP("0.1"), + mintFeePerc: percFP("0.1"), burnFeePerc: 0n, perpToUSDSwapFeePercs: { lower: 0n, @@ -259,7 +260,7 @@ describe("BillBroker", function () { perpFP("200"), ); await billBroker.updateFees({ - mintFeePerc: percentageFP("0.1"), + mintFeePerc: percFP("0.1"), burnFeePerc: 0n, perpToUSDSwapFeePercs: { lower: 0n, @@ -347,7 +348,7 @@ describe("BillBroker", function () { perpFP("100"), ); await billBroker.updateFees({ - mintFeePerc: percentageFP("0.1"), + mintFeePerc: percFP("0.1"), burnFeePerc: 0n, perpToUSDSwapFeePercs: { lower: 0n, @@ -565,7 +566,7 @@ describe("BillBroker", function () { it("should withhold fees and mint lp tokens", async function () { const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); await billBroker.updateFees({ - mintFeePerc: percentageFP("0.1"), + mintFeePerc: percFP("0.1"), burnFeePerc: 0n, perpToUSDSwapFeePercs: { lower: 0n, @@ -645,15 +646,14 @@ describe("BillBroker", function () { it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.pause(); - await expect(billBroker.depositUSD(usdFP("115"), percentageFP("1"))).to.be - .reverted; + await expect(billBroker.depositUSD(usdFP("115"), percFP("1"))).to.be.reverted; }); }); describe("when usdAmtIn is zero", function () { it("should return zero", async function () { const { billBroker } = await loadFixture(setupContracts); - const r = await billBroker.depositUSD.staticCall(0n, percentageFP("1")); + const r = await billBroker.depositUSD.staticCall(0n, percFP("1")); expect(r).to.eq(0n); }); }); @@ -744,7 +744,7 @@ describe("BillBroker", function () { await usd.approve(billBroker.target, usdFP("10")); await expect( - billBroker.depositUSD(usdFP("10"), percentageFP("0.50")), + billBroker.depositUSD(usdFP("10"), percFP("0.50")), ).to.be.revertedWithCustomError(billBroker, "SlippageTooHigh"); }); }); @@ -763,7 +763,7 @@ describe("BillBroker", function () { await usd.approve(billBroker.target, usdFP("10")); await expect(() => - billBroker.depositUSD(usdFP("10"), percentageFP("1")), + billBroker.depositUSD(usdFP("10"), percFP("1")), ).to.changeTokenBalance(usd, deployer, usdFP("-10")); }); @@ -780,7 +780,7 @@ describe("BillBroker", function () { await usd.approve(billBroker.target, usdFP("10")); await expect(() => - billBroker.depositUSD(usdFP("10"), percentageFP("1")), + billBroker.depositUSD(usdFP("10"), percFP("1")), ).to.changeTokenBalance( billBroker, deployer, @@ -803,9 +803,9 @@ describe("BillBroker", function () { ); await usd.approve(billBroker.target, usdFP("10")); const r = await billBroker.reserveState.staticCall(); - await expect(billBroker.depositUSD(usdFP("10"), percentageFP("1"))) + await expect(billBroker.depositUSD(usdFP("10"), percFP("1"))) .to.emit(billBroker, "DepositUSD") - .withArgs(usdFP("10"), r); + .withArgs(usdFP("10"), r, percFP("1")); expect(await billBroker.totalSupply()).to.eq( lpAmtFP("324.130434782608695652173913"), ); @@ -823,7 +823,7 @@ describe("BillBroker", function () { ); await usd.approve(billBroker.target, usdFP("10")); - const r = await billBroker.depositUSD.staticCall(usdFP("10"), percentageFP("1")); + const r = await billBroker.depositUSD.staticCall(usdFP("10"), percFP("1")); expect(r).to.eq(lpAmtFP("9.130434782608695652173913")); }); }); @@ -832,7 +832,7 @@ describe("BillBroker", function () { it("should withhold fees and mint lp tokens", async function () { const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); await billBroker.updateFees({ - mintFeePerc: percentageFP("0.1"), + mintFeePerc: percFP("0.1"), burnFeePerc: 0n, perpToUSDSwapFeePercs: { lower: 0n, @@ -856,7 +856,7 @@ describe("BillBroker", function () { await usd.approve(billBroker.target, usdFP("10")); await expect(() => - billBroker.depositUSD(usdFP("10"), percentageFP("1")), + billBroker.depositUSD(usdFP("10"), percFP("1")), ).to.changeTokenBalance( billBroker, deployer, @@ -871,15 +871,14 @@ describe("BillBroker", function () { it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.pause(); - await expect(billBroker.depositPerp(perpFP("100"), percentageFP("1"))).to.be - .reverted; + await expect(billBroker.depositPerp(perpFP("100"), percFP("1"))).to.be.reverted; }); }); describe("when perpAmtIn is zero", function () { it("should return zero", async function () { const { billBroker } = await loadFixture(setupContracts); - const r = await billBroker.depositPerp.staticCall(0n, percentageFP("1")); + const r = await billBroker.depositPerp.staticCall(0n, percFP("1")); expect(r).to.eq(0n); }); }); @@ -959,7 +958,7 @@ describe("BillBroker", function () { await perp.approve(billBroker.target, perpFP("10")); await expect( - billBroker.depositPerp(perpFP("10"), percentageFP("1.85")), + billBroker.depositPerp(perpFP("10"), percFP("1.85")), ).to.be.revertedWithCustomError(billBroker, "SlippageTooHigh"); }); }); @@ -978,7 +977,7 @@ describe("BillBroker", function () { await perp.approve(billBroker.target, perpFP("10")); await expect(() => - billBroker.depositPerp(perpFP("10"), percentageFP("1")), + billBroker.depositPerp(perpFP("10"), percFP("1")), ).to.changeTokenBalance(perp, deployer, perpFP("-10")); }); @@ -995,7 +994,7 @@ describe("BillBroker", function () { await perp.approve(billBroker.target, perpFP("10")); await expect(() => - billBroker.depositPerp(perpFP("10"), percentageFP("1")), + billBroker.depositPerp(perpFP("10"), percFP("1")), ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("11")); expect(await billBroker.totalSupply()).to.eq(lpAmtFP("341")); }); @@ -1013,9 +1012,9 @@ describe("BillBroker", function () { await perp.approve(billBroker.target, perpFP("10")); const r = await billBroker.reserveState.staticCall(); - await expect(billBroker.depositPerp(perpFP("10"), percentageFP("1"))) + await expect(billBroker.depositPerp(perpFP("10"), percFP("1"))) .to.emit(billBroker, "DepositPerp") - .withArgs(perpFP("10"), r); + .withArgs(perpFP("10"), r, percFP("1")); expect(await billBroker.totalSupply()).to.eq(lpAmtFP("341")); }); @@ -1031,10 +1030,7 @@ describe("BillBroker", function () { ); await perp.approve(billBroker.target, perpFP("10")); - const r = await billBroker.depositPerp.staticCall( - perpFP("10"), - percentageFP("1"), - ); + const r = await billBroker.depositPerp.staticCall(perpFP("10"), percFP("1")); expect(r).to.eq(lpAmtFP("11")); }); }); @@ -1052,7 +1048,7 @@ describe("BillBroker", function () { ); await billBroker.updateFees({ - mintFeePerc: percentageFP("0.1"), + mintFeePerc: percFP("0.1"), burnFeePerc: 0n, perpToUSDSwapFeePercs: { lower: 0n, @@ -1066,7 +1062,7 @@ describe("BillBroker", function () { }); await perp.approve(billBroker.target, perpFP("10")); await expect(() => - billBroker.depositPerp(perpFP("10"), percentageFP("1")), + billBroker.depositPerp(perpFP("10"), percFP("1")), ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("9.9")); }); }); @@ -1129,7 +1125,7 @@ describe("BillBroker", function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); await billBroker.updateFees({ mintFeePerc: 0n, - burnFeePerc: percentageFP("0.1"), + burnFeePerc: percFP("0.1"), perpToUSDSwapFeePercs: { lower: 0n, upper: 0n, @@ -1170,15 +1166,7 @@ describe("BillBroker", function () { await billBroker.swapUSDForPerps(usdFP("115"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); - const s = await billBroker.reserveState.staticCall(); - expect( - await billBroker.assetRatio({ - usdBalance: s[0], - perpBalance: s[1], - usdPrice: s[2], - perpPrice: s[3], - }), - ).to.eq(ethers.MaxUint256); + expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); const r = await billBroker.computeRedemptionAmts.staticCall(lpAmtFP("100")); expect(r[0]).to.eq(usdFP("106.976744")); @@ -1202,15 +1190,7 @@ describe("BillBroker", function () { await billBroker.swapPerpsForUSD(perpFP("100"), 0n); expect(await usd.balanceOf(billBroker.target)).to.eq(0n); - const s = await billBroker.reserveState.staticCall(); - expect( - await billBroker.assetRatio({ - usdBalance: s[0], - perpBalance: s[1], - usdPrice: s[2], - perpPrice: s[3], - }), - ).to.eq(0); + expect(await assetRatio(billBroker)).to.eq(0); const r = await billBroker.computeRedemptionAmts.staticCall(lpAmtFP("100")); expect(r[0]).to.eq(0n); @@ -1426,15 +1406,7 @@ describe("BillBroker", function () { await billBroker.swapUSDForPerps(usdFP("115"), 0n); expect(await perp.balanceOf(billBroker.target)).to.eq(0n); - const s = await billBroker.reserveState.staticCall(); - expect( - await billBroker.assetRatio({ - usdBalance: s[0], - perpBalance: s[1], - usdPrice: s[2], - perpPrice: s[3], - }), - ).to.eq(ethers.MaxUint256); + expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); const perpBal = await perp.balanceOf(await deployer.getAddress()); await expect(() => billBroker.redeem(lpAmtFP("100"))).to.changeTokenBalance( @@ -1463,15 +1435,7 @@ describe("BillBroker", function () { await billBroker.swapPerpsForUSD(perpFP("100"), 0n); expect(await usd.balanceOf(billBroker.target)).to.eq(0n); - const s = await billBroker.reserveState.staticCall(); - expect( - await billBroker.assetRatio({ - usdBalance: s[0], - perpBalance: s[1], - usdPrice: s[2], - perpPrice: s[3], - }), - ).to.eq(0); + expect(await assetRatio(billBroker)).to.eq(0); const usdBal = await usd.balanceOf(await deployer.getAddress()); await expect(() => billBroker.redeem(lpAmtFP("100"))).to.changeTokenBalance( diff --git a/spot-vaults/test/BillBroker_swap.ts b/spot-vaults/test/BillBroker_swap.ts index d22bca2d..d1f7fefc 100644 --- a/spot-vaults/test/BillBroker_swap.ts +++ b/spot-vaults/test/BillBroker_swap.ts @@ -2,7 +2,7 @@ import { ethers, upgrades } from "hardhat"; import { Contract } from "ethers"; import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; import { expect } from "chai"; -import { DMock, usdFP, perpFP, priceFP, percentageFP } from "./helpers"; +import { DMock, usdFP, perpFP, priceFP, percFP } from "./helpers"; async function updateFees(billBroker: Contract, fees: any) { const currentFees = await billBroker.fees(); @@ -28,11 +28,12 @@ async function checkUSDToPerpSwapAmt( billBroker: Contract, usdAmtIn: BigInt, reserveState: any, + discountPerc: BigInt, amoutsOut: any, ) { const r = await billBroker[ - "computeUSDToPerpSwapAmt(uint256,(uint256,uint256,uint256,uint256))" - ](usdAmtIn, reserveState); + "computeUSDToPerpSwapAmt(uint256,(uint256,uint256,uint256,uint256),uint256)" + ](usdAmtIn, reserveState, discountPerc); expect(r[0]).to.eq(amoutsOut[0]); expect(r[1]).to.eq(amoutsOut[1]); expect(r[2]).to.eq(amoutsOut[2]); @@ -42,11 +43,12 @@ async function checkPerpTpUSDSwapAmt( billBroker: Contract, perpAmtIn: BigInt, reserveState: any, + discountPerc: BigInt, amoutsOut: any, ) { const r = await billBroker[ - "computePerpToUSDSwapAmt(uint256,(uint256,uint256,uint256,uint256))" - ](perpAmtIn, reserveState); + "computePerpToUSDSwapAmt(uint256,(uint256,uint256,uint256,uint256),uint256)" + ](perpAmtIn, reserveState, discountPerc); expect(r[0]).to.eq(amoutsOut[0]); expect(r[1]).to.eq(amoutsOut[1]); expect(r[2]).to.eq(amoutsOut[2]); @@ -77,11 +79,12 @@ describe("BillBroker", function () { await usd.init("USD token", "usd", 6); const perp = await Token.deploy(); await perp.init("Perp token", "perp", 9); - const pricingStrategy = new DMock("SpotAppraiser"); + const pricingStrategy = new DMock("IPerpPricer"); await pricingStrategy.deploy(); await pricingStrategy.mockMethod("decimals()", [18]); - await pricingStrategy.mockMethod("perpPrice()", [priceFP("1.15"), true]); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [priceFP("1.15"), true]); await pricingStrategy.mockMethod("usdPrice()", [priceFP("1"), true]); + await pricingStrategy.mockMethod("perpDiscountFactor()", [percFP("1")]); const BillBroker = await ethers.getContractFactory("BillBroker"); const billBroker = await upgrades.deployProxy( @@ -105,8 +108,8 @@ describe("BillBroker", function () { protocolSwapSharePerc: 0n, }); await billBroker.updateARBounds( - [percentageFP("0.9"), percentageFP("1.1")], - [percentageFP("0.75"), percentageFP("1.25")], + [percFP("0.9"), percFP("1.1")], + [percFP("0.75"), percFP("1.25")], ); await usd.mint(billBroker.target, usdFP("115000")); @@ -132,6 +135,7 @@ describe("BillBroker", function () { billBroker, usdFP("115"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], + percFP("1"), [perpFP("100"), 0n, 0n], ); }); @@ -142,6 +146,7 @@ describe("BillBroker", function () { billBroker, usdFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [perpFP("100"), 0n, 0n], ); }); @@ -152,6 +157,7 @@ describe("BillBroker", function () { billBroker, usdFP("11111"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [perpFP("11111"), 0n, 0n], ); }); @@ -162,6 +168,7 @@ describe("BillBroker", function () { billBroker, usdFP("11112"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [0n, 0n, 0n], ); }); @@ -172,6 +179,7 @@ describe("BillBroker", function () { billBroker, usdFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], + percFP("1"), [perpFP("111.111111"), 0n, 0n], ); }); @@ -181,14 +189,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("115"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], + percFP("1"), [perpFP("95"), perpFP("5"), 0n], ); }); @@ -197,14 +206,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [perpFP("95"), perpFP("5"), 0n], ); }); @@ -213,14 +223,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], + percFP("1"), [perpFP("101.460470381"), perpFP("9.650640619"), 0n], ); }); @@ -229,14 +240,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("10000"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [perpFP("8491.666666667"), perpFP("1508.333333333"), 0n], ); }); @@ -245,14 +257,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await checkUSDToPerpSwapAmt( billBroker, usdFP("20000"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [0n, 0n, 0n], ); }); @@ -263,15 +276,16 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await checkUSDToPerpSwapAmt( billBroker, usdFP("115"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], + percFP("1"), [perpFP("95"), perpFP("4.5"), perpFP("0.5")], ); }); @@ -288,8 +302,8 @@ describe("BillBroker", function () { await expect( billBroker[ - "computeUSDToPerpSwapAmt(uint256,(uint256,uint256,uint256,uint256))" - ](usdFP("100"), [usdFP("100000"), 0n, priceFP("1"), priceFP("1")]), + "computeUSDToPerpSwapAmt(uint256,(uint256,uint256,uint256,uint256),uint256)" + ](usdFP("100"), [usdFP("100000"), 0n, priceFP("1"), priceFP("1")], percFP("1")), ).to.be.reverted; }); }); @@ -307,10 +321,39 @@ describe("BillBroker", function () { billBroker, usdFP("100"), [0n, perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [perpFP("100"), 0n, 0n], ); }); }); + + describe("when discount factor is set", function () { + describe("when discount factor is less than 1", function () { + it("should not apply discount", async function () { + const { billBroker } = await loadFixture(setupContracts); + await checkUSDToPerpSwapAmt( + billBroker, + usdFP("100"), + [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], + percFP("0.9"), + [perpFP("111.111111"), 0n, 0n], + ); + }); + }); + + describe("when discount factor is more than 1", function () { + it("should apply premium", async function () { + const { billBroker } = await loadFixture(setupContracts); + await checkUSDToPerpSwapAmt( + billBroker, + usdFP("100"), + [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], + percFP("1.1"), + [perpFP("101.010101000"), 0n, 0n], + ); + }); + }); + }); }); describe("#computePerpToUSDSwapAmt", function () { @@ -320,6 +363,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], + percFP("1"), [usdFP("115"), 0n, 0n], ); }); @@ -330,6 +374,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [usdFP("100"), 0n, 0n], ); }); @@ -340,6 +385,7 @@ describe("BillBroker", function () { billBroker, perpFP("14285"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [usdFP("14285"), 0n, 0n], ); }); @@ -350,6 +396,7 @@ describe("BillBroker", function () { billBroker, perpFP("14286"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [0n, 0n, 0n], ); }); @@ -360,6 +407,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], + percFP("1"), [usdFP("90"), 0n, 0n], ); }); @@ -369,14 +417,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await checkPerpTpUSDSwapAmt( billBroker, perpFP("100"), [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], + percFP("1"), [usdFP("103.5"), usdFP("11.5"), 0n], ); }); @@ -385,14 +434,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await checkPerpTpUSDSwapAmt( billBroker, perpFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [usdFP("90"), usdFP("10"), 0n], ); }); @@ -401,14 +451,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await checkPerpTpUSDSwapAmt( billBroker, perpFP("14285"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [usdFP("11142.474991"), usdFP("3142.525009"), 0n], ); }); @@ -417,14 +468,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await checkPerpTpUSDSwapAmt( billBroker, perpFP("14286"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [0n, 0n, 0n], ); }); @@ -433,14 +485,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await checkPerpTpUSDSwapAmt( billBroker, perpFP("100"), [usdFP("100000"), perpFP("100000"), priceFP("1"), priceFP("0.9")], + percFP("1"), [usdFP("81"), usdFP("9"), 0n], ); }); @@ -451,15 +504,16 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await checkPerpTpUSDSwapAmt( billBroker, perpFP("100"), [usdFP("110000"), perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), [usdFP("90"), usdFP("9"), usdFP("1")], ); }); @@ -477,6 +531,7 @@ describe("BillBroker", function () { billBroker, perpFP("100"), [usdFP("100000"), 0n, priceFP("1"), priceFP("1")], + percFP("1"), [usdFP("100"), 0n, 0n], ); }); @@ -492,11 +547,43 @@ describe("BillBroker", function () { await expect( billBroker[ - "computePerpToUSDSwapAmt(uint256,(uint256,uint256,uint256,uint256))" - ](perpFP("100"), [0n, perpFP("100000"), priceFP("1"), priceFP("1")]), + "computePerpToUSDSwapAmt(uint256,(uint256,uint256,uint256,uint256),uint256)" + ]( + perpFP("100"), + [0n, perpFP("100000"), priceFP("1"), priceFP("1")], + percFP("1"), + ), ).to.be.reverted; }); }); + + describe("when discount factor is set", function () { + describe("when discount factor is less than 1", function () { + it("should apply discount", async function () { + const { billBroker } = await loadFixture(setupContracts); + await checkPerpTpUSDSwapAmt( + billBroker, + perpFP("100"), + [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], + percFP("0.9"), + [usdFP("103.5"), 0n, 0n], + ); + }); + }); + + describe("when discount factor is more than 1", function () { + it("should NOT apply discount", async function () { + const { billBroker } = await loadFixture(setupContracts); + await checkPerpTpUSDSwapAmt( + billBroker, + perpFP("100"), + [usdFP("115000"), perpFP("100000"), priceFP("1"), priceFP("1.15")], + percFP("1.1"), + [usdFP("115"), 0n, 0n], + ); + }); + }); + }); }); describe("#swapUSDForPerps", function () { @@ -530,7 +617,7 @@ describe("BillBroker", function () { describe("when oracle price is unreliable", function () { it("should revert", async function () { const { billBroker, pricingStrategy } = await loadFixture(setupContracts); - await pricingStrategy.mockMethod("perpPrice()", [0n, false]); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [0n, false]); await expect( billBroker.swapUSDForPerps(usdFP("115"), perpFP("100")), ).to.be.revertedWithCustomError(billBroker, "UnreliablePrice"); @@ -559,9 +646,9 @@ describe("BillBroker", function () { }); it("should increase the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapUSDForPerps(usdFP("115"), perpFP("100")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1.002002002002002002")); + expect(await assetRatio(billBroker)).to.eq(percFP("1.002002002002002002")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); @@ -576,7 +663,7 @@ describe("BillBroker", function () { const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapUSDForPerps(usdFP("115"), perpFP("100"))) .to.emit(billBroker, "SwapUSDForPerps") - .withArgs(usdFP("115"), r); + .withArgs(usdFP("115"), r, percFP("1")); }); }); @@ -585,8 +672,8 @@ describe("BillBroker", function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await expect(() => @@ -597,8 +684,8 @@ describe("BillBroker", function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await expect(() => @@ -609,20 +696,20 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapUSDForPerps(usdFP("115"), perpFP("95")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1.001951854261548471")); + expect(await assetRatio(billBroker)).to.eq(percFP("1.001951854261548471")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await billBroker.swapUSDForPerps(usdFP("115"), perpFP("95")); @@ -634,14 +721,14 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapUSDForPerps(usdFP("115"), perpFP("95"))) .to.emit(billBroker, "SwapUSDForPerps") - .withArgs(usdFP("115"), r); + .withArgs(usdFP("115"), r, percFP("1")); }); }); @@ -650,10 +737,10 @@ describe("BillBroker", function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await expect(() => billBroker.swapUSDForPerps(usdFP("115"), perpFP("95")), @@ -665,10 +752,10 @@ describe("BillBroker", function () { ); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await billBroker .connect(deployer) @@ -681,10 +768,10 @@ describe("BillBroker", function () { const { billBroker, perp, feeCollector } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await billBroker.transferOwnership(await feeCollector.getAddress()); await expect(() => @@ -695,23 +782,23 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapUSDForPerps(usdFP("115"), perpFP("95")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1.001956868809713276")); + expect(await assetRatio(billBroker)).to.eq(percFP("1.001956868809713276")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await billBroker.swapUSDForPerps(usdFP("115"), perpFP("95")); const r = await reserveState(billBroker); @@ -722,15 +809,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapUSDForPerps(usdFP("115"), perpFP("95"))) .to.emit(billBroker, "SwapUSDForPerps") - .withArgs(usdFP("115"), r); + .withArgs(usdFP("115"), r, percFP("1")); }); }); @@ -739,8 +826,8 @@ describe("BillBroker", function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await expect(() => @@ -751,8 +838,8 @@ describe("BillBroker", function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await expect(() => @@ -764,20 +851,20 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapUSDForPerps(usdFP("3795"), perpFP("3130")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1.066432664016930779")); + expect(await assetRatio(billBroker)).to.eq(percFP("1.066432664016930779")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await billBroker.swapUSDForPerps(usdFP("3795"), perpFP("3130")); @@ -789,14 +876,14 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapUSDForPerps(usdFP("3795"), perpFP("3130"))) .to.emit(billBroker, "SwapUSDForPerps") - .withArgs(usdFP("3795"), r); + .withArgs(usdFP("3795"), r, percFP("1")); }); }); @@ -804,13 +891,13 @@ describe("BillBroker", function () { it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( - [percentageFP("0.8"), percentageFP("1")], - [percentageFP("0.75"), percentageFP("1.05")], + [percFP("0.8"), percFP("1")], + [percFP("0.75"), percFP("1.05")], ); await updateFees(billBroker, { usdToPerpSwapFeePercs: { - lower: percentageFP("0.05"), - upper: percentageFP("0.5"), + lower: percFP("0.05"), + upper: percFP("0.5"), }, }); await expect( @@ -880,7 +967,7 @@ describe("BillBroker", function () { describe("when oracle price is unreliable", function () { it("should revert", async function () { const { billBroker, pricingStrategy } = await loadFixture(setupContracts); - await pricingStrategy.mockMethod("perpPrice()", [0n, false]); + await pricingStrategy.mockMethod("perpFmvUsdPrice()", [0n, false]); await expect( billBroker.swapPerpsForUSD(perpFP("115"), usdFP("100")), ).to.be.revertedWithCustomError(billBroker, "UnreliablePrice"); @@ -910,9 +997,9 @@ describe("BillBroker", function () { it("should decrease the reserve ar", async function () { const { billBroker } = await loadFixture(setupContracts); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapPerpsForUSD(perpFP("100"), usdFP("115")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("0.998001998001998001")); + expect(await assetRatio(billBroker)).to.eq(percFP("0.998001998001998001")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); @@ -926,7 +1013,7 @@ describe("BillBroker", function () { const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapPerpsForUSD(perpFP("100"), usdFP("115"))) .to.emit(billBroker, "SwapPerpsForUSD") - .withArgs(perpFP("100"), r); + .withArgs(perpFP("100"), r, percFP("1")); }); }); @@ -935,8 +1022,8 @@ describe("BillBroker", function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await expect(() => @@ -947,8 +1034,8 @@ describe("BillBroker", function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await expect(() => @@ -959,20 +1046,20 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("0.998101898101898101")); + expect(await assetRatio(billBroker)).to.eq(percFP("0.998101898101898101")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103")); @@ -984,14 +1071,14 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103"))) .to.emit(billBroker, "SwapPerpsForUSD") - .withArgs(perpFP("100"), r); + .withArgs(perpFP("100"), r, percFP("1")); }); }); @@ -1000,10 +1087,10 @@ describe("BillBroker", function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await expect(() => billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103")), @@ -1015,10 +1102,10 @@ describe("BillBroker", function () { ); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await billBroker.transferOwnership(await feeCollector.getAddress()); await expect(() => @@ -1029,10 +1116,10 @@ describe("BillBroker", function () { const { billBroker, usd, feeCollector } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await billBroker.transferOwnership(await feeCollector.getAddress()); await expect(() => @@ -1043,23 +1130,23 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("0.998091908091908091")); + expect(await assetRatio(billBroker)).to.eq(percFP("0.998091908091908091")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); await billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103")); const r = await reserveState(billBroker); @@ -1070,15 +1157,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103"))) .to.emit(billBroker, "SwapPerpsForUSD") - .withArgs(perpFP("100"), r); + .withArgs(perpFP("100"), r, percFP("1")); }); }); @@ -1087,8 +1174,8 @@ describe("BillBroker", function () { const { billBroker, deployer, perp } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await expect(() => @@ -1099,8 +1186,8 @@ describe("BillBroker", function () { const { billBroker, deployer, usd } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await expect(() => @@ -1111,20 +1198,20 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); - expect(await assetRatio(billBroker)).to.eq(percentageFP("1")); + expect(await assetRatio(billBroker)).to.eq(percFP("1")); await billBroker.swapPerpsForUSD(perpFP("3600"), usdFP("3700")); - expect(await assetRatio(billBroker)).to.eq(percentageFP("0.933976833976833976")); + expect(await assetRatio(billBroker)).to.eq(percFP("0.933976833976833976")); }); it("should update the reserve", async function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await billBroker.swapPerpsForUSD(perpFP("3600"), usdFP("3700")); @@ -1138,13 +1225,13 @@ describe("BillBroker", function () { it("should revert", async function () { const { billBroker } = await loadFixture(setupContracts); await billBroker.updateARBounds( - [percentageFP("1"), percentageFP("1.1")], - [percentageFP("0.95"), percentageFP("1.25")], + [percFP("1"), percFP("1.1")], + [percFP("0.95"), percFP("1.25")], ); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, }); await expect( @@ -1155,15 +1242,15 @@ describe("BillBroker", function () { const { billBroker } = await loadFixture(setupContracts); await updateFees(billBroker, { perpToUSDSwapFeePercs: { - lower: percentageFP("0.1"), - upper: percentageFP("0.5"), + lower: percFP("0.1"), + upper: percFP("0.5"), }, - protocolSwapSharePerc: percentageFP("0.1"), + protocolSwapSharePerc: percFP("0.1"), }); const r = await billBroker.reserveState.staticCall(); await expect(billBroker.swapPerpsForUSD(perpFP("5000"), usdFP("4000"))) .to.emit(billBroker, "SwapPerpsForUSD") - .withArgs(perpFP("5000"), r); + .withArgs(perpFP("5000"), r, percFP("1")); }); }); diff --git a/spot-vaults/test/SpotAppraiser.ts b/spot-vaults/test/SpotAppraiser.ts deleted file mode 100644 index 80f1e85b..00000000 --- a/spot-vaults/test/SpotAppraiser.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { ethers } from "hardhat"; -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; -import { expect } from "chai"; -import { oracleAnsFP, perpFP, percentageFP, priceFP, DMock } from "./helpers"; - -const nowTS = () => parseInt(Date.now() / 1000); - -describe("SpotAppraiser", function () { - async function setupContracts() { - const accounts = await ethers.getSigners(); - const deployer = accounts[0]; - - const amplTargetOracle = new DMock("MedianOracle"); - await amplTargetOracle.deploy(); - await amplTargetOracle.mockMethod("getData()", [priceFP("1.15"), true]); - await amplTargetOracle.mockMethod("DECIMALS()", [18]); - - const policy = new DMock("UFragmentsPolicy"); - await policy.deploy(); - await policy.mockMethod("cpiOracle()", [amplTargetOracle.target]); - - const ampl = new DMock("UFragments"); - await ampl.deploy(); - await ampl.mockMethod("decimals()", [9]); - await ampl.mockMethod("monetaryPolicy()", [policy.target]); - - const bond = new DMock("BondController"); - await bond.deploy(); - await bond.mockMethod("isMature()", [false]); - await bond.mockMethod("trancheCount()", [2]); - await bond.mockMethod("totalDebt()", [perpFP("500000")]); - await ampl.mockCall("balanceOf(address)", [bond.target], [perpFP("500000")]); - - const tranche = new DMock("Tranche"); - await tranche.deploy(); - await tranche.mockMethod("bond()", [bond.target]); - await tranche.mockMethod("totalSupply()", [perpFP("100000")]); - await bond.mockCall("tranches(uint256)", [0], [tranche.target, 200]); - await bond.mockCall("tranches(uint256)", [1], [ethers.ZeroAddress, 800]); - - const spot = new DMock("PerpetualTranche"); - await spot.deploy(); - await spot.mockMethod("underlying()", [ampl.target]); - await spot.mockMethod("getTVL()", [perpFP("1000000")]); - await spot.mockMethod("totalSupply()", [perpFP("1000000")]); - await spot.mockMethod("deviationRatio()", [oracleAnsFP("1.5")]); - await spot.mockMethod("getReserveCount()", [2]); - await spot.mockCall("getReserveAt(uint256)", [0], [ampl.target]); - await spot.mockCall("getReserveAt(uint256)", [1], [tranche.target]); - await ampl.mockCall("balanceOf(address)", [spot.target], [perpFP("1000")]); - - const usdPriceOrcle = new DMock("IChainlinkOracle"); - await usdPriceOrcle.deploy(); - await usdPriceOrcle.mockMethod("decimals()", [8]); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("1"), - 0, - nowTS(), - 0, - ]); - - const SpotAppraiser = await ethers.getContractFactory("SpotAppraiser"); - const strategy = await SpotAppraiser.deploy( - spot.target, - usdPriceOrcle.target, - amplTargetOracle.target, - ); - return { - deployer, - ampl, - spot, - usdPriceOrcle, - amplTargetOracle, - bond, - tranche, - strategy, - }; - } - - describe("init", function () { - it("should initial params", async function () { - const { deployer, strategy, ampl, spot, usdPriceOrcle, amplTargetOracle } = - await loadFixture(setupContracts); - expect(await strategy.AMPL()).to.eq(ampl.target); - expect(await strategy.SPOT()).to.eq(spot.target); - expect(await strategy.USD_ORACLE()).to.eq(usdPriceOrcle.target); - expect(await strategy.AMPL_CPI_ORACLE()).to.eq(amplTargetOracle.target); - expect(await strategy.AMPL_DUST_AMT()).to.eq(perpFP("25000")); - expect(await strategy.minSPOTDR()).to.eq(percentageFP("0.8")); - expect(await strategy.minSeniorCDR()).to.eq(percentageFP("1.1")); - expect(await strategy.owner()).to.eq(await deployer.getAddress()); - expect(await strategy.decimals()).to.eq(18); - }); - }); - - describe("#updateMinSPOTDR", function () { - describe("when triggered by non-owner", function () { - it("should revert", async function () { - const { strategy } = await loadFixture(setupContracts); - await strategy.renounceOwnership(); - await expect(strategy.updateMinSPOTDR(percentageFP("1.15"))).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - }); - - describe("when triggered by owner", function () { - it("should update value", async function () { - const { strategy } = await loadFixture(setupContracts); - await strategy.updateMinSPOTDR(percentageFP("1.15")); - expect(await strategy.minSPOTDR()).to.eq(percentageFP("1.15")); - }); - }); - }); - - describe("#updateMinPerpCollateralCDR", function () { - describe("when triggered by non-owner", function () { - it("should revert", async function () { - const { strategy } = await loadFixture(setupContracts); - await strategy.renounceOwnership(); - await expect( - strategy.updateMinPerpCollateralCDR(percentageFP("1.25")), - ).to.be.revertedWith("Ownable: caller is not the owner"); - }); - }); - - describe("when cdr is invalid", function () { - it("should revert", async function () { - const { strategy } = await loadFixture(setupContracts); - await expect( - strategy.updateMinPerpCollateralCDR(percentageFP("0.9")), - ).to.be.revertedWithCustomError(strategy, "InvalidSeniorCDRBound"); - }); - }); - - describe("when triggered by owner", function () { - it("should update value", async function () { - const { strategy } = await loadFixture(setupContracts); - await strategy.updateMinPerpCollateralCDR(percentageFP("1.25")); - expect(await strategy.minSeniorCDR()).to.eq(percentageFP("1.25")); - }); - }); - }); - - describe("#usdPrice", function () { - describe("when data is stale", function () { - it("should return invalid", async function () { - const { strategy, usdPriceOrcle } = await loadFixture(setupContracts); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("1"), - 0, - nowTS() - 50 * 3600, - 0, - ]); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(false); - }); - }); - - describe("when oracle price is below thresh", function () { - it("should return invalid", async function () { - const { strategy, usdPriceOrcle } = await loadFixture(setupContracts); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("0.98"), - 0, - nowTS(), - 0, - ]); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(false); - }); - }); - - describe("when oracle price is above thresh", function () { - it("should return invalid", async function () { - const { strategy, usdPriceOrcle } = await loadFixture(setupContracts); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("1.02"), - 0, - nowTS(), - 0, - ]); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(false); - }); - }); - - it("should return price", async function () { - const { strategy } = await loadFixture(setupContracts); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(true); - }); - }); - - describe("#perpPrice", function () { - describe("when AMPL target data is invalid", function () { - it("should return invalid", async function () { - const { strategy, amplTargetOracle } = await loadFixture(setupContracts); - await amplTargetOracle.mockMethod("getData()", [priceFP("1.2"), false]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.2")); - expect(p[1]).to.eq(false); - }); - }); - - describe("when balancer DR is too low", function () { - it("should return invalid", async function () { - const { strategy, spot } = await loadFixture(setupContracts); - await spot.mockMethod("deviationRatio()", [oracleAnsFP("0.79999")]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.15")); - expect(p[1]).to.eq(false); - }); - }); - - describe("when spot senior cdr is too low", function () { - it("should return invalid", async function () { - const { strategy, ampl, bond } = await loadFixture(setupContracts); - await ampl.mockCall("balanceOf(address)", [bond.target], [perpFP("109999")]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.15")); - expect(p[1]).to.eq(false); - }); - it("should return invalid", async function () { - const { strategy, bond } = await loadFixture(setupContracts); - await bond.mockMethod("isMature()", [true]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.15")); - expect(p[1]).to.eq(false); - }); - }); - - describe("when spot has mature AMPL", function () { - it("should return invalid", async function () { - const { strategy, ampl, spot } = await loadFixture(setupContracts); - await ampl.mockCall("balanceOf(address)", [spot.target], [perpFP("25001")]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.15")); - expect(p[1]).to.eq(false); - }); - }); - - it("should return price", async function () { - const { strategy } = await loadFixture(setupContracts); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.15")); - expect(p[1]).to.eq(true); - }); - - describe("when debasement/enrichment multiplier is not 1", function () { - it("should return price", async function () { - const { strategy, spot } = await loadFixture(setupContracts); - await spot.mockMethod("getTVL()", [perpFP("1500000")]); - await spot.mockMethod("totalSupply()", [perpFP("1000000")]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.725")); - expect(p[1]).to.eq(true); - }); - it("should return price", async function () { - const { strategy, spot } = await loadFixture(setupContracts); - await spot.mockMethod("getTVL()", [perpFP("900000")]); - await spot.mockMethod("totalSupply()", [perpFP("1000000")]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.035")); - expect(p[1]).to.eq(true); - }); - }); - }); -}); diff --git a/spot-vaults/test/SpotCDRPricer.ts b/spot-vaults/test/SpotCDRPricer.ts deleted file mode 100644 index b3095ee7..00000000 --- a/spot-vaults/test/SpotCDRPricer.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { ethers } from "hardhat"; -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; -import { expect } from "chai"; -import { oracleAnsFP, perpFP, priceFP, DMock } from "./helpers"; - -const nowTS = () => parseInt(Date.now() / 1000); - -describe("SpotCDRPricer", function () { - async function setupContracts() { - const accounts = await ethers.getSigners(); - const deployer = accounts[0]; - - const amplTargetOracle = new DMock("MedianOracle"); - await amplTargetOracle.deploy(); - await amplTargetOracle.mockMethod("getData()", [priceFP("1.15"), true]); - await amplTargetOracle.mockMethod("DECIMALS()", [18]); - - const policy = new DMock("UFragmentsPolicy"); - await policy.deploy(); - await policy.mockMethod("cpiOracle()", [amplTargetOracle.target]); - - const ampl = new DMock("UFragments"); - await ampl.deploy(); - await ampl.mockMethod("decimals()", [9]); - await ampl.mockMethod("monetaryPolicy()", [policy.target]); - - const spot = new DMock("PerpetualTranche"); - await spot.deploy(); - await spot.mockMethod("underlying()", [ampl.target]); - await spot.mockMethod("getTVL()", [perpFP("1000000")]); - await spot.mockMethod("totalSupply()", [perpFP("1000000")]); - - const usdPriceOrcle = new DMock("IChainlinkOracle"); - await usdPriceOrcle.deploy(); - await usdPriceOrcle.mockMethod("decimals()", [8]); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("1"), - 0, - nowTS(), - 0, - ]); - - const SpotCDRPricer = await ethers.getContractFactory("SpotCDRPricer"); - const strategy = await SpotCDRPricer.deploy( - spot.target, - usdPriceOrcle.target, - amplTargetOracle.target, - ); - return { - deployer, - ampl, - spot, - usdPriceOrcle, - amplTargetOracle, - strategy, - }; - } - - describe("init", function () { - it("should initial params", async function () { - const { strategy, ampl, spot, usdPriceOrcle, amplTargetOracle } = await loadFixture( - setupContracts, - ); - expect(await strategy.AMPL()).to.eq(ampl.target); - expect(await strategy.SPOT()).to.eq(spot.target); - expect(await strategy.USD_ORACLE()).to.eq(usdPriceOrcle.target); - expect(await strategy.AMPL_CPI_ORACLE()).to.eq(amplTargetOracle.target); - expect(await strategy.decimals()).to.eq(18); - }); - }); - - describe("#usdPrice", function () { - describe("when data is stale", function () { - it("should return invalid", async function () { - const { strategy, usdPriceOrcle } = await loadFixture(setupContracts); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("1"), - 0, - nowTS() - 50 * 3600, - 0, - ]); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(false); - }); - }); - - describe("when oracle price is below thresh", function () { - it("should return invalid", async function () { - const { strategy, usdPriceOrcle } = await loadFixture(setupContracts); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("0.98"), - 0, - nowTS(), - 0, - ]); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(false); - }); - }); - - describe("when oracle price is above thresh", function () { - it("should return invalid", async function () { - const { strategy, usdPriceOrcle } = await loadFixture(setupContracts); - await usdPriceOrcle.mockMethod("latestRoundData()", [ - 0, - oracleAnsFP("1.02"), - 0, - nowTS(), - 0, - ]); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(false); - }); - }); - - it("should return price", async function () { - const { strategy } = await loadFixture(setupContracts); - const p = await strategy.usdPrice(); - expect(p[0]).to.eq(priceFP("1")); - expect(p[1]).to.eq(true); - }); - }); - - describe("#perpPrice", function () { - describe("when AMPL target data is invalid", function () { - it("should return invalid", async function () { - const { strategy, amplTargetOracle } = await loadFixture(setupContracts); - await amplTargetOracle.mockMethod("getData()", [priceFP("1.2"), false]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.2")); - expect(p[1]).to.eq(false); - }); - }); - - it("should return price", async function () { - const { strategy } = await loadFixture(setupContracts); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.15")); - expect(p[1]).to.eq(true); - }); - - describe("when debasement/enrichment multiplier is not 1", function () { - it("should return price", async function () { - const { strategy, spot } = await loadFixture(setupContracts); - await spot.mockMethod("getTVL()", [perpFP("1500000")]); - await spot.mockMethod("totalSupply()", [perpFP("1000000")]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.725")); - expect(p[1]).to.eq(true); - }); - it("should return price", async function () { - const { strategy, spot } = await loadFixture(setupContracts); - await spot.mockMethod("getTVL()", [perpFP("900000")]); - await spot.mockMethod("totalSupply()", [perpFP("1000000")]); - const p = await strategy.perpPrice.staticCall(); - expect(p[0]).to.eq(priceFP("1.035")); - expect(p[1]).to.eq(true); - }); - }); - }); -}); diff --git a/spot-vaults/test/SpotPricer.ts b/spot-vaults/test/SpotPricer.ts new file mode 100644 index 00000000..cf718461 --- /dev/null +++ b/spot-vaults/test/SpotPricer.ts @@ -0,0 +1,584 @@ +import { ethers } from "hardhat"; +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; +import { expect } from "chai"; +import { + amplOracleFP, + usdOracleFP, + ethOracleFP, + perpFP, + percFP, + priceFP, + wamplFP, + amplFP, + drFP, + DMock, +} from "./helpers"; + +const nowTS = () => parseInt(Date.now() / 1000); + +describe("SpotPricer", function () { + async function setupContracts() { + const accounts = await ethers.getSigners(); + const deployer = accounts[0]; + + const amplTargetOracle = new DMock("MedianOracle"); + await amplTargetOracle.deploy(); + await amplTargetOracle.mockMethod("getData()", [amplOracleFP("1.15"), true]); + await amplTargetOracle.mockMethod("DECIMALS()", [18]); + + const usdcPriceOrcle = new DMock("IChainlinkOracle"); + await usdcPriceOrcle.deploy(); + await usdcPriceOrcle.mockMethod("decimals()", [8]); + await usdcPriceOrcle.mockMethod("latestRoundData()", [ + 0, + usdOracleFP("1"), + 0, + nowTS(), + 0, + ]); + + const ethPriceOrcle = new DMock("IChainlinkOracle"); + await ethPriceOrcle.deploy(); + await ethPriceOrcle.mockMethod("decimals()", [18]); + await ethPriceOrcle.mockMethod("latestRoundData()", [ + 0, + ethOracleFP("2357.76"), + 0, + nowTS(), + 0, + ]); + + const usdc = new DMock("contracts/_interfaces/external/IERC20.sol:IERC20"); + await usdc.deploy(); + + const ampl = new DMock("UFragments"); + await ampl.deploy(); + + const wampl = new DMock("IWAMPL"); + await wampl.deploy(); + await wampl.mockCall( + "wrapperToUnderlying(uint256)", + [wamplFP("1")], + [amplFP("7.692284616")], + ); + + const bond = new DMock( + "contracts/_interfaces/external/IBondController.sol:IBondController", + ); + await bond.deploy(); + await bond.mockMethod("collateralBalance()", [perpFP("2000")]); + + const tranche = new DMock("Tranche"); + await tranche.deploy(); + await tranche.mockMethod("bond()", [bond.target]); + + const feePolicy = new DMock("IPerpFeePolicy"); + await feePolicy.deploy(); + await feePolicy.mockMethod("decimals()", [8]); + await feePolicy.mockMethod("deviationRatio()", [drFP("1")]); + await feePolicy.mockMethod("computePerpRolloverFeePerc(uint256)", [0]); + + const spot = new DMock("PerpetualTranche"); + await spot.deploy(); + await spot.mockMethod("feePolicy()", [feePolicy.target]); + await spot.mockMethod("underlying()", [ampl.target]); + await spot.mockMethod("getTVL()", [perpFP("1000")]); + await spot.mockMethod("totalSupply()", [perpFP("1000")]); + await spot.mockMethod("getReserveCount()", [2]); + await spot.mockCall("getReserveAt(uint256)", [0], [ampl.target]); + await spot.mockCall("getReserveTokenBalance(address)", [ampl.target], ["0"]); + await spot.mockCall("getReserveAt(uint256)", [1], [tranche.target]); + await spot.mockCall( + "getReserveTokenBalance(address)", + [tranche.target], + [perpFP("1000")], + ); + await spot.mockCall( + "getReserveTokenValue(address)", + [tranche.target], + [perpFP("1000")], + ); + + const wamplPool = new DMock("IUniswapV3Pool"); + await wamplPool.deploy(); + await wamplPool.mockMethod("token1()", [wampl.target]); + await wamplPool.mockMethod("observe(uint32[])", [ + ["376921685400", "377121673968"], + ["5338479444003340079488911551", "5338479669430834262798687400"], + ]); + + const spotPool = new DMock("IUniswapV3Pool"); + await spotPool.deploy(); + await spotPool.mockMethod("token0()", [usdc.target]); + await spotPool.mockMethod("token1()", [spot.target]); + await spotPool.mockMethod("observe(uint32[])", [ + ["3780978019944", "3781218388344"], + [ + "15033345577239143106349258268248842184594399522", + "15033345577239143129748458314415242759127803748", + ], + ]); + + const SpotPricer = await ethers.getContractFactory("SpotPricer"); + const strategy = await SpotPricer.deploy( + wamplPool.target, + spotPool.target, + ethPriceOrcle.target, + usdcPriceOrcle.target, + amplTargetOracle.target, + ); + return { + deployer, + amplTargetOracle, + ethPriceOrcle, + usdcPriceOrcle, + usdc, + ampl, + wampl, + spot, + feePolicy, + bond, + tranche, + wamplPool, + spotPool, + strategy, + }; + } + + describe("init", function () { + it("should initial params", async function () { + const { + deployer, + amplTargetOracle, + ethPriceOrcle, + usdcPriceOrcle, + usdc, + ampl, + wampl, + spot, + wamplPool, + spotPool, + strategy, + } = await loadFixture(setupContracts); + expect(await strategy.WETH_WAMPL_POOL()).to.eq(wamplPool.target); + expect(await strategy.USDC_SPOT_POOL()).to.eq(spotPool.target); + + expect(await strategy.ETH_ORACLE()).to.eq(ethPriceOrcle.target); + expect(await strategy.USDC_ORACLE()).to.eq(usdcPriceOrcle.target); + expect(await strategy.CPI_ORACLE()).to.eq(amplTargetOracle.target); + + expect(await strategy.WAMPL()).to.eq(wampl.target); + expect(await strategy.USDC()).to.eq(usdc.target); + expect(await strategy.AMPL()).to.eq(ampl.target); + expect(await strategy.SPOT()).to.eq(spot.target); + + const p = await strategy.discountFactorParams(); + expect(p.seniorCDRLimit).to.eq(percFP("1.5")); + expect(p.perpDRLimit).to.eq(percFP("0.8")); + expect(p.minDiscountFactor).to.eq(percFP("0.333333333333333333")); + expect(p.enrichmentScalar).to.eq(percFP("1")); + expect(p.debasementScalar).to.eq(percFP("2")); + + expect(await strategy.owner()).to.eq(await deployer.getAddress()); + expect(await strategy.decimals()).to.eq(18); + }); + }); + + describe("#updatePerpDiscountFactorParams", function () { + describe("when triggered by non-owner", function () { + it("should revert", async function () { + const { strategy } = await loadFixture(setupContracts); + await strategy.transferOwnership(ethers.ZeroAddress); + await expect( + strategy.updatePerpDiscountFactorParams([ + percFP("1"), + percFP("1"), + percFP("1"), + percFP("2"), + percFP("1"), + ]), + ).to.be.revertedWith("UnauthorizedCall"); + }); + }); + + describe("when triggered by owner", function () { + it("should update value", async function () { + const { strategy } = await loadFixture(setupContracts); + await strategy.updatePerpDiscountFactorParams([ + percFP("1"), + percFP("1"), + percFP("1"), + percFP("2"), + percFP("1"), + ]); + const p = await strategy.discountFactorParams(); + expect(p.seniorCDRLimit).to.eq(percFP("1")); + expect(p.perpDRLimit).to.eq(percFP("1")); + expect(p.minDiscountFactor).to.eq(percFP("1")); + expect(p.enrichmentScalar).to.eq(percFP("2")); + expect(p.debasementScalar).to.eq(percFP("1")); + }); + }); + }); + + describe("#transferOwnership", function () { + describe("when triggered by non-owner", function () { + it("should revert", async function () { + const { strategy } = await loadFixture(setupContracts); + await strategy.transferOwnership(ethers.ZeroAddress); + await expect(strategy.transferOwnership(ethers.ZeroAddress)).to.be.revertedWith( + "UnauthorizedCall", + ); + }); + }); + + describe("when triggered by owner", function () { + it("should update value", async function () { + const { strategy } = await loadFixture(setupContracts); + await strategy.transferOwnership(ethers.ZeroAddress); + expect(await strategy.owner()).to.eq(ethers.ZeroAddress); + }); + }); + }); + + describe("#usdPrice", function () { + describe("when data is stale", function () { + it("should return invalid", async function () { + const { strategy, usdcPriceOrcle } = await loadFixture(setupContracts); + await usdcPriceOrcle.mockMethod("latestRoundData()", [ + 0, + usdOracleFP("1"), + 0, + nowTS() - 50 * 3600, + 0, + ]); + const p = await strategy.usdPrice(); + expect(p[0]).to.eq(amplOracleFP("1")); + expect(p[1]).to.eq(false); + }); + }); + + describe("when oracle price is below thresh", function () { + it("should return invalid", async function () { + const { strategy, usdcPriceOrcle } = await loadFixture(setupContracts); + await usdcPriceOrcle.mockMethod("latestRoundData()", [ + 0, + usdOracleFP("0.98"), + 0, + nowTS(), + 0, + ]); + const p = await strategy.usdPrice(); + expect(p[0]).to.eq(amplOracleFP("1")); + expect(p[1]).to.eq(false); + }); + }); + + describe("when oracle price is above thresh", function () { + it("should return invalid", async function () { + const { strategy, usdcPriceOrcle } = await loadFixture(setupContracts); + await usdcPriceOrcle.mockMethod("latestRoundData()", [ + 0, + usdOracleFP("1.02"), + 0, + nowTS(), + 0, + ]); + const p = await strategy.usdPrice(); + expect(p[0]).to.eq(amplOracleFP("1")); + expect(p[1]).to.eq(false); + }); + }); + + it("should return price", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.usdPrice(); + expect(p[0]).to.eq(amplOracleFP("1")); + expect(p[1]).to.eq(true); + }); + }); + + describe("#perpFmvUsdPrice", function () { + describe("when AMPL target data is invalid", function () { + it("should return invalid", async function () { + const { strategy, amplTargetOracle } = await loadFixture(setupContracts); + await amplTargetOracle.mockMethod("getData()", [amplOracleFP("1.2"), false]); + const p = await strategy.perpFmvUsdPrice.staticCall(); + expect(p[0]).to.eq(amplOracleFP("1.2")); + expect(p[1]).to.eq(false); + }); + }); + + it("should return price", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.perpFmvUsdPrice.staticCall(); + expect(p[0]).to.eq(amplOracleFP("1.15")); + expect(p[1]).to.eq(true); + }); + + describe("when debasement/enrichment multiplier is not 1", function () { + it("should return price", async function () { + const { strategy, spot } = await loadFixture(setupContracts); + await spot.mockMethod("getTVL()", [perpFP("1500000")]); + await spot.mockMethod("totalSupply()", [perpFP("1000000")]); + const p = await strategy.perpFmvUsdPrice.staticCall(); + expect(p[0]).to.eq(amplOracleFP("1.725")); + expect(p[1]).to.eq(true); + }); + it("should return price", async function () { + const { strategy, spot } = await loadFixture(setupContracts); + await spot.mockMethod("getTVL()", [perpFP("900000")]); + await spot.mockMethod("totalSupply()", [perpFP("1000000")]); + const p = await strategy.perpFmvUsdPrice.staticCall(); + expect(p[0]).to.eq(amplOracleFP("1.035")); + expect(p[1]).to.eq(true); + }); + }); + }); + + describe("#perpUsdPrice", function () { + describe("when usdc price is invalid", function () { + it("should return invalid", async function () { + const { strategy, usdcPriceOrcle } = await loadFixture(setupContracts); + await usdcPriceOrcle.mockMethod("latestRoundData()", [ + 0, + usdOracleFP("1"), + 0, + nowTS() - 50 * 3600, + 0, + ]); + const p = await strategy.perpUsdPrice.staticCall(); + expect(p[0]).to.eq(priceFP("1.260097503535148000")); + expect(p[1]).to.eq(false); + }); + }); + + it("should compute spot usd price", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.perpUsdPrice.staticCall(); + expect(p[0]).to.eq(priceFP("1.260097503535148000")); + expect(p[1]).to.eq(true); + }); + }); + + describe("#underlyingUsdPrice", function () { + describe("when eth price is invalid", function () { + it("should return invalid", async function () { + const { strategy, ethPriceOrcle } = await loadFixture(setupContracts); + await ethPriceOrcle.mockMethod("latestRoundData()", [ + 0, + ethOracleFP("3000"), + 0, + nowTS() - 50 * 3600, + 0, + ]); + const p = await strategy.underlyingUsdPrice.staticCall(); + expect(p[0]).to.eq(priceFP("1.508668510241881174")); + expect(p[1]).to.eq(false); + }); + }); + + it("should compute ampl price", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.underlyingUsdPrice.staticCall(); + expect(p[0]).to.eq(priceFP("1.185692755569299252")); + expect(p[1]).to.eq(true); + }); + }); + + describe("intermediate prices", function () { + it("should compute eth price", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.ethUsdPrice.staticCall(); + expect(p[0]).to.eq(priceFP("2357.76")); + expect(p[1]).to.eq(true); + }); + + it("should compute wampl price", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.wamplUsdPrice.staticCall(); + expect(p[0]).to.eq(priceFP("9.120686142968368965")); + expect(p[1]).to.eq(true); + }); + + it("should compute spot price deviation", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.spotPriceDeviation.staticCall(); + expect(p[0]).to.eq(percFP("1.095736959595780869")); + expect(p[1]).to.eq(true); + }); + + it("should compute spot price deviation", async function () { + const { strategy, amplTargetOracle } = await loadFixture(setupContracts); + await amplTargetOracle.mockMethod("getData()", [amplOracleFP("2"), true]); + const p = await strategy.spotPriceDeviation.staticCall(); + expect(p[0]).to.eq(percFP("0.630048751767574000")); + expect(p[1]).to.eq(true); + }); + + it("should compute spot price deviation", async function () { + const { strategy, amplTargetOracle } = await loadFixture(setupContracts); + await amplTargetOracle.mockMethod("getData()", [amplOracleFP("0"), false]); + const p = await strategy.spotPriceDeviation.staticCall(); + expect(p[0]).to.eq(percFP("100")); + expect(p[1]).to.eq(false); + }); + + it("should compute ampl price deviation", async function () { + const { strategy } = await loadFixture(setupContracts); + const p = await strategy.amplPriceDeviation.staticCall(); + expect(p[0]).to.eq(percFP("1.031037178755912393")); + expect(p[1]).to.eq(true); + }); + + it("should compute spot price deviation", async function () { + const { strategy, amplTargetOracle } = await loadFixture(setupContracts); + await amplTargetOracle.mockMethod("getData()", [amplOracleFP("1.5"), true]); + const p = await strategy.amplPriceDeviation.staticCall(); + expect(p[0]).to.eq(percFP("0.790461837046199501")); + expect(p[1]).to.eq(true); + }); + + it("should compute spot price deviation", async function () { + const { strategy, amplTargetOracle } = await loadFixture(setupContracts); + await amplTargetOracle.mockMethod("getData()", [amplOracleFP("0"), false]); + const p = await strategy.amplPriceDeviation.staticCall(); + expect(p[0]).to.eq(percFP("100")); + expect(p[1]).to.eq(false); + }); + }); + + describe("#perpDiscountFactor", function () { + it("should return the discount factor", async function () { + const { strategy } = await loadFixture(setupContracts); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq(percFP("1")); + }); + + describe("when perp has raw underlying", function () { + it("should return the discount factor", async function () { + const { strategy, spot, ampl } = await loadFixture(setupContracts); + await spot.mockCall( + "getReserveTokenBalance(address)", + [ampl.target], + [perpFP("350")], + ); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.975308641975308641"), + ); + + await spot.mockCall( + "getReserveTokenBalance(address)", + [ampl.target], + [perpFP("400")], + ); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.904761904761904761"), + ); + + await spot.mockCall( + "getReserveTokenBalance(address)", + [ampl.target], + [perpFP("700")], + ); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.568627450980392155"), + ); + + await spot.mockCall( + "getReserveTokenBalance(address)", + [ampl.target], + [perpFP("1000")], + ); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.333333333333333333"), + ); + + await spot.mockCall( + "getReserveTokenBalance(address)", + [ampl.target], + [perpFP("10000")], + ); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.333333333333333333"), + ); + }); + }); + + describe("when tranche cdr is below limit", function () { + it("should return the discount factor", async function () { + const { strategy, bond } = await loadFixture(setupContracts); + + await bond.mockMethod("collateralBalance()", [perpFP("1500")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq(percFP("1")); + + await bond.mockMethod("collateralBalance()", [perpFP("1200")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.599999999999999999"), + ); + + await bond.mockMethod("collateralBalance()", [perpFP("1000")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.333333333333333333"), + ); + + await bond.mockMethod("collateralBalance()", [perpFP("1")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.333333333333333333"), + ); + }); + }); + + describe("when tranche dr is below limit", function () { + it("should return the discount factor", async function () { + const { strategy, feePolicy } = await loadFixture(setupContracts); + + await feePolicy.mockMethod("deviationRatio()", [drFP("2")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq(percFP("1")); + + await feePolicy.mockMethod("deviationRatio()", [drFP("1")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq(percFP("1")); + + await feePolicy.mockMethod("deviationRatio()", [drFP("0.8")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq(percFP("1")); + + await feePolicy.mockMethod("deviationRatio()", [drFP("0.75")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.958333333333333333"), + ); + + await feePolicy.mockMethod("deviationRatio()", [drFP("0.5")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.749999999999999999"), + ); + + await feePolicy.mockMethod("deviationRatio()", [drFP("0.25")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.541666666666666666"), + ); + + await feePolicy.mockMethod("deviationRatio()", [drFP("0")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq( + percFP("0.333333333333333333"), + ); + }); + }); + + describe("when enrichment is active", function () { + it("should return the discount factor", async function () { + const { strategy, feePolicy } = await loadFixture(setupContracts); + await feePolicy.mockMethod("computePerpRolloverFeePerc(uint256)", [drFP("0.05")]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq(percFP("1.05")); + }); + }); + + describe("when debasement is active", function () { + it("should return the discount factor", async function () { + const { strategy, feePolicy } = await loadFixture(setupContracts); + await feePolicy.mockMethod("computePerpRolloverFeePerc(uint256)", [ + drFP("-0.05"), + ]); + expect(await strategy.perpDiscountFactor.staticCall()).to.eq(percFP("0.9")); + }); + }); + }); +}); diff --git a/spot-vaults/test/UsdcSpotManager.ts b/spot-vaults/test/UsdcSpotManager.ts index e4e437a2..59fa6d0d 100644 --- a/spot-vaults/test/UsdcSpotManager.ts +++ b/spot-vaults/test/UsdcSpotManager.ts @@ -17,22 +17,22 @@ describe("UsdcSpotManager", function () { // Deploy mock contracts const mockVault = new DMock("IAlphaProVault"); await mockVault.deploy(); - await mockVault.mockMethod("fullLower()", [-800000]); - await mockVault.mockMethod("fullUpper()", [800000]); - await mockVault.mockMethod("baseLower()", [45000]); - await mockVault.mockMethod("baseUpper()", [55000]); - await mockVault.mockMethod("getTwap()", [67200]); - await mockVault.mockMethod("limitThreshold()", [800000]); const mockPool = new DMock("IUniswapV3Pool"); await mockPool.deploy(); + await mockPool.mockCall( + "positions(bytes32)", + [univ3PositionKey(mockVault.target, 20000, 40000)], + [50000, 0, 0, 0, 0], + ); + await mockVault.mockMethod("limitLower()", [20000]); + await mockVault.mockMethod("limitUpper()", [40000]); await mockVault.mockMethod("pool()", [mockPool.target]); - const mockAppraiser = new DMock("ISpotPricingStrategy"); - await mockAppraiser.deploy(); - await mockAppraiser.mockMethod("decimals()", [18]); - await mockAppraiser.mockMethod("perpPrice()", [priceFP("1.2"), true]); - await mockAppraiser.mockMethod("usdPrice()", [priceFP("1"), true]); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [18]); + await mockOracle.mockMethod("spotPriceDeviation()", [priceFP("1.2"), true]); const mockUsdc = new DMock("IERC20Upgradeable"); await mockUsdc.deploy(); @@ -44,13 +44,13 @@ describe("UsdcSpotManager", function () { // Deploy Manager contract const Manager = await ethers.getContractFactory("UsdcSpotManager"); - const manager = await Manager.deploy(mockVault.target, mockAppraiser.target); + const manager = await Manager.deploy(mockVault.target, mockOracle.target); return { owner, addr1, mockVault, - mockAppraiser, + mockOracle, mockUsdc, mockSpot, mockPool, @@ -58,6 +58,40 @@ describe("UsdcSpotManager", function () { }; } + async function stubRebalance(mockVault) { + await mockVault.clearMockMethod("setPeriod(uint32)"); + await mockVault.clearMockMethod("period()"); + await mockVault.mockMethod("rebalance()", []); + } + + async function stubForceRebalance(mockVault) { + await mockVault.mockMethod("period()", [86400]); + await mockVault.mockCall("setPeriod(uint32)", [0], []); + await mockVault.mockCall("setPeriod(uint32)", [86400], []); + await mockVault.mockMethod("rebalance()", []); + } + + async function stubOverweightSpot(mockVault) { + await mockVault.mockMethod("getTwap()", [30001]); + } + + async function stubOverweightUsdc(mockVault) { + await mockVault.mockMethod("getTwap()", [29999]); + } + + async function stubUnchangedLimitRange(mockVault) { + await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); + } + + async function stubRemovedLimitRange(mockVault) { + await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); + await mockVault.mockCall( + "emergencyBurn(int24,int24,uint128)", + [20000, 40000, 50000], + [], + ); + } + describe("Initialization", function () { it("should set the correct owner", async function () { const { manager, owner } = await loadFixture(setupContracts); @@ -70,15 +104,13 @@ describe("UsdcSpotManager", function () { }); it("should set the appraiser address", async function () { - const { manager, mockAppraiser } = await loadFixture(setupContracts); - expect(await manager.pricingStrategy()).to.eq(mockAppraiser.target); + const { manager, mockOracle } = await loadFixture(setupContracts); + expect(await manager.oracle()).to.eq(mockOracle.target); }); it("should set the token refs", async function () { - const { manager, mockUsdc, mockSpot, mockPool } = await loadFixture(setupContracts); + const { manager, mockPool } = await loadFixture(setupContracts); expect(await manager.POOL()).to.eq(mockPool.target); - expect(await manager.USDC()).to.eq(mockUsdc.target); - expect(await manager.SPOT()).to.eq(mockSpot.target); }); it("should return the decimals", async function () { @@ -87,33 +119,34 @@ describe("UsdcSpotManager", function () { }); }); - describe("#transferOwnership", function () { + describe("#updateOracle", function () { it("should fail to when called by non-owner", async function () { const { manager, addr1 } = await loadFixture(setupContracts); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [18]); await expect( - manager.connect(addr1).transferOwnership(addr1.address), - ).to.be.revertedWith("Unauthorized caller"); + manager.connect(addr1).updateOracle(mockOracle.target), + ).to.be.revertedWith("Ownable: caller is not the owner"); }); - it("should succeed when called by owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await manager.transferOwnership(addr1.address); - expect(await manager.owner()).to.eq(await addr1.getAddress()); - }); - }); - - describe("#updatePricingStrategy", function () { - it("should fail to when called by non-owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await expect( - manager.connect(addr1).updatePricingStrategy(addr1.address), - ).to.be.revertedWith("Unauthorized caller"); + it("should fail when decimals dont match", async function () { + const { manager } = await loadFixture(setupContracts); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [9]); + await expect(manager.updateOracle(mockOracle.target)).to.be.revertedWith( + "UnexpectedDecimals", + ); }); it("should succeed when called by owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await manager.updatePricingStrategy(addr1.address); - expect(await manager.pricingStrategy()).to.eq(await addr1.getAddress()); + const { manager } = await loadFixture(setupContracts); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [18]); + await manager.updateOracle(mockOracle.target); + expect(await manager.oracle()).to.eq(mockOracle.target); }); }); @@ -122,7 +155,7 @@ describe("UsdcSpotManager", function () { const { manager, addr1 } = await loadFixture(setupContracts); await expect( manager.connect(addr1).setLiquidityRanges(7200, 330000, 1200), - ).to.be.revertedWith("Unauthorized caller"); + ).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should succeed when called by owner", async function () { @@ -143,7 +176,7 @@ describe("UsdcSpotManager", function () { .execOnVault( mockVault.refFactory.interface.encodeFunctionData("acceptManager"), ), - ).to.be.revertedWith("Unauthorized caller"); + ).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should succeed when called by owner", async function () { @@ -162,7 +195,7 @@ describe("UsdcSpotManager", function () { manager.execOnVault( mockVault.refFactory.interface.encodeFunctionData("acceptManager"), ), - ).to.be.revertedWith("Vault call failed"); + ).to.be.revertedWith("VaultExecutionFailed"); await mockVault.mockCall("acceptManager()", [], []); await manager.execOnVault( mockVault.refFactory.interface.encodeFunctionData("acceptManager"), @@ -170,275 +203,153 @@ describe("UsdcSpotManager", function () { }); }); - describe("#computeDeviationFactor", function () { - describe("when spot price is invalid", function () { - it("should return invalid", async function () { - const { manager, mockAppraiser } = await loadFixture(setupContracts); - await mockAppraiser.mockMethod("perpPrice()", [priceFP("1.2"), false]); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.0057863765655975")); - expect(r[1]).to.eq(false); + describe("isOverweightSpot", function () { + describe("when spot sell", function () { + it("should return true", async function () { + const { manager, mockVault } = await loadFixture(setupContracts); + await stubOverweightSpot(mockVault); + expect(await manager.isOverweightSpot()).to.eq(true); }); }); - describe("when usd price is invalid", function () { - it("should return invalid", async function () { - const { manager, mockAppraiser } = await loadFixture(setupContracts); - await mockAppraiser.mockMethod("usdPrice()", [priceFP("0.8"), false]); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.0057863765655975")); - expect(r[1]).to.eq(false); + describe("when spot buy", function () { + it("should return false", async function () { + const { manager, mockVault } = await loadFixture(setupContracts); + await stubOverweightUsdc(mockVault); + expect(await manager.isOverweightSpot()).to.eq(false); }); }); + }); - it("should return deviation factor", async function () { - const { manager } = await loadFixture(setupContracts); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.0057863765655975")); - expect(r[1]).to.eq(true); - }); - - it("should return deviation factor", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - await mockVault.mockMethod("getTwap()", [65800]); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.1569216182711425")); - expect(r[1]).to.eq(true); - }); - - it("should return deviation factor", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - await mockVault.mockMethod("getTwap()", [67800]); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("0.947216779268338333")); - expect(r[1]).to.eq(true); - }); - - it("should return deviation factor", async function () { - const { manager, mockAppraiser } = await loadFixture(setupContracts); - await mockAppraiser.mockMethod("perpPrice()", [priceFP("1.5"), true]); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("0.804629101252478")); - expect(r[1]).to.eq(true); - }); - - it("should return deviation factor", async function () { - const { manager, mockAppraiser } = await loadFixture(setupContracts); - await mockAppraiser.mockMethod("perpPrice()", [priceFP("1"), true]); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.206943651878717")); - expect(r[1]).to.eq(true); + describe("shouldRemoveLimitRange", function () { + describe("is overweight spot", function () { + it("should return bool", async function () { + const { manager, mockVault } = await loadFixture(setupContracts); + await stubOverweightSpot(mockVault); + expect(await manager.shouldRemoveLimitRange(percFP("1.01"))).to.eq(false); + expect(await manager.shouldRemoveLimitRange(percFP("1"))).to.eq(false); + expect(await manager.shouldRemoveLimitRange(percFP("0.99"))).to.eq(true); + }); }); - it("should return deviation factor when perp price is invalid", async function () { - const { manager, mockAppraiser } = await loadFixture(setupContracts); - await mockAppraiser.mockMethod("perpPrice()", [priceFP("0"), true]); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("100")); - expect(r[1]).to.eq(true); + describe("is overweight usdc", function () { + it("should return bool", async function () { + const { manager, mockVault } = await loadFixture(setupContracts); + await stubOverweightUsdc(mockVault); + expect(await manager.shouldRemoveLimitRange(percFP("1.01"))).to.eq(true); + expect(await manager.shouldRemoveLimitRange(percFP("1"))).to.eq(false); + expect(await manager.shouldRemoveLimitRange(percFP("0.99"))).to.eq(false); + }); }); }); - describe("isOverweightSpot", function () { - describe("when spot sell", function () { + describe("shouldForceRebalance", function () { + describe("when deviation crosses 1", function () { it("should return true", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - await mockVault.mockMethod("getTwap()", [30001]); - await mockVault.mockMethod("limitLower()", [20000]); - await mockVault.mockMethod("limitUpper()", [40000]); - expect(await manager.isOverweightSpot()).to.eq(true); + const { manager } = await loadFixture(setupContracts); + expect(await manager.shouldForceRebalance(percFP("0.9"), percFP("1.1"))).to.eq( + true, + ); + expect(await manager.shouldForceRebalance(percFP("1.5"), percFP("0.99"))).to.eq( + true, + ); + expect(await manager.shouldForceRebalance(percFP("1"), percFP("1.1"))).to.eq( + true, + ); + expect(await manager.shouldForceRebalance(percFP("1"), percFP("0.99"))).to.eq( + true, + ); }); }); - describe("when spot buy", function () { + describe("when deviation does not cross 1", function () { it("should return false", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - await mockVault.mockMethod("getTwap()", [29999]); - await mockVault.mockMethod("limitLower()", [20000]); - await mockVault.mockMethod("limitUpper()", [40000]); - expect(await manager.isOverweightSpot()).to.eq(false); + const { manager } = await loadFixture(setupContracts); + expect(await manager.shouldForceRebalance(percFP("0.9"), percFP("0.99"))).to.eq( + false, + ); + expect(await manager.shouldForceRebalance(percFP("1.5"), percFP("1.1"))).to.eq( + false, + ); + expect(await manager.shouldForceRebalance(percFP("0.9"), percFP("1"))).to.eq( + false, + ); + expect(await manager.shouldForceRebalance(percFP("1.5"), percFP("1"))).to.eq( + false, + ); }); }); }); describe("#rebalance", function () { - describe("when deviation is < 1 and goes > 1", function () { - describe("when overweight spot", function () { - it("should keep limit range", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [66200]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightSpot()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.111560295732100833")); - }); - }); + it("should rebalance, update limit range and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightUsdc(mockVault); + await stubRebalance(mockVault); + await stubUnchangedLimitRange(mockVault); + await mockOracle.mockMethod("spotPriceDeviation()", [priceFP("0.99"), true]); + + expect(await manager.isOverweightSpot()).to.eq(false); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("0.99")); + }); - describe("when overweight usdc", function () { - it("should remove limit range", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [66200]); - await mockVault.mockMethod("limitLower()", [73000]); - await mockVault.mockMethod("limitUpper()", [75000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 73000, 75000)], - [50000, 0, 0, 0, 0], - ); - - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [73000, 75000, 50000], - [], - ); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightSpot()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.111560295732100833")); - }); - }); + it("should rebalance, update limit range and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightSpot(mockVault); + await stubRebalance(mockVault); + await stubRemovedLimitRange(mockVault); + await mockOracle.mockMethod("spotPriceDeviation()", [priceFP("0.99"), true]); + + expect(await manager.isOverweightSpot()).to.eq(true); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("0.99")); }); - describe("when deviation is > 1 and goes < 1", function () { - describe("when overweight spot", function () { - it("should remove limit range", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [66200]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await manager.rebalance(); - - await mockVault.mockMethod("getTwap()", [67800]); - await mockVault.mockMethod("limitLower()", [60000]); - await mockVault.mockMethod("limitUpper()", [65000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 60000, 65000)], - [50000, 0, 0, 0, 0], - ); - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [60000, 65000, 50000], - [], - ); - - expect(await manager.prevDeviation()).to.eq(percFP("1.111560295732100833")); - expect(await manager.isOverweightSpot()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.947216779268338333")); - }); - }); + it("should rebalance, update limit range and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); - describe("when overweight usdc", function () { - it("should keep limit range", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [66200]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await manager.rebalance(); - - await mockVault.mockMethod("getTwap()", [67800]); - await mockVault.mockMethod("limitLower()", [75000]); - await mockVault.mockMethod("limitUpper()", [80000]); - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - - expect(await manager.prevDeviation()).to.eq(percFP("1.111560295732100833")); - expect(await manager.isOverweightSpot()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.947216779268338333")); - }); - }); + await stubOverweightUsdc(mockVault); + await stubForceRebalance(mockVault); + await stubRemovedLimitRange(mockVault); + await mockOracle.mockMethod("spotPriceDeviation()", [priceFP("1.2"), true]); + + expect(await manager.isOverweightSpot()).to.eq(false); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("1.2")); }); - describe("when deviation remains below 1", function () { - describe("when overweight spot", function () { - it("should not force rebalance", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [67800]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("rebalance()", []); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 40000, 45000)], - [50000, 0, 0, 0, 0], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [40000, 45000, 50000], - [], - ); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightSpot()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.947216779268338333")); - }); - }); + it("should rebalance, update limit range and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightSpot(mockVault); + await stubForceRebalance(mockVault); + await stubUnchangedLimitRange(mockVault); + await mockOracle.mockMethod("spotPriceDeviation()", [priceFP("1.2"), true]); + + expect(await manager.isOverweightSpot()).to.eq(true); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("1.2")); }); - describe("when deviation remains above 1", function () { - describe("when overweight usdc", function () { - it("should not force rebalance", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [66200]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await manager.rebalance(); - - await mockVault.clearMockCall("setPeriod(uint32)", [0]); - await mockVault.clearMockCall("setPeriod(uint32)", [86400]); - await mockVault.clearMockCall("period()", []); - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - - await mockVault.mockMethod("getTwap()", [66800]); - await mockVault.mockMethod("limitLower()", [75000]); - await mockVault.mockMethod("limitUpper()", [80000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 75000, 80000)], - [50000, 0, 0, 0, 0], - ); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - - expect(await manager.prevDeviation()).to.eq(percFP("1.111560295732100833")); - expect(await manager.isOverweightSpot()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.0468312037404625")); - }); - }); + it("should rebalance, remove limit range and not change prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightSpot(mockVault); + await stubRebalance(mockVault); + await stubRemovedLimitRange(mockVault); + await mockOracle.mockMethod("spotPriceDeviation()", [priceFP("1.2"), false]); + + expect(await manager.isOverweightSpot()).to.eq(true); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("0")); }); }); }); diff --git a/spot-vaults/test/WethWamplManager.ts b/spot-vaults/test/WethWamplManager.ts index 2506d5c6..3eb6acda 100644 --- a/spot-vaults/test/WethWamplManager.ts +++ b/spot-vaults/test/WethWamplManager.ts @@ -4,12 +4,7 @@ import { expect } from "chai"; import { DMock, sciParseFloat, univ3PositionKey } from "./helpers"; export const percFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); -export const amplFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 9); -export const wamplFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); -export const ethOracleFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 8); -export const amplOracleFP = (a: string): BigInt => - ethers.parseUnits(sciParseFloat(a), 18); -const nowTS = () => parseInt(Date.now() / 1000); +export const priceFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); describe("WethWamplManager", function () { async function setupContracts() { @@ -20,12 +15,6 @@ describe("WethWamplManager", function () { // Deploy mock contracts const mockVault = new DMock("IAlphaProVault"); await mockVault.deploy(); - await mockVault.mockMethod("fullLower()", [-800000]); - await mockVault.mockMethod("fullUpper()", [800000]); - await mockVault.mockMethod("baseLower()", [45000]); - await mockVault.mockMethod("baseUpper()", [55000]); - await mockVault.mockMethod("getTwap()", [49875]); - await mockVault.mockMethod("limitThreshold()", [800000]); const mockPool = new DMock("IUniswapV3Pool"); await mockPool.deploy(); @@ -36,26 +25,26 @@ describe("WethWamplManager", function () { ); await mockPool.mockCall( "positions(bytes32)", - [univ3PositionKey(mockVault.target, 45000, 55000)], - [20000, 0, 0, 0, 0], + [univ3PositionKey(mockVault.target, -100000, 100000)], + [100000, 0, 0, 0, 0], ); + await mockPool.mockCall( + "positions(bytes32)", + [univ3PositionKey(mockVault.target, 20000, 40000)], + [50000, 0, 0, 0, 0], + ); + await mockVault.mockMethod("baseLower()", [-100000]); + await mockVault.mockMethod("baseUpper()", [100000]); + await mockVault.mockMethod("fullLower()", [-800000]); + await mockVault.mockMethod("fullUpper()", [800000]); + await mockVault.mockMethod("limitLower()", [20000]); + await mockVault.mockMethod("limitUpper()", [40000]); await mockVault.mockMethod("pool()", [mockPool.target]); - const mockCPIOracle = new DMock("IAmpleforthOracle"); - await mockCPIOracle.deploy(); - await mockCPIOracle.mockMethod("DECIMALS()", [18]); - await mockCPIOracle.mockMethod("getData()", [amplOracleFP("1.19"), true]); - - const mockETHOracle = new DMock("IChainlinkOracle"); - await mockETHOracle.deploy(); - await mockETHOracle.mockMethod("decimals()", [8]); - await mockETHOracle.mockMethod("latestRoundData()", [ - 0, - ethOracleFP("3300"), - 0, - nowTS(), - 0, - ]); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [18]); + await mockOracle.mockMethod("amplPriceDeviation()", [priceFP("1.2"), true]); const mockWeth = new DMock("IERC20Upgradeable"); await mockWeth.deploy(); @@ -63,27 +52,17 @@ describe("WethWamplManager", function () { const mockWampl = new DMock("IWAMPL"); await mockWampl.deploy(); - await mockWampl.mockCall( - "wrapperToUnderlying(uint256)", - [wamplFP("1")], - [amplFP("18")], - ); await mockVault.mockMethod("token1()", [mockWampl.target]); // Deploy Manager contract const Manager = await ethers.getContractFactory("WethWamplManager"); - const manager = await Manager.deploy( - mockVault.target, - mockCPIOracle.target, - mockETHOracle.target, - ); + const manager = await Manager.deploy(mockVault.target, mockOracle.target); return { owner, addr1, mockVault, - mockCPIOracle, - mockETHOracle, + mockOracle, mockWeth, mockWampl, mockPool, @@ -91,6 +70,56 @@ describe("WethWamplManager", function () { }; } + async function stubRebalance(mockVault) { + await mockVault.clearMockMethod("setPeriod(uint32)"); + await mockVault.clearMockMethod("period()"); + await mockVault.mockMethod("rebalance()", []); + } + + async function stubForceRebalance(mockVault) { + await mockVault.mockMethod("period()", [86400]); + await mockVault.mockCall("setPeriod(uint32)", [0], []); + await mockVault.mockCall("setPeriod(uint32)", [86400], []); + await mockVault.mockMethod("rebalance()", []); + } + + async function stubOverweightWampl(mockVault) { + await mockVault.mockMethod("getTwap()", [30001]); + } + async function stubOverweightWeth(mockVault) { + await mockVault.mockMethod("getTwap()", [29999]); + await mockVault.mockMethod("limitLower()", [20000]); + await mockVault.mockMethod("limitUpper()", [40000]); + } + + async function stubTrimLiquidity(mockVault, burntLiq) { + await mockVault.mockCall( + "emergencyBurn(int24,int24,uint128)", + [-800000, 800000, burntLiq], + [], + ); + await mockVault.mockCall( + "emergencyBurn(int24,int24,uint128)", + [-100000, 100000, burntLiq], + [], + ); + } + + async function stubUnchangedLimitRange(mockVault) { + await mockVault.clearMockCall( + "emergencyBurn(int24,int24,uint128)", + [20000, 40000, 50000], + ); + } + + async function stubRemovedLimitRange(mockVault) { + await mockVault.mockCall( + "emergencyBurn(int24,int24,uint128)", + [20000, 40000, 50000], + [], + ); + } + describe("Initialization", function () { it("should set the correct owner", async function () { const { manager, owner } = await loadFixture(setupContracts); @@ -102,23 +131,14 @@ describe("WethWamplManager", function () { expect(await manager.VAULT()).to.eq(mockVault.target); }); - it("should set the correct CPI oracle address", async function () { - const { manager, mockCPIOracle } = await loadFixture(setupContracts); - expect(await manager.cpiOracle()).to.eq(mockCPIOracle.target); - }); - - it("should set the correct ETH oracle address", async function () { - const { manager, mockETHOracle } = await loadFixture(setupContracts); - expect(await manager.ethOracle()).to.eq(mockETHOracle.target); + it("should set the correct oracle address", async function () { + const { manager, mockOracle } = await loadFixture(setupContracts); + expect(await manager.oracle()).to.eq(mockOracle.target); }); it("should set the token refs", async function () { - const { manager, mockWeth, mockWampl, mockPool } = await loadFixture( - setupContracts, - ); + const { manager, mockPool } = await loadFixture(setupContracts); expect(await manager.POOL()).to.eq(mockPool.target); - expect(await manager.WETH()).to.eq(mockWeth.target); - expect(await manager.WAMPL()).to.eq(mockWampl.target); }); it("should set the active perc calculation params", async function () { @@ -145,48 +165,34 @@ describe("WethWamplManager", function () { }); }); - describe("#transferOwnership", function () { + describe("#updateOracle", function () { it("should fail to when called by non-owner", async function () { const { manager, addr1 } = await loadFixture(setupContracts); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [18]); await expect( - manager.connect(addr1).transferOwnership(addr1.address), - ).to.be.revertedWith("Unauthorized caller"); - }); - - it("should succeed when called by owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await manager.transferOwnership(addr1.address); - expect(await manager.owner()).to.eq(await addr1.getAddress()); - }); - }); - - describe("#setCpiOracle", function () { - it("should fail to when called by non-owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await expect(manager.connect(addr1).setCpiOracle(addr1.address)).to.be.revertedWith( - "Unauthorized caller", - ); + manager.connect(addr1).updateOracle(mockOracle.target), + ).to.be.revertedWith("Ownable: caller is not the owner"); }); - it("should succeed when called by owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await manager.setCpiOracle(addr1.address); - expect(await manager.cpiOracle()).to.eq(await addr1.getAddress()); - }); - }); - - describe("#setEthOracle", function () { - it("should fail to when called by non-owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await expect(manager.connect(addr1).setEthOracle(addr1.address)).to.be.revertedWith( - "Unauthorized caller", + it("should fail when decimals dont match", async function () { + const { manager } = await loadFixture(setupContracts); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [9]); + await expect(manager.updateOracle(mockOracle.target)).to.be.revertedWith( + "UnexpectedDecimals", ); }); it("should succeed when called by owner", async function () { - const { manager, addr1 } = await loadFixture(setupContracts); - await manager.setEthOracle(addr1.address); - expect(await manager.ethOracle()).to.eq(await addr1.getAddress()); + const { manager } = await loadFixture(setupContracts); + const mockOracle = new DMock("IMetaOracle"); + await mockOracle.deploy(); + await mockOracle.mockMethod("decimals()", [18]); + await manager.updateOracle(mockOracle.target); + expect(await manager.oracle()).to.eq(mockOracle.target); }); }); @@ -201,7 +207,7 @@ describe("WethWamplManager", function () { [percFP("0.5"), percFP("0.2"), percFP("1"), percFP("1")], [percFP("1"), percFP("1"), percFP("2"), percFP("0.2")], ), - ).to.be.revertedWith("Unauthorized caller"); + ).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should succeed when called by owner", async function () { @@ -233,7 +239,7 @@ describe("WethWamplManager", function () { const { manager, addr1 } = await loadFixture(setupContracts); await expect( manager.connect(addr1).setLiquidityRanges(7200, 330000, 1200), - ).to.be.revertedWith("Unauthorized caller"); + ).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should succeed when called by owner", async function () { @@ -254,7 +260,7 @@ describe("WethWamplManager", function () { .execOnVault( mockVault.refFactory.interface.encodeFunctionData("acceptManager"), ), - ).to.be.revertedWith("Unauthorized caller"); + ).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should succeed when called by owner", async function () { @@ -273,7 +279,7 @@ describe("WethWamplManager", function () { manager.execOnVault( mockVault.refFactory.interface.encodeFunctionData("acceptManager"), ), - ).to.be.revertedWith("Vault call failed"); + ).to.be.revertedWith("VaultExecutionFailed"); await mockVault.mockCall("acceptManager()", [], []); await manager.execOnVault( mockVault.refFactory.interface.encodeFunctionData("acceptManager"), @@ -298,145 +304,7 @@ describe("WethWamplManager", function () { expect(await manager.computeActiveLiqPerc(percFP("5"))).to.eq(percFP("0.2")); expect(await manager.computeActiveLiqPerc(percFP("10"))).to.eq(percFP("0.2")); expect(await manager.computeActiveLiqPerc(percFP("100000"))).to.eq(percFP("0.2")); - expect(await manager.computeActiveLiqPerc(ethers.MaxUint256)).to.eq(percFP("0.2")); - }); - }); - - describe("#computeDeviationFactor", function () { - describe("when cpi is invalid", function () { - it("should return invalid", async function () { - const { manager, mockETHOracle, mockCPIOracle, mockWampl } = await loadFixture( - setupContracts, - ); - await mockETHOracle.mockMethod("latestRoundData()", [ - 0, - ethOracleFP("3300"), - 0, - nowTS(), - 0, - ]); - await mockCPIOracle.mockMethod("getData()", [amplOracleFP("1.19"), false]); - await mockWampl.mockCall( - "wrapperToUnderlying(uint256)", - [wamplFP("1")], - [amplFP("18")], - ); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.051378374404781289")); - expect(r[1]).to.eq(false); - }); - }); - - describe("when eth price is invalid", function () { - it("should return invalid", async function () { - const { manager, mockETHOracle, mockCPIOracle, mockWampl } = await loadFixture( - setupContracts, - ); - await mockETHOracle.mockMethod("latestRoundData()", [ - 0, - ethOracleFP("3300"), - 0, - nowTS() - 86400 * 7, - 0, - ]); - await mockCPIOracle.mockMethod("getData()", [amplOracleFP("1.19"), true]); - await mockWampl.mockCall( - "wrapperToUnderlying(uint256)", - [wamplFP("1")], - [amplFP("18")], - ); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.051378374404781289")); - expect(r[1]).to.eq(false); - }); - }); - - it("should return deviation factor", async function () { - const { manager, mockETHOracle, mockCPIOracle, mockWampl } = await loadFixture( - setupContracts, - ); - await mockETHOracle.mockMethod("latestRoundData()", [ - 0, - ethOracleFP("3300"), - 0, - nowTS(), - 0, - ]); - await mockCPIOracle.mockMethod("getData()", [amplOracleFP("1.19"), true]); - await mockWampl.mockCall( - "wrapperToUnderlying(uint256)", - [wamplFP("1")], - [amplFP("18")], - ); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.051378374404781289")); - expect(r[1]).to.eq(true); - }); - - it("should return deviation factor", async function () { - const { manager, mockETHOracle, mockCPIOracle, mockWampl } = await loadFixture( - setupContracts, - ); - await mockETHOracle.mockMethod("latestRoundData()", [ - 0, - ethOracleFP("3300"), - 0, - nowTS(), - 0, - ]); - await mockCPIOracle.mockMethod("getData()", [amplOracleFP("1.19"), true]); - await mockWampl.mockCall( - "wrapperToUnderlying(uint256)", - [wamplFP("1")], - [amplFP("10")], - ); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("1.892481073928606322")); - expect(r[1]).to.eq(true); - }); - - it("should return deviation factor", async function () { - const { manager, mockETHOracle, mockCPIOracle, mockWampl } = await loadFixture( - setupContracts, - ); - await mockETHOracle.mockMethod("latestRoundData()", [ - 0, - ethOracleFP("3300"), - 0, - nowTS(), - 0, - ]); - await mockCPIOracle.mockMethod("getData()", [amplOracleFP("1.19"), true]); - await mockWampl.mockCall( - "wrapperToUnderlying(uint256)", - [wamplFP("1")], - [amplFP("25")], - ); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("0.756992429571442528")); - expect(r[1]).to.eq(true); - }); - - it("should return max deviation when price is too high", async function () { - const { manager, mockVault, mockETHOracle, mockCPIOracle, mockWampl } = - await loadFixture(setupContracts); - await mockVault.mockMethod("getTwap()", [1]); - await mockETHOracle.mockMethod("latestRoundData()", [ - 0, - ethOracleFP("3300"), - 0, - nowTS(), - 0, - ]); - await mockCPIOracle.mockMethod("getData()", [amplOracleFP("1.19"), true]); - await mockWampl.mockCall( - "wrapperToUnderlying(uint256)", - [wamplFP("1")], - [amplFP("18")], - ); - const r = await manager.computeDeviationFactor.staticCall(); - expect(r[0]).to.eq(percFP("100")); - expect(r[1]).to.eq(true); + expect(await manager.computeActiveLiqPerc(ethers.MaxInt256)).to.eq(percFP("0.2")); }); }); @@ -444,9 +312,7 @@ describe("WethWamplManager", function () { describe("when wampl sell", function () { it("should return true", async function () { const { manager, mockVault } = await loadFixture(setupContracts); - await mockVault.mockMethod("getTwap()", [30001]); - await mockVault.mockMethod("limitLower()", [20000]); - await mockVault.mockMethod("limitUpper()", [40000]); + await stubOverweightWampl(mockVault); expect(await manager.isOverweightWampl()).to.eq(true); }); }); @@ -454,427 +320,166 @@ describe("WethWamplManager", function () { describe("when wampl buy", function () { it("should return false", async function () { const { manager, mockVault } = await loadFixture(setupContracts); - await mockVault.mockMethod("getTwap()", [29999]); - await mockVault.mockMethod("limitLower()", [20000]); - await mockVault.mockMethod("limitUpper()", [40000]); + await stubOverweightWeth(mockVault); expect(await manager.isOverweightWampl()).to.eq(false); }); }); }); - describe("#rebalance", function () { - describe("when activePercDelta is within threshold", function () { - describe("when deviation is < 1 and goes > 1", function () { - describe("when overweight wampl", function () { - it("should trim liquidity & keep limit range", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - await manager.setActivePercParams( - percFP("1"), - [percFP("0.5"), percFP("0.2"), percFP("1"), percFP("1")], - [percFP("1"), percFP("1"), percFP("2"), percFP("0.2")], - ); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 7324], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 1464], - [], - ); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightWampl()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - }); - }); - - describe("when overweight weth", function () { - it("should trim liquidity & remove limit range", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - await manager.setActivePercParams( - percFP("1"), - [percFP("0.5"), percFP("0.2"), percFP("1"), percFP("1")], - [percFP("1"), percFP("1"), percFP("2"), percFP("0.2")], - ); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [50000]); - await mockVault.mockMethod("limitUpper()", [55000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 50000, 55000)], - [50000, 0, 0, 0, 0], - ); - - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 7324], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 1464], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [50000, 55000, 50000], - [], - ); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightWampl()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - }); - }); + describe("shouldRemoveLimitRange", function () { + describe("is overweight wampl", function () { + it("should return bool", async function () { + const { manager, mockVault } = await loadFixture(setupContracts); + await stubOverweightWampl(mockVault); + expect(await manager.shouldRemoveLimitRange(percFP("1.01"))).to.eq(false); + expect(await manager.shouldRemoveLimitRange(percFP("1"))).to.eq(false); + expect(await manager.shouldRemoveLimitRange(percFP("0.99"))).to.eq(true); }); + }); - describe("when deviation is > 1 and goes < 1", function () { - describe("when overweight wampl", function () { - it("should trim liquidity & remove limit range", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - await manager.setActivePercParams( - percFP("1"), - [percFP("0.5"), percFP("0.2"), percFP("1"), percFP("1")], - [percFP("1"), percFP("1"), percFP("2"), percFP("0.2")], - ); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - await manager.rebalance(); - - await mockVault.mockMethod("getTwap()", [52000]); - await mockVault.mockMethod("limitLower()", [50000]); - await mockVault.mockMethod("limitUpper()", [51000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 50000, 51000)], - [50000, 0, 0, 0, 0], - ); - - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 23982], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 4796], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [50000, 51000, 50000], - [], - ); - - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - expect(await manager.isOverweightWampl()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.850111862770710708")); - }); - }); - - describe("when overweight weth", function () { - it("should trim liquidity & keep limit range", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - await manager.setActivePercParams( - percFP("1"), - [percFP("0.5"), percFP("0.2"), percFP("1"), percFP("1")], - [percFP("1"), percFP("1"), percFP("2"), percFP("0.2")], - ); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - await manager.rebalance(); - - await mockVault.mockMethod("getTwap()", [52000]); - await mockVault.mockMethod("limitLower()", [53000]); - await mockVault.mockMethod("limitUpper()", [55000]); - - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 23982], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 4796], - [], - ); - - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - expect(await manager.isOverweightWampl()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.850111862770710708")); - }); - }); + describe("is overweight weth", function () { + it("should return bool", async function () { + const { manager, mockVault } = await loadFixture(setupContracts); + await stubOverweightWeth(mockVault); + expect(await manager.shouldRemoveLimitRange(percFP("1.01"))).to.eq(true); + expect(await manager.shouldRemoveLimitRange(percFP("1"))).to.eq(false); + expect(await manager.shouldRemoveLimitRange(percFP("0.99"))).to.eq(false); }); }); + }); - describe("when activePercDelta is outside threshold", function () { - describe("when deviation is < 1 and goes > 1", function () { - describe("when overweight wampl", function () { - it("should trim liquidity & keep limit range", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 7324], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 1464], - [], - ); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightWampl()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - }); - }); - - describe("when overweight weth", function () { - it("should trim liquidity & remove limit range", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [50000]); - await mockVault.mockMethod("limitUpper()", [55000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 50000, 55000)], - [50000, 0, 0, 0, 0], - ); - - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 7324], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 1464], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [50000, 55000, 50000], - [], - ); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightWampl()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - }); - }); - }); + describe("shouldForceRebalance", function () { + it("should return bool", async function () { + const { manager } = await loadFixture(setupContracts); - describe("when deviation is > 1 and goes < 1", function () { - describe("when overweight wampl", function () { - it("should trim liquidity & remove limit range", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - await manager.rebalance(); - - await mockVault.mockMethod("getTwap()", [52000]); - await mockVault.mockMethod("limitLower()", [50000]); - await mockVault.mockMethod("limitUpper()", [51000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 50000, 51000)], - [50000, 0, 0, 0, 0], - ); - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 23982], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 4796], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [50000, 51000, 50000], - [], - ); - - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - expect(await manager.isOverweightWampl()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.850111862770710708")); - }); - }); - - describe("when overweight weth", function () { - it("should trim liquidity & keep limit range", async function () { - const { manager, mockVault } = await loadFixture(setupContracts); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - await manager.rebalance(); - - await mockVault.mockMethod("getTwap()", [52000]); - await mockVault.mockMethod("limitLower()", [53000]); - await mockVault.mockMethod("limitUpper()", [55000]); - - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [-800000, 800000, 23982], - [], - ); - await mockVault.mockCall( - "emergencyBurn(int24,int24,uint128)", - [45000, 55000, 4796], - [], - ); - - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - expect(await manager.isOverweightWampl()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.850111862770710708")); - }); - }); - }); + // inside active delta + expect( + await manager.shouldForceRebalance(percFP("0.5"), percFP("0.8"), percFP("0.09")), + ).to.eq(false); + expect( + await manager.shouldForceRebalance(percFP("0.9"), percFP("1.1"), percFP("0.09")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1.0"), percFP("1.1"), percFP("0.09")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1.05"), percFP("1.1"), percFP("0.09")), + ).to.eq(false); + expect( + await manager.shouldForceRebalance(percFP("1.1"), percFP("1"), percFP("0.09")), + ).to.eq(false); + expect( + await manager.shouldForceRebalance(percFP("1.1"), percFP("0.99"), percFP("0.09")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1"), percFP("0.8"), percFP("0.09")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("0.9"), percFP("0.8"), percFP("0.09")), + ).to.eq(false); + + // outside active delta + expect( + await manager.shouldForceRebalance(percFP("0.5"), percFP("0.8"), percFP("1.1")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("0.9"), percFP("1.1"), percFP("1.1")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1.0"), percFP("1.1"), percFP("1.1")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1.05"), percFP("1.1"), percFP("1.1")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1.1"), percFP("1"), percFP("1.1")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1.1"), percFP("0.99"), percFP("1.1")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("1"), percFP("0.8"), percFP("1.1")), + ).to.eq(true); + expect( + await manager.shouldForceRebalance(percFP("0.9"), percFP("0.8"), percFP("1.1")), + ).to.eq(true); }); + }); - describe("when deviation remains below 1", function () { - describe("when overweight wampl", function () { - it("should not force rebalance", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - await manager.setActivePercParams( - percFP("1"), - [percFP("0.5"), percFP("0.2"), percFP("1"), percFP("1")], - [percFP("1"), percFP("1"), percFP("2"), percFP("0.2")], - ); - - await mockVault.mockMethod("getTwap()", [51500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 40000, 45000)], - [50000, 0, 0, 0, 0], - ); - await mockVault.mockMethod("rebalance()", []); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - - expect(await manager.prevDeviation()).to.eq("0"); - expect(await manager.isOverweightWampl()).to.eq(true); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("0.893695795923885030")); - }); - }); + describe("#rebalance", function () { + it("should rebalance, trim liquidity and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightWeth(mockVault); + await stubForceRebalance(mockVault); + await stubTrimLiquidity(mockVault, 1600); + await stubUnchangedLimitRange(mockVault); + await mockOracle.mockMethod("amplPriceDeviation()", [priceFP("0.99"), true]); + + expect(await manager.isOverweightWampl()).to.eq(false); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("0.99")); }); - describe("when deviation remains above 1", function () { - describe("when overweight weth", function () { - it("should not force rebalance", async function () { - const { manager, mockVault, mockPool } = await loadFixture(setupContracts); - await manager.setActivePercParams( - percFP("1"), - [percFP("0.5"), percFP("0.2"), percFP("1"), percFP("1")], - [percFP("1"), percFP("1"), percFP("2"), percFP("0.2")], - ); - - await mockVault.mockMethod("getTwap()", [49500]); - await mockVault.mockMethod("limitLower()", [40000]); - await mockVault.mockMethod("limitUpper()", [45000]); - await mockVault.mockMethod("period()", [86400]); - await mockVault.mockCall("setPeriod(uint32)", [0], []); - await mockVault.mockCall("setPeriod(uint32)", [86400], []); - await mockVault.mockMethod("rebalance()", []); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - await manager.rebalance(); - - await mockVault.clearMockCall("setPeriod(uint32)", [0]); - await mockVault.clearMockCall("setPeriod(uint32)", [86400]); - await mockVault.clearMockCall("period()", []); - await mockVault.clearMockMethod("emergencyBurn(int24,int24,uint128)"); - - await mockVault.mockMethod("getTwap()", [50000]); - await mockVault.mockMethod("limitLower()", [53000]); - await mockVault.mockMethod("limitUpper()", [55000]); - await mockPool.mockCall( - "positions(bytes32)", - [univ3PositionKey(mockVault.target, 53000, 55000)], - [50000, 0, 0, 0, 0], - ); - await mockVault.mockMethod("emergencyBurn(int24,int24,uint128)", []); - - expect(await manager.prevDeviation()).to.eq(percFP("1.091551595254704898")); - expect(await manager.isOverweightWampl()).to.eq(false); - await expect(manager.rebalance()).not.to.be.reverted; - expect(await manager.prevDeviation()).to.eq(percFP("1.038318591387163286")); - }); - }); + it("should rebalance, trim liquidity and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightWampl(mockVault); + await stubForceRebalance(mockVault); + await stubTrimLiquidity(mockVault, 1600); + await stubRemovedLimitRange(mockVault); + await mockOracle.mockMethod("amplPriceDeviation()", [priceFP("0.99"), true]); + + expect(await manager.isOverweightWampl()).to.eq(true); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("0.99")); + }); + + it("should rebalance, trim liquidity and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightWeth(mockVault); + await stubForceRebalance(mockVault); + await stubTrimLiquidity(mockVault, 16000); + await stubRemovedLimitRange(mockVault); + await mockOracle.mockMethod("amplPriceDeviation()", [priceFP("1.2"), true]); + + expect(await manager.isOverweightWampl()).to.eq(false); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("1.2")); + }); + + it("should rebalance, trim liquidity and prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightWampl(mockVault); + await stubForceRebalance(mockVault); + await stubTrimLiquidity(mockVault, 16000); + await stubUnchangedLimitRange(mockVault); + await mockOracle.mockMethod("amplPriceDeviation()", [priceFP("1.2"), true]); + + expect(await manager.isOverweightWampl()).to.eq(true); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("1.2")); + }); + + it("should rebalance, trim liquidity and not change prev_deviation", async function () { + const { manager, mockVault, mockOracle } = await loadFixture(setupContracts); + + await stubOverweightWampl(mockVault); + await stubRebalance(mockVault); + await stubTrimLiquidity(mockVault, 80000); + await stubRemovedLimitRange(mockVault); + await mockOracle.mockMethod("amplPriceDeviation()", [priceFP("1.2"), false]); + + expect(await manager.isOverweightWampl()).to.eq(true); + expect(await manager.prevDeviation()).to.eq("0"); + await expect(manager.rebalance()).not.to.be.reverted; + expect(await manager.prevDeviation()).to.eq(percFP("0")); }); }); }); diff --git a/spot-vaults/test/helpers.ts b/spot-vaults/test/helpers.ts index aa3bfa14..141635fd 100644 --- a/spot-vaults/test/helpers.ts +++ b/spot-vaults/test/helpers.ts @@ -3,13 +3,20 @@ import { Contract, ContractFactory } from "ethers"; export const sciParseFloat = (a: string): BigInt => a.includes("e") ? parseFloat(a).toFixed(18) : a; +export const percFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); +export const priceFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); + export const usdFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 6); export const perpFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 9); -export const percentageFP = (a: string): BigInt => - ethers.parseUnits(sciParseFloat(a), 18); -export const priceFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); export const lpAmtFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 24); -export const oracleAnsFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 8); +export const amplFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 9); +export const wamplFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); +export const wethFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); +export const usdOracleFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 8); +export const ethOracleFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 18); +export const amplOracleFP = (a: string): BigInt => + ethers.parseUnits(sciParseFloat(a), 18); +export const drFP = (a: string): BigInt => ethers.parseUnits(sciParseFloat(a), 8); export class DMock { private refArtifact: string; diff --git a/yarn.lock b/yarn.lock index 50ffa547..0ac2c2f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -157,7 +157,7 @@ __metadata: ethers: ^6.6.0 ethers-v5: "npm:ethers@^5.7.0" ganache-cli: latest - hardhat: ^2.22.8 + hardhat: ^2.22.10 hardhat-gas-reporter: latest lodash: ^4.17.21 prettier: ^2.7.1 @@ -9321,9 +9321,9 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.22.8": - version: 2.22.8 - resolution: "hardhat@npm:2.22.8" +"hardhat@npm:^2.22.10": + version: 2.22.10 + resolution: "hardhat@npm:2.22.10" dependencies: "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 @@ -9378,7 +9378,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 3731b510540f800b3b931e3ad7db4510dbd35eff18f34382875778db43de2a5ce1c52596c392aa694324f66392e844fede85954ab3cdc08df3da10f2a810135f + checksum: 2bb961a11f428fd025f990ea18472f4197c8352dd81f4231f27c04b7a8e94bc71d668262475102ae2c339ad83dd0e759b90ac7e4905f043be7bde471c04b5951 languageName: node linkType: hard