diff --git a/spot-vaults/contracts/BillBroker.sol b/spot-vaults/contracts/BillBroker.sol index 1efb986d..0e12f3b4 100644 --- a/spot-vaults/contracts/BillBroker.sol +++ b/spot-vaults/contracts/BillBroker.sol @@ -50,6 +50,9 @@ import { UnacceptableSwap, UnreliablePrice, UnexpectedDecimals, InvalidPerc, Inv * Lenders can buy perps from the bill broker contract when it's under-priced, * hold the perp tokens until the market price recovers and sell it back to the bill broker contract. * + * Single Sided deposits: + * The pool also supports single sided deposits with either perps or usd tokens + * insofar as it brings the pool back into balance. * */ contract BillBroker is @@ -112,6 +115,29 @@ contract BillBroker is /// the swap fees transition from a flat percentage fee to a linear function. Range public arSoftBound; + //-------------------------------------------------------------------------- + // Events + + /// @notice Emitted when a user deposits USD tokens to mint LP tokens. + /// @param usdAmtIn The amount of USD tokens deposited. + /// @param preOpState The reserve state before the deposit operation. + event DepositUSD(uint256 usdAmtIn, ReserveState preOpState); + + /// @notice Emitted when a user deposits Perp tokens to mint LP tokens. + /// @param perpAmtIn The amount of Perp tokens deposited. + /// @param preOpState The reserve state before the deposit operation. + event DepositPerp(uint256 perpAmtIn, ReserveState preOpState); + + /// @notice Emitted when a user swaps Perp tokens for USD tokens. + /// @param perpAmtIn The amount of Perp tokens swapped in. + /// @param preOpState The reserve state before the swap operation. + event SwapPerpsForUSD(uint256 perpAmtIn, ReserveState preOpState); + + /// @notice Emitted when a user swaps USD tokens for Perp tokens. + /// @param usdAmtIn The amount of USD tokens swapped in. + /// @param preOpState The reserve state before the swap operation. + event SwapUSDForPerps(uint256 usdAmtIn, ReserveState preOpState); + //-------------------------------------------------------------------------- // Modifiers @@ -289,6 +315,90 @@ contract BillBroker is _mint(msg.sender, mintAmt); } + /// @notice Single sided usd token deposit and mint LP tokens. + /// @param usdAmtIn The amount of usd tokens to be deposited. + /// @param postOpAssetRatioMax The system asset ratio can be no higher than this value after deposit. + /// @return mintAmt The amount of LP tokens minted. + function depositUSD( + uint256 usdAmtIn, + uint256 postOpAssetRatioMax + ) external nonReentrant whenNotPaused returns (uint256 mintAmt) { + ReserveState memory preOpState = reserveState(); + uint256 preOpAssetRatio = assetRatio(preOpState); + uint256 postOpAssetRatio = assetRatio( + ReserveState({ + usdBalance: preOpState.usdBalance + usdAmtIn, + perpBalance: preOpState.perpBalance, + usdPrice: preOpState.usdPrice, + perpPrice: preOpState.perpPrice + }) + ); + + // We allow minting only pool is underweight usd + if (preOpAssetRatio >= ONE || postOpAssetRatio > ONE) { + return 0; + } + + mintAmt = computeMintAmtWithUSD(usdAmtIn, preOpState); + if (mintAmt <= 0) { + return 0; + } + if (postOpAssetRatio > postOpAssetRatioMax) { + revert SlippageTooHigh(); + } + + // Transfer usd tokens from the user + usd.safeTransferFrom(msg.sender, address(this), usdAmtIn); + + // mint LP tokens to the user + _mint(msg.sender, mintAmt); + + // Emit deposit info + emit DepositUSD(usdAmtIn, preOpState); + } + + /// @notice Single sided perp token deposit and mint LP tokens. + /// @param perpAmtIn The amount of perp tokens to be deposited. + /// @param postOpAssetRatioMin The system asset ratio can be no lower than this value after deposit. + /// @return mintAmt The amount of LP tokens minted. + function depositPerp( + uint256 perpAmtIn, + uint256 postOpAssetRatioMin + ) external nonReentrant whenNotPaused returns (uint256 mintAmt) { + ReserveState memory preOpState = reserveState(); + uint256 preOpAssetRatio = assetRatio(preOpState); + uint256 postOpAssetRatio = assetRatio( + ReserveState({ + usdBalance: preOpState.usdBalance, + perpBalance: preOpState.perpBalance + perpAmtIn, + usdPrice: preOpState.usdPrice, + perpPrice: preOpState.perpPrice + }) + ); + + // We allow minting only pool is underweight perp + if (preOpAssetRatio <= ONE || postOpAssetRatio < ONE) { + return 0; + } + + mintAmt = computeMintAmtWithPerp(perpAmtIn, preOpState); + if (mintAmt <= 0) { + return 0; + } + if (postOpAssetRatio < postOpAssetRatioMin) { + revert SlippageTooHigh(); + } + + // Transfer perp tokens from the user + perp.safeTransferFrom(msg.sender, address(this), perpAmtIn); + + // mint LP tokens to the user + _mint(msg.sender, mintAmt); + + // Emit deposit info + emit DepositPerp(perpAmtIn, preOpState); + } + /// @notice Burns LP tokens and redeems usd and perp tokens. /// @param burnAmt The LP tokens to be burnt. /// @return usdAmtOut The amount usd tokens returned. @@ -323,10 +433,11 @@ contract BillBroker is uint256 perpAmtMin ) external nonReentrant whenNotPaused returns (uint256 perpAmtOut) { // compute perp amount out + ReserveState memory preOpState = reserveState(); uint256 protocolFeePerpAmt; (perpAmtOut, , protocolFeePerpAmt) = computeUSDToPerpSwapAmt( usdAmtIn, - reserveState() + preOpState ); if (usdAmtIn <= 0 || perpAmtOut <= 0) { revert UnacceptableSwap(); @@ -345,6 +456,9 @@ contract BillBroker is // transfer perps out to the user perp.safeTransfer(msg.sender, perpAmtOut); + + // Emit swap info + emit SwapUSDForPerps(usdAmtIn, preOpState); } /// @notice Swaps perp tokens from the user for usd tokens from the reserve. @@ -356,11 +470,9 @@ contract BillBroker is uint256 usdAmtMin ) external nonReentrant whenNotPaused returns (uint256 usdAmtOut) { // Compute swap amount + ReserveState memory preOpState = reserveState(); uint256 protocolFeeUsdAmt; - (usdAmtOut, , protocolFeeUsdAmt) = computePerpToUSDSwapAmt( - perpAmtIn, - reserveState() - ); + (usdAmtOut, , protocolFeeUsdAmt) = computePerpToUSDSwapAmt(perpAmtIn, preOpState); if (perpAmtIn <= 0 || usdAmtOut <= 0) { revert UnacceptableSwap(); } @@ -378,11 +490,30 @@ contract BillBroker is // transfer usd out to the user usd.safeTransfer(msg.sender, usdAmtOut); + + // Emit swap info + emit SwapPerpsForUSD(perpAmtIn, preOpState); } //----------------------------------------------------------------------------- // Public methods + /// @notice Computes the amount of LP tokens minted, + /// when the given number of usd tokens are deposited. + /// @param usdAmtIn The amount of usd tokens deposited. + /// @return mintAmt The amount of LP tokens minted. + function computeMintAmtWithUSD(uint256 usdAmtIn) public returns (uint256 mintAmt) { + return computeMintAmtWithUSD(usdAmtIn, reserveState()); + } + + /// @notice Computes the amount of LP tokens minted, + /// when the given number of perp tokens are deposited. + /// @param perpAmtIn The amount of perp tokens deposited. + /// @return mintAmt The amount of LP tokens minted. + function computeMintAmtWithPerp(uint256 perpAmtIn) public returns (uint256 mintAmt) { + return computeMintAmtWithPerp(perpAmtIn, reserveState()); + } + /// @notice Computes the amount of usd tokens swapped out, /// when the given number of perp tokens are sent in. /// @param perpAmtIn The amount of perp tokens swapped in. @@ -508,6 +639,58 @@ contract BillBroker is mintAmt = mintAmt.mulDiv(ONE - fees.mintFeePerc, ONE); } + /// @notice Computes the amount of LP tokens minted, + /// when the given number of usd tokens are deposited. + /// @param usdAmtIn The amount of usd tokens deposited. + /// @param s The current reserve state. + /// @return mintAmt The amount of LP tokens minted. + function computeMintAmtWithUSD( + uint256 usdAmtIn, + ReserveState memory s + ) public view returns (uint256) { + if (usdAmtIn <= 0) { + return 0; + } + + uint256 valueIn = s.usdPrice.mulDiv(usdAmtIn, usdUnitAmt); + uint256 totalReserveVal = (s.usdPrice.mulDiv(s.usdBalance, usdUnitAmt) + + s.perpPrice.mulDiv(s.perpBalance, perpUnitAmt)); + + return + (totalReserveVal > 0) + ? valueIn.mulDiv(totalSupply(), totalReserveVal).mulDiv( + ONE - fees.mintFeePerc, + ONE + ) + : 0; + } + + /// @notice Computes the amount of LP tokens minted, + /// when the given number of perp tokens are deposited. + /// @param perpAmtIn The amount of perp tokens deposited. + /// @param s The current reserve state. + /// @return mintAmt The amount of LP tokens minted. + function computeMintAmtWithPerp( + uint256 perpAmtIn, + ReserveState memory s + ) public view returns (uint256) { + if (perpAmtIn <= 0) { + return 0; + } + + uint256 valueIn = s.perpPrice.mulDiv(perpAmtIn, perpUnitAmt); + uint256 totalReserveVal = (s.usdPrice.mulDiv(s.usdBalance, usdUnitAmt) + + s.perpPrice.mulDiv(s.perpBalance, perpUnitAmt)); + + return + (totalReserveVal > 0) + ? valueIn.mulDiv(totalSupply(), totalReserveVal).mulDiv( + ONE - fees.mintFeePerc, + ONE + ) + : 0; + } + /// @notice Computes the amount of usd and perp tokens redeemed, /// when the given number of LP tokens are burnt. /// @param burnAmt The amount of LP tokens to be burnt. diff --git a/spot-vaults/hardhat.config.ts b/spot-vaults/hardhat.config.ts index 88c2c00e..a9ad1cfa 100644 --- a/spot-vaults/hardhat.config.ts +++ b/spot-vaults/hardhat.config.ts @@ -78,6 +78,7 @@ export default { enabled: !!process.env.REPORT_GAS, excludeContracts: ["_test/"], coinmarketcap: process.env.COINMARKETCAP_API_KEY, + L1Etherscan: process.env.ETHERSCAN_API_KEY, }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, diff --git a/spot-vaults/package.json b/spot-vaults/package.json index 311a8cd2..b2915325 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.6", + "hardhat": "^2.22.8", "hardhat-gas-reporter": "latest", "lodash": "^4.17.21", "prettier": "^2.7.1", diff --git a/spot-vaults/test/BillBroker_deposit_redeem.ts b/spot-vaults/test/BillBroker_deposit_redeem.ts index 2c460873..784f65a9 100644 --- a/spot-vaults/test/BillBroker_deposit_redeem.ts +++ b/spot-vaults/test/BillBroker_deposit_redeem.ts @@ -48,6 +48,16 @@ describe("BillBroker", function () { return { deployer, otherUser, usd, perp, pricingStrategy, billBroker }; } + async function assetRatio(billBroker) { + const r = await billBroker.reserveState.staticCall(); + return billBroker.assetRatio({ + usdBalance: r[0], + perpBalance: r[1], + usdPrice: r[2], + perpPrice: r[3], + }); + } + describe("#computeMintAmt", function () { describe("when amounts available are zero", function () { it("should return zero", async function () { @@ -170,15 +180,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.computeMintAmt.staticCall(usdFP("100"), 0n); expect(r[0]).to.eq(lpAmtFP("93.478260869565217391304347")); @@ -203,15 +205,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.computeMintAmt.staticCall(0n, perpFP("100")); expect(r[0]).to.eq(lpAmtFP("107.5")); @@ -221,6 +215,180 @@ describe("BillBroker", function () { }); }); + describe("#computeMintAmtWithUSD", function () { + describe("when usdAmtIn is zero", function () { + it("should return zero", async function () { + const { billBroker } = await loadFixture(setupContracts); + expect(await billBroker.computeMintAmtWithUSD.staticCall(0n)).to.eq(0n); + }); + }); + + describe("when total supply is zero", function () { + it("should return zero", async function () { + const { billBroker } = await loadFixture(setupContracts); + expect(await billBroker.computeMintAmtWithUSD.staticCall(usdFP("100"))).to.eq(0n); + }); + }); + + describe("when fee = 0", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + expect(await billBroker.computeMintAmtWithUSD.staticCall(usdFP("11.5"))).to.eq( + lpAmtFP("10.5"), + ); + }); + }); + + describe("when fee > 0", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + await billBroker.updateFees({ + mintFeePerc: percentageFP("0.1"), + burnFeePerc: 0n, + perpToUSDSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + usdToPerpSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + protocolSwapSharePerc: 0n, + }); + expect(await billBroker.computeMintAmtWithUSD.staticCall(usdFP("11.5"))).to.eq( + lpAmtFP("9.45"), + ); + }); + }); + + describe("when the pool has only perps", function () { + it("should mint lp tokens", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("115"), + perpFP("100"), + usdFP("115"), + perpFP("100"), + ); + + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.swapPerpsForUSD(perpFP("100"), 0n); + expect(await usd.balanceOf(billBroker.target)).to.eq(0n); + + expect(await assetRatio(billBroker)).to.eq(0); + expect(await billBroker.computeMintAmtWithUSD.staticCall(usdFP("115"))).to.eq( + lpAmtFP("107.5"), + ); + }); + }); + }); + + describe("#computeMintAmtWithPerp", function () { + describe("when perpAmtIn is zero", function () { + it("should return zero", async function () { + const { billBroker } = await loadFixture(setupContracts); + expect(await billBroker.computeMintAmtWithPerp.staticCall(0n)).to.eq(0n); + }); + }); + + describe("when total supply is zero", function () { + it("should return zero", async function () { + const { billBroker } = await loadFixture(setupContracts); + expect(await billBroker.computeMintAmtWithPerp.staticCall(perpFP("100"))).to.eq( + 0n, + ); + }); + }); + + describe("when fee = 0", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("200")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("200"), + perpFP("100"), + usdFP("200"), + perpFP("100"), + ); + expect(await billBroker.computeMintAmtWithPerp.staticCall(perpFP("10.5"))).to.eq( + lpAmtFP("11.5"), + ); + }); + }); + + describe("when fee > 0", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("200")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("200"), + perpFP("100"), + usdFP("200"), + perpFP("100"), + ); + await billBroker.updateFees({ + mintFeePerc: percentageFP("0.1"), + burnFeePerc: 0n, + perpToUSDSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + usdToPerpSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + protocolSwapSharePerc: 0n, + }); + expect(await billBroker.computeMintAmtWithPerp.staticCall(perpFP("10.5"))).to.eq( + lpAmtFP("10.35"), + ); + }); + }); + + describe("when the pool has only usd", function () { + it("should mint lp tokens", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("115"), + perpFP("100"), + usdFP("115"), + perpFP("100"), + ); + + await usd.approve(billBroker.target, usdFP("115")); + await billBroker.swapUSDForPerps(usdFP("115"), 0n); + expect(await perp.balanceOf(billBroker.target)).to.eq(0n); + + expect(await assetRatio(billBroker)).to.eq(ethers.MaxUint256); + expect(await billBroker.computeMintAmtWithPerp.staticCall(perpFP("100"))).to.eq( + lpAmtFP("107.5"), + ); + }); + }); + }); + describe("#deposit", function () { describe("when paused", function () { it("should revert", async function () { @@ -433,15 +601,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); await usd.approve(billBroker.target, usdFP("115")); await expect(() => @@ -470,15 +630,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); await perp.approve(billBroker.target, perpFP("100")); await expect(() => @@ -488,6 +640,438 @@ describe("BillBroker", function () { }); }); + describe("#depositUSD", function () { + describe("when paused", 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; + }); + }); + + 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")); + expect(r).to.eq(0n); + }); + }); + + describe("when assetRatioPre > 1", function () { + it("should return zero", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("115"), + perpFP("100"), + usdFP("115"), + perpFP("100"), + ); + await usd.approve(billBroker.target, usdFP("100")); + expect( + await billBroker.depositUSD.staticCall(usdFP("100"), ethers.MaxUint256), + ).to.eq(0n); + }); + }); + + describe("when assetRatioPost > 1", function () { + it("should return zero", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("100")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("100"), + perpFP("100"), + usdFP("100"), + perpFP("100"), + ); + await usd.approve(billBroker.target, usdFP("100")); + expect( + await billBroker.depositUSD.staticCall(usdFP("100"), ethers.MaxUint256), + ).to.eq(0n); + }); + }); + + describe("when assetRatioPre = 1", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("115"), + perpFP("100"), + usdFP("115"), + perpFP("100"), + ); + await usd.approve(billBroker.target, usdFP("1")); + expect( + await billBroker.depositUSD.staticCall(usdFP("1"), ethers.MaxUint256), + ).to.eq(0n); + }); + }); + + describe("when assetRatioPost = 1", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + await usd.approve(billBroker.target, usdFP("115")); + expect( + await billBroker.depositUSD.staticCall(usdFP("115"), ethers.MaxUint256), + ).to.eq(lpAmtFP("105")); + }); + }); + + describe("when slippage is too high", function () { + it("should revert asset ratio increases beyond limit", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + + await usd.approve(billBroker.target, usdFP("10")); + await expect( + billBroker.depositUSD(usdFP("10"), percentageFP("0.50")), + ).to.be.revertedWithCustomError(billBroker, "SlippageTooHigh"); + }); + }); + + describe("successful deposit", function () { + it("should transfer usd from user", async function () { + const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + + await usd.approve(billBroker.target, usdFP("10")); + await expect(() => + billBroker.depositUSD(usdFP("10"), percentageFP("1")), + ).to.changeTokenBalance(usd, deployer, usdFP("-10")); + }); + + it("should mint lp tokens", async function () { + const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + + await usd.approve(billBroker.target, usdFP("10")); + await expect(() => + billBroker.depositUSD(usdFP("10"), percentageFP("1")), + ).to.changeTokenBalance( + billBroker, + deployer, + lpAmtFP("9.130434782608695652173913"), + ); + expect(await billBroker.totalSupply()).to.eq( + lpAmtFP("324.130434782608695652173913"), + ); + }); + + it("should emit DepositUSD", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + await usd.approve(billBroker.target, usdFP("10")); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.depositUSD(usdFP("10"), percentageFP("1"))) + .to.emit(billBroker, "DepositUSD") + .withArgs(usdFP("10"), r); + expect(await billBroker.totalSupply()).to.eq( + lpAmtFP("324.130434782608695652173913"), + ); + }); + + it("should return mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + + await usd.approve(billBroker.target, usdFP("10")); + const r = await billBroker.depositUSD.staticCall(usdFP("10"), percentageFP("1")); + expect(r).to.eq(lpAmtFP("9.130434782608695652173913")); + }); + }); + + describe("when fee > 0", 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"), + burnFeePerc: 0n, + perpToUSDSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + usdToPerpSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + protocolSwapSharePerc: 0n, + }); + + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("200")); + await billBroker.deposit( + usdFP("115"), + perpFP("200"), + usdFP("115"), + perpFP("200"), + ); + + await usd.approve(billBroker.target, usdFP("10")); + await expect(() => + billBroker.depositUSD(usdFP("10"), percentageFP("1")), + ).to.changeTokenBalance( + billBroker, + deployer, + lpAmtFP("7.395652173913043478260868"), + ); + }); + }); + }); + + describe("#depositPerp", function () { + describe("when paused", 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; + }); + }); + + 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")); + expect(r).to.eq(0n); + }); + }); + + describe("when assetRatioPre < 1", function () { + it("should return zero", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("200")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("200"), + perpFP("100"), + usdFP("200"), + perpFP("100"), + ); + await perp.approve(billBroker.target, perpFP("100")); + expect(await billBroker.depositPerp.staticCall(perpFP("100"), 0n)).to.eq(0n); + }); + }); + + describe("when assetRatioPost < 1", function () { + it("should return zero", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("120")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("120"), + perpFP("100"), + usdFP("120"), + perpFP("100"), + ); + await perp.approve(billBroker.target, perpFP("100")); + expect(await billBroker.depositPerp.staticCall(perpFP("100"), 0n)).to.eq(0n); + }); + }); + + describe("when assetRatioPre = 1", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("115"), + perpFP("100"), + usdFP("115"), + perpFP("100"), + ); + await perp.approve(billBroker.target, perpFP("1")); + expect(await billBroker.depositPerp.staticCall(perpFP("1"), 0n)).to.eq(0n); + }); + }); + + describe("when assetRatioPost = 1", function () { + it("should return the mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("115")); + await perp.approve(billBroker.target, perpFP("90")); + await billBroker.deposit(usdFP("115"), perpFP("90"), usdFP("115"), perpFP("90")); + await perp.approve(billBroker.target, perpFP("10")); + expect(await billBroker.depositPerp.staticCall(perpFP("10"), 0n)).to.eq( + lpAmtFP("10.789473684210526315789473"), + ); + }); + }); + + describe("when slippage is too high", function () { + it("should revert asset ratio reduces below the limit", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("230"), + perpFP("100"), + usdFP("230"), + perpFP("100"), + ); + + await perp.approve(billBroker.target, perpFP("10")); + await expect( + billBroker.depositPerp(perpFP("10"), percentageFP("1.85")), + ).to.be.revertedWithCustomError(billBroker, "SlippageTooHigh"); + }); + }); + + describe("successful deposit", function () { + it("should transfer perps from user", async function () { + const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("230"), + perpFP("100"), + usdFP("230"), + perpFP("100"), + ); + + await perp.approve(billBroker.target, perpFP("10")); + await expect(() => + billBroker.depositPerp(perpFP("10"), percentageFP("1")), + ).to.changeTokenBalance(perp, deployer, perpFP("-10")); + }); + + it("should mint lp tokens", async function () { + const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("230"), + perpFP("100"), + usdFP("230"), + perpFP("100"), + ); + + await perp.approve(billBroker.target, perpFP("10")); + await expect(() => + billBroker.depositPerp(perpFP("10"), percentageFP("1")), + ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("11")); + expect(await billBroker.totalSupply()).to.eq(lpAmtFP("341")); + }); + + it("should emit DepositPerp", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("230"), + perpFP("100"), + usdFP("230"), + perpFP("100"), + ); + + await perp.approve(billBroker.target, perpFP("10")); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.depositPerp(perpFP("10"), percentageFP("1"))) + .to.emit(billBroker, "DepositPerp") + .withArgs(perpFP("10"), r); + expect(await billBroker.totalSupply()).to.eq(lpAmtFP("341")); + }); + + it("should return mint amount", async function () { + const { billBroker, usd, perp } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("230"), + perpFP("100"), + usdFP("230"), + perpFP("100"), + ); + + await perp.approve(billBroker.target, perpFP("10")); + const r = await billBroker.depositPerp.staticCall( + perpFP("10"), + percentageFP("1"), + ); + expect(r).to.eq(lpAmtFP("11")); + }); + }); + + describe("when fee > 0", function () { + it("should withhold fees and mint lp tokens", async function () { + const { billBroker, usd, perp, deployer } = await loadFixture(setupContracts); + await usd.approve(billBroker.target, usdFP("230")); + await perp.approve(billBroker.target, perpFP("100")); + await billBroker.deposit( + usdFP("230"), + perpFP("100"), + usdFP("230"), + perpFP("100"), + ); + + await billBroker.updateFees({ + mintFeePerc: percentageFP("0.1"), + burnFeePerc: 0n, + perpToUSDSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + usdToPerpSwapFeePercs: { + lower: 0n, + upper: 0n, + }, + protocolSwapSharePerc: 0n, + }); + await perp.approve(billBroker.target, perpFP("10")); + await expect(() => + billBroker.depositPerp(perpFP("10"), percentageFP("1")), + ).to.changeTokenBalance(billBroker, deployer, lpAmtFP("9.9")); + }); + }); + }); + describe("#computeRedemptionAmts", function () { describe("when burn amount is zero", function () { it("should return zero", async function () { @@ -540,7 +1124,7 @@ describe("BillBroker", function () { }); }); - describe("when fee is zero", function () { + describe("when fee > 0", function () { it("should withhold and return redemption amounts", async function () { const { billBroker, usd, perp } = await loadFixture(setupContracts); await billBroker.updateFees({ diff --git a/spot-vaults/test/BillBroker_swap.ts b/spot-vaults/test/BillBroker_swap.ts index c58faab5..d22bca2d 100644 --- a/spot-vaults/test/BillBroker_swap.ts +++ b/spot-vaults/test/BillBroker_swap.ts @@ -570,6 +570,14 @@ describe("BillBroker", function () { expect(r.usdBalance).to.eq(usdFP("115115")); expect(r.perpBalance).to.eq(perpFP("99900")); }); + + it("should emit SwapUSDForPerps", async function () { + const { billBroker } = await loadFixture(setupContracts); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapUSDForPerps(usdFP("115"), perpFP("100"))) + .to.emit(billBroker, "SwapUSDForPerps") + .withArgs(usdFP("115"), r); + }); }); describe("stable swap with fees", function () { @@ -622,6 +630,19 @@ describe("BillBroker", function () { expect(r.usdBalance).to.eq(usdFP("115115")); expect(r.perpBalance).to.eq(perpFP("99905")); }); + it("should emit SwapUSDForPerps", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + usdToPerpSwapFeePercs: { + lower: percentageFP("0.05"), + upper: percentageFP("0.5"), + }, + }); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapUSDForPerps(usdFP("115"), perpFP("95"))) + .to.emit(billBroker, "SwapUSDForPerps") + .withArgs(usdFP("115"), r); + }); }); describe("stable swap with protocol fees", function () { @@ -697,6 +718,20 @@ describe("BillBroker", function () { expect(r.usdBalance).to.eq(usdFP("115115")); expect(r.perpBalance).to.eq(perpFP("99904.5")); }); + it("should emit SwapUSDForPerps", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + usdToPerpSwapFeePercs: { + lower: percentageFP("0.05"), + upper: percentageFP("0.5"), + }, + protocolSwapSharePerc: percentageFP("0.1"), + }); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapUSDForPerps(usdFP("115"), perpFP("95"))) + .to.emit(billBroker, "SwapUSDForPerps") + .withArgs(usdFP("115"), r); + }); }); describe("when swap amount pushes system outside soft bound", async function () { @@ -750,6 +785,19 @@ describe("BillBroker", function () { expect(r.usdBalance).to.eq(usdFP("118795")); expect(r.perpBalance).to.eq(perpFP("96865")); }); + it("should emit SwapUSDForPerps", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + usdToPerpSwapFeePercs: { + lower: percentageFP("0.05"), + upper: percentageFP("0.5"), + }, + }); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapUSDForPerps(usdFP("3795"), perpFP("3130"))) + .to.emit(billBroker, "SwapUSDForPerps") + .withArgs(usdFP("3795"), r); + }); }); describe("when swap amount pushes system outside hard bound", async function () { @@ -873,6 +921,13 @@ describe("BillBroker", function () { expect(r.usdBalance).to.eq(usdFP("114885")); expect(r.perpBalance).to.eq(perpFP("100100")); }); + it("should emit SwapPerpsForUSD", async function () { + const { billBroker } = await loadFixture(setupContracts); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapPerpsForUSD(perpFP("100"), usdFP("115"))) + .to.emit(billBroker, "SwapPerpsForUSD") + .withArgs(perpFP("100"), r); + }); }); describe("stable swap with fees", function () { @@ -925,6 +980,19 @@ describe("BillBroker", function () { expect(r.usdBalance).to.eq(usdFP("114896.5")); expect(r.perpBalance).to.eq(perpFP("100100")); }); + it("should emit SwapPerpsForUSD", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + perpToUSDSwapFeePercs: { + lower: percentageFP("0.1"), + upper: percentageFP("0.5"), + }, + }); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103"))) + .to.emit(billBroker, "SwapPerpsForUSD") + .withArgs(perpFP("100"), r); + }); }); describe("stable swap with protocol fees", function () { @@ -998,6 +1066,20 @@ describe("BillBroker", function () { expect(r.usdBalance).to.eq(usdFP("114895.35")); expect(r.perpBalance).to.eq(perpFP("100100")); }); + it("should emit SwapPerpsForUSD", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + perpToUSDSwapFeePercs: { + lower: percentageFP("0.1"), + upper: percentageFP("0.5"), + }, + protocolSwapSharePerc: percentageFP("0.1"), + }); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapPerpsForUSD(perpFP("100"), usdFP("103"))) + .to.emit(billBroker, "SwapPerpsForUSD") + .withArgs(perpFP("100"), r); + }); }); describe("when swap pushes system outside soft bound", function () { @@ -1069,6 +1151,20 @@ describe("BillBroker", function () { billBroker.swapPerpsForUSD(perpFP("5000"), usdFP("4000")), ).to.be.revertedWithCustomError(billBroker, "UnacceptableSwap"); }); + it("should emit SwapPerpsForUSD", async function () { + const { billBroker } = await loadFixture(setupContracts); + await updateFees(billBroker, { + perpToUSDSwapFeePercs: { + lower: percentageFP("0.1"), + upper: percentageFP("0.5"), + }, + protocolSwapSharePerc: percentageFP("0.1"), + }); + const r = await billBroker.reserveState.staticCall(); + await expect(billBroker.swapPerpsForUSD(perpFP("5000"), usdFP("4000"))) + .to.emit(billBroker, "SwapPerpsForUSD") + .withArgs(perpFP("5000"), r); + }); }); describe("when the pool has only usd", function () { diff --git a/yarn.lock b/yarn.lock index e5863ebf..50ffa547 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.6 + hardhat: ^2.22.8 hardhat-gas-reporter: latest lodash: ^4.17.21 prettier: ^2.7.1 @@ -1341,10 +1341,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-darwin-arm64@npm:0.4.2": - version: 0.4.2 - resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.4.2" - checksum: 7835e998c2ef83924efac0694bb4392f6abf770dc7f935dd28abc1a291f830cade14750d83a46a3205338e4ddff943dda60a9849317cf42edd38d7a2ce843588 +"@nomicfoundation/edr-darwin-arm64@npm:0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.5.2" + checksum: f6ab386603c6e080fe7f611b739eb6d1d6a370220318b725cb582702563577150b3be14b6d0be71cb72bdb854e6992c587ecfc6833216f750eae8e7cd96de46f languageName: node linkType: hard @@ -1355,10 +1355,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-darwin-x64@npm:0.4.2": - version: 0.4.2 - resolution: "@nomicfoundation/edr-darwin-x64@npm:0.4.2" - checksum: 94daa26610621e85cb025feb37bb93e9b89c59f908bf3eae70720d2b86632dbb1236420ae3ae6f685d563ba52519d5f860e68ccd898fa1fced831961dea2c08a +"@nomicfoundation/edr-darwin-x64@npm:0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr-darwin-x64@npm:0.5.2" + checksum: 6f91f6d0294c0450e0501983f1de34a48582fe93f48428bc4b798ac93bb5483a96d626c2b4c62ac91102f00c826a3f9bfa16d748301440ebe1bbb2920ba3ba6d languageName: node linkType: hard @@ -1369,10 +1369,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-gnu@npm:0.4.2": - version: 0.4.2 - resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.4.2" - checksum: a7181e237f6ece8bd97e0f75972044dbf584c506bbac5bef586d9f7d627a2c07a279a2d892837bbedc80ea3dfb39fa66becc297238b5d715a942eed2a50745cd +"@nomicfoundation/edr-linux-arm64-gnu@npm:0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.5.2" + checksum: bd84cc2741bb2be3c3a60bae9dbb8ca7794a68b8675684c97f2c6e7310e5cba7efd1cf18d392d42481cda83fb478f83c0bd605104c5cf08bab44ec07669c3010 languageName: node linkType: hard @@ -1383,10 +1383,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-musl@npm:0.4.2": - version: 0.4.2 - resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.4.2" - checksum: 5a849484b7a104a7e1497774c4117afc58f64d57d30889d4f6f676dddb5c695192c0789b8be0b71171a2af770167a28aa301ae3ece7a2a156d82d94388639b66 +"@nomicfoundation/edr-linux-arm64-musl@npm:0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.5.2" + checksum: e7f7d82f16be1b26805bd90964c456aecb4a6a1397e87d507810d37bd383064271fa63932564e725fdb30868925334338ec459fe32f84fc11206644b7b37825c languageName: node linkType: hard @@ -1397,10 +1397,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-gnu@npm:0.4.2": - version: 0.4.2 - resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.4.2" - checksum: 0520dd9a583976fd0f49dfe6c23227f03cd811a395dc5eed1a2922b4358d7c71fdcfea8f389d4a0e23b4ec53e1435959a544380f94e48122a75f94a42b177ac7 +"@nomicfoundation/edr-linux-x64-gnu@npm:0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.5.2" + checksum: ec025bf75227638b6b2cd01b7ba01b3ddaddf54c4d18d25e9d0364ac621981be2aaf124f4e60a88da5c9e267adb41a660a42668e2d6c9a6a57e55e8671fc76f1 languageName: node linkType: hard @@ -1411,10 +1411,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-musl@npm:0.4.2": - version: 0.4.2 - resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.4.2" - checksum: 80c3b4346d8c27539bc005b09db233dedd8930310d1a049827661e69a8e03be9cbac27eb620a6ae9bfd46a2fbe22f83cee5af8d9e63178925d74d9c656246708 +"@nomicfoundation/edr-linux-x64-musl@npm:0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.5.2" + checksum: c9ff47f72645492383b2a598675878abc029b86325e2c457db1b2c4281916e11e4ef6336c355d40ae3c1736595bc43da51cfcf1e923464011f526f4db64c245b languageName: node linkType: hard @@ -1425,10 +1425,10 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-win32-x64-msvc@npm:0.4.2": - version: 0.4.2 - resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.4.2" - checksum: 736fb866fd5c2708560cbd5ae72815b5fc96e650cd74bc8bab0a1cb0e8baede4f595fdceb445c159814a6a7e8e691de227a5db49f61b3cd0ddfafd5715b397ab +"@nomicfoundation/edr-win32-x64-msvc@npm:0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.5.2" + checksum: 56da7a1283470dede413cda5f2fef96e10250ec7a25562254ca0cd8045a653212c91e40fbcf37330e7af4e036d3c3aed83ea617831f9c7a5424389c73c53ed4e languageName: node linkType: hard @@ -1447,18 +1447,18 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr@npm:^0.4.1": - version: 0.4.2 - resolution: "@nomicfoundation/edr@npm:0.4.2" +"@nomicfoundation/edr@npm:^0.5.2": + version: 0.5.2 + resolution: "@nomicfoundation/edr@npm:0.5.2" dependencies: - "@nomicfoundation/edr-darwin-arm64": 0.4.2 - "@nomicfoundation/edr-darwin-x64": 0.4.2 - "@nomicfoundation/edr-linux-arm64-gnu": 0.4.2 - "@nomicfoundation/edr-linux-arm64-musl": 0.4.2 - "@nomicfoundation/edr-linux-x64-gnu": 0.4.2 - "@nomicfoundation/edr-linux-x64-musl": 0.4.2 - "@nomicfoundation/edr-win32-x64-msvc": 0.4.2 - checksum: 8c8457257b59ed9a29d88b7492e98e974d24e8318903e876a14dc0f6d5dc77948cd9053937d9730f54f920ba82ce3d244cab518d068359bcc20df88623f171ef + "@nomicfoundation/edr-darwin-arm64": 0.5.2 + "@nomicfoundation/edr-darwin-x64": 0.5.2 + "@nomicfoundation/edr-linux-arm64-gnu": 0.5.2 + "@nomicfoundation/edr-linux-arm64-musl": 0.5.2 + "@nomicfoundation/edr-linux-x64-gnu": 0.5.2 + "@nomicfoundation/edr-linux-x64-musl": 0.5.2 + "@nomicfoundation/edr-win32-x64-msvc": 0.5.2 + checksum: 336b1c7cad96fa78410f0c9cc9524abe9fb1e56384fe990b98bfd17f15f25b4665ad8f0525ccd9511f7c19173841fe712d50db993078629e2fc4047fda4665dc languageName: node linkType: hard @@ -9321,13 +9321,13 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.22.6": - version: 2.22.6 - resolution: "hardhat@npm:2.22.6" +"hardhat@npm:^2.22.8": + version: 2.22.8 + resolution: "hardhat@npm:2.22.8" dependencies: "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 - "@nomicfoundation/edr": ^0.4.1 + "@nomicfoundation/edr": ^0.5.2 "@nomicfoundation/ethereumjs-common": 4.0.4 "@nomicfoundation/ethereumjs-tx": 5.0.4 "@nomicfoundation/ethereumjs-util": 9.0.4 @@ -9378,7 +9378,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 5aec1824db3575d63754de18c2629bcd820bc836d836f8a6346bcd9aa2ae4c397e090c43ea482ee765b704e018001015b5c84c5ded301a6a1144129c1a4c509b + checksum: 3731b510540f800b3b931e3ad7db4510dbd35eff18f34382875778db43de2a5ce1c52596c392aa694324f66392e844fede85954ab3cdc08df3da10f2a810135f languageName: node linkType: hard