diff --git a/contracts/interfaces/IVPoolWrapper.sol b/contracts/interfaces/IVPoolWrapper.sol index b6f93ff6..a4b95629 100644 --- a/contracts/interfaces/IVPoolWrapper.sol +++ b/contracts/interfaces/IVPoolWrapper.sol @@ -66,10 +66,6 @@ interface IVPoolWrapper { /// @param protocolFeePips the new protocol fee ratio event ProtocolFeeUpdated(uint24 protocolFeePips); - /// @notice Emitted when funding rate override is updated - /// @param fundingRateOverrideX128 the new funding rate override value - event FundingRateOverrideUpdated(int256 fundingRateOverrideX128); - function initialize(InitializeVPoolWrapperParams memory params) external; function vPool() external view returns (IUniswapV3Pool); diff --git a/contracts/libraries/FundingRateOverride.sol b/contracts/libraries/FundingRateOverride.sol new file mode 100644 index 00000000..1ab5ec8a --- /dev/null +++ b/contracts/libraries/FundingRateOverride.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +import { AggregatorV3Interface } from '@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol'; + +/// @title Funding Rate Override library +/// @notice There are three modes of operation: +/// 1. NULL mode: No override is set, hence protocol uses mark and index prices. +/// 2. ORACLE mode: An address is set and value of FR is queried from it every time. +/// 3. VALUE mode: A fixed value of FR, which stays in effect until it is changed again. +library FundingRateOverride { + using FundingRateOverride for FundingRateOverride.Info; + + bytes12 constant PREFIX = 'ADDRESS'; // Fits with address in one word. + bytes32 constant NULL_VALUE = bytes32(uint256(type(int256).max)); + + struct Info { + bytes32 data; + } + + error InvalidFundingRateOracle(address oracle); + error InvalidFundingRateValueX128(int256 value); + + /// @notice Emitted when funding rate override is updated + /// @param fundingRateOverrideData the new funding rate override data + event FundingRateOverrideUpdated(bytes32 fundingRateOverrideData); + + /// @notice Updates state to not use any funding rate override. + /// @param info the funding rate override state + function setNull(FundingRateOverride.Info storage info) internal { + info.set(NULL_VALUE); + } + + /// @notice Updates state to use a chainlink oracle for funding rates + /// @dev The oracle must provide hourly funding rates in D8 format + /// @param info the funding rate override state + /// @param oracle the address of the oracle contract + function setOracle(FundingRateOverride.Info storage info, AggregatorV3Interface oracle) internal { + info.set(packOracleAddress(address(oracle))); // reverts if zero address + } + + /// @notice Sets a constant value for funding rate + /// @param info the funding rate override state + /// @param fundingRateOverrideX128 The value of funding rate per sec in X128 format + function setValueX128(FundingRateOverride.Info storage info, int256 fundingRateOverrideX128) internal { + info.set(packInt256(fundingRateOverrideX128)); // reverts if invalid + } + + function set(FundingRateOverride.Info storage info, bytes32 data) internal { + info.data = data; + emit FundingRateOverrideUpdated(data); + } + + /// @notice Get the funding rate override. + /// @param info The info to get the funding rate override. + /// @return success Whether the funding rate override was successfully retrieved. + /// @return fundingRateX128 The funding rate override. + function getValueX128(FundingRateOverride.Info storage info) + internal + view + returns (bool success, int256 fundingRateX128) + { + // NULL mode: if the data is set to NULL value, then no funding rate override + bytes32 data = info.data; + if (data == NULL_VALUE) { + return (false, 0); + } + + // ORACLE mode: if the slot is set to an address, then query override value from the address + address oracle = unpackOracleAddress(data); + if (oracle != address(0)) { + return getValueX128FromOracle(AggregatorV3Interface(oracle)); + } + + // VALUE mode: use the value in the data slot + return (true, unpackInt256(data)); + } + + /// @notice Packs an oracle address into a bytes32 variable. + /// @dev Packed into bytes32 as: . + /// @param oracleAddress The address to pack. + /// @return data The packed address. + function packOracleAddress(address oracleAddress) internal pure returns (bytes32 data) { + if (oracleAddress == address(0)) revert InvalidFundingRateOracle(oracleAddress); + assembly { + data := or(shr(160, PREFIX), shl(96, oracleAddress)) + } + } + + /// @notice Packs the int256 into the data. + /// @param fundingRateOverrideX128 The funding rate override to pack. + /// @return data The funding rate override variable data. + function packInt256(int256 fundingRateOverrideX128) internal pure returns (bytes32 data) { + assembly { + data := fundingRateOverrideX128 + } + // ensure the value being packed does not collide with Address or NULL_VALUE + if (fundingRateOverrideX128 == type(int256).max || unpackOracleAddress(data) != address(0)) { + revert InvalidFundingRateValueX128(fundingRateOverrideX128); + } + } + + /// @notice Unpacks the slot into address. + /// @param data The funding rate override variable. + /// @return oracleAddress The address if it is packed with the PREFIX, else returns address(0). + function unpackOracleAddress(bytes32 data) internal pure returns (address oracleAddress) { + assembly { + if eq(PREFIX, shl(160, data)) { + oracleAddress := shr(96, data) + } + } + } + + /// @notice Unpacks the slot into int256. + /// @dev Does not have sanity checks, null check and unpackOracleAddress should already be tried. + /// @param data The funding rate override variable. + /// @return fundingRateOverrideX128 bytes32 parsed into int256. + function unpackInt256(bytes32 data) internal pure returns (int256 fundingRateOverrideX128) { + assembly { + fundingRateOverrideX128 := data + } + } + + /// @notice Gets the funding rate override from the oracle contract. + /// @param oracle The address of the oracle contract. + /// @return success Whether the funding rate override was successfully retrieved. + /// @return fundingRateX128 The funding rate override. + function getValueX128FromOracle(AggregatorV3Interface oracle) + internal + view + returns (bool success, int256 fundingRateX128) + { + bytes4 selector = oracle.latestRoundData.selector; + assembly { + mstore(0, selector) + // only copy first two words of return data to the scratch space + // gas: pass all available gas + // address: oracle address + // argsOffset: use scratch space's starting + // argsSize: 4 bytes of selector + // retOffset: use scratch space's starting + // retSize: two words, i.e. 64 bytes of return data, rest ignore + success := staticcall(gas(), oracle, 0, 4, 0, 64) + if success { + let fundingRateD8 := mload(32) // we only need second word of return data + fundingRateX128 := sdiv(shl(128, fundingRateD8), 360000000000) // divide by 10**8 and 1 hours + } + } + } +} diff --git a/contracts/libraries/SignedMath.sol b/contracts/libraries/SignedMath.sol index 8c74cfa0..d42ae68d 100644 --- a/contracts/libraries/SignedMath.sol +++ b/contracts/libraries/SignedMath.sol @@ -58,4 +58,17 @@ library SignedMath { if (a > b) c = a; else c = b; } + + /// @notice if a int256 value is outside a range then give it's closest bound + /// @param val int256 value to bound + /// @param absoluteBound absolute cap of the range + function bound(int256 val, uint256 absoluteBound) internal pure returns (int256) { + int256 bound_ = int256(absoluteBound); + if (val > bound_) { + return bound_; + } else if (val < (bound_ = -bound_)) { + return bound_; + } + return val; + } } diff --git a/contracts/protocol/wrapper/VPoolWrapper.sol b/contracts/protocol/wrapper/VPoolWrapper.sol index cb751e97..e7f43727 100644 --- a/contracts/protocol/wrapper/VPoolWrapper.sol +++ b/contracts/protocol/wrapper/VPoolWrapper.sol @@ -4,6 +4,8 @@ pragma solidity =0.8.14; import { Initializable } from '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; +import { AggregatorV3Interface } from '@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol'; + import { IUniswapV3Pool } from '@uniswap/v3-core-0.8-support/contracts/interfaces/IUniswapV3Pool.sol'; import { IUniswapV3MintCallback } from '@uniswap/v3-core-0.8-support/contracts/interfaces/callback/IUniswapV3MintCallback.sol'; import { IUniswapV3SwapCallback } from '@uniswap/v3-core-0.8-support/contracts/interfaces/callback/IUniswapV3SwapCallback.sol'; @@ -20,6 +22,7 @@ import { IClearingHouseStructures } from '../../interfaces/clearinghouse/ICleari import { AddressHelper } from '../../libraries/AddressHelper.sol'; import { FundingPayment } from '../../libraries/FundingPayment.sol'; +import { FundingRateOverride } from '../../libraries/FundingRateOverride.sol'; import { SimulateSwap } from '../../libraries/SimulateSwap.sol'; import { TickExtended } from '../../libraries/TickExtended.sol'; import { PriceMath } from '../../libraries/PriceMath.sol'; @@ -37,6 +40,7 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa using AddressHelper for IVToken; using FullMath for uint256; using FundingPayment for FundingPayment.Info; + using FundingRateOverride for FundingRateOverride.Info; using SignedMath for int256; using SignedFullMath for int256; using PriceMath for uint160; @@ -64,8 +68,7 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa FundingPayment.Info public fpGlobal; uint256 public sumFeeGlobalX128; - int256 constant FUNDING_RATE_OVERRIDE_NULL_VALUE = type(int256).max; - int256 public fundingRateOverrideX128; + FundingRateOverride.Info internal fundingRateOverride; mapping(int24 => TickExtended.Info) public ticksExtended; @@ -128,7 +131,7 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa liquidityFeePips = params.liquidityFeePips; protocolFeePips = params.protocolFeePips; - fundingRateOverrideX128 = type(int256).max; + fundingRateOverride.setNull(); // initializes the funding payment state by zeroing the funding payment for time 0 to blockTimestamp fpGlobal.update({ @@ -152,15 +155,8 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa /// @notice Update the global funding state, from clearing house /// @dev Done when clearing house is paused or unpaused, to prevent funding payments from being received /// or paid when clearing house is in paused mode. - function updateGlobalFundingState(bool useZeroFundingRate) public onlyClearingHouse { - (int256 fundingRateX128, uint256 virtualPriceX128) = getFundingRateAndVirtualPrice(); - fpGlobal.update({ - vTokenAmount: 0, - liquidity: 1, - blockTimestamp: _blockTimestamp(), - virtualPriceX128: virtualPriceX128, - fundingRateX128: useZeroFundingRate ? int256(0) : fundingRateX128 - }); + function updateGlobalFundingState(bool useZeroFundingRate) external onlyClearingHouse { + _updateGlobalFundingState(useZeroFundingRate); } /** @@ -179,15 +175,25 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa emit ProtocolFeeUpdated(protocolFeePips_); } - function setFundingRateOverride(int256 fundingRateOverrideX128_) external onlyGovernanceOrTeamMultisig { - uint256 fundingRateOverrideX128Abs = fundingRateOverrideX128_.absUint(); - // ensure that funding rate magnitude is < 100% APR - if ( - fundingRateOverrideX128_ != FUNDING_RATE_OVERRIDE_NULL_VALUE && - fundingRateOverrideX128Abs > FixedPoint128.Q128 / (365 days) - ) revert InvalidSetting(0x30); - fundingRateOverrideX128 = fundingRateOverrideX128_; - emit FundingRateOverrideUpdated(fundingRateOverrideX128_); + /// @notice Updates state to not use any funding rate override. + function unsetFundingRateOverride() external onlyGovernanceOrTeamMultisig { + _updateGlobalFundingState({ useZeroFundingRate: true }); + fundingRateOverride.setNull(); + } + + /// @notice Updates state to use a chainlink oracle for funding rates + /// @dev The oracle must provide hourly funding rates in D8 format + /// @param chainlinkOracle The address of the chainlink oracle + function setFundingRateOverride(AggregatorV3Interface chainlinkOracle) external onlyGovernanceOrTeamMultisig { + _updateGlobalFundingState({ useZeroFundingRate: true }); + fundingRateOverride.setOracle(chainlinkOracle); + } + + /// @notice Sets a constant value for funding rate + /// @param fundingRateOverrideX128 The value of funding rate per sec in X128 format + function setFundingRateOverride(int256 fundingRateOverrideX128) external onlyGovernanceOrTeamMultisig { + _updateGlobalFundingState({ useZeroFundingRate: true }); + fundingRateOverride.setValueX128(fundingRateOverrideX128); } /** @@ -283,7 +289,7 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa ) { // records the funding payment for last updated timestamp to blockTimestamp using current price difference - _updateGlobalFundingState(); + _updateGlobalFundingState({ useZeroFundingRate: false }); wrapperValuesInside = _updateTicks(tickLower, tickUpper, liquidity.toInt128(), vPool.tickCurrent()); @@ -314,7 +320,7 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa ) { // records the funding payment for last updated timestamp to blockTimestamp using current price difference - _updateGlobalFundingState(); + _updateGlobalFundingState({ useZeroFundingRate: false }); wrapperValuesInside = _updateTicks(tickLower, tickUpper, -liquidity.toInt128(), vPool.tickCurrent()); @@ -361,21 +367,21 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa VIEW METHODS */ - function getFundingRateAndVirtualPrice() public view returns (int256 fundingRateX128, uint256 virtualPriceX128) { - int256 _fundingRateOverrideX128 = fundingRateOverrideX128; - bool shouldUseActualPrices = _fundingRateOverrideX128 == FUNDING_RATE_OVERRIDE_NULL_VALUE; - + function getFundingRateAndVirtualPrice() public view returns (int256, uint256) { uint32 poolId = vToken.truncate(); - virtualPriceX128 = clearingHouse.getVirtualTwapPriceX128(poolId); + uint256 virtualPriceX128 = clearingHouse.getVirtualTwapPriceX128(poolId); - if (shouldUseActualPrices) { + (bool shouldUseOverrides, int256 fundingRateX128) = fundingRateOverride.getValueX128(); + if (!shouldUseOverrides) { // uses actual price to calculate funding rate uint256 realPriceX128 = clearingHouse.getRealTwapPriceX128(poolId); fundingRateX128 = FundingPayment.getFundingRate(realPriceX128, virtualPriceX128); - } else { - // uses funding rate override - fundingRateX128 = _fundingRateOverrideX128; } + + // ensure that abs(funding rate) < 100% APR + fundingRateX128 = fundingRateX128.bound(FixedPoint128.Q128 / (365 days)); + + return (fundingRateX128, virtualPriceX128); } function getSumAX128() external view returns (int256) { @@ -435,6 +441,10 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa ) = ticksExtended.getTickExtendedStateInside(tickLower, tickUpper, currentTick, _fpGlobal, sumFeeGlobalX128); } + function getFundingRateOverride() public view returns (bytes32) { + return fundingRateOverride.data; + } + /** INTERNAL HELPERS */ @@ -529,14 +539,14 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa } /// @notice Update global funding payment, by getting prices from Clearing House - function _updateGlobalFundingState() internal { + function _updateGlobalFundingState(bool useZeroFundingRate) internal { (int256 fundingRateX128, uint256 virtualPriceX128) = getFundingRateAndVirtualPrice(); fpGlobal.update({ vTokenAmount: 0, liquidity: 1, blockTimestamp: _blockTimestamp(), virtualPriceX128: virtualPriceX128, - fundingRateX128: fundingRateX128 + fundingRateX128: useZeroFundingRate ? int256(0) : fundingRateX128 }); } diff --git a/contracts/test/FundingRateOverrideTest.sol b/contracts/test/FundingRateOverrideTest.sol new file mode 100644 index 00000000..2f28ddff --- /dev/null +++ b/contracts/test/FundingRateOverrideTest.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.9; + +import { AggregatorV3Interface } from '@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol'; + +import { FixedPoint128 } from '@uniswap/v3-core-0.8-support/contracts/libraries/FixedPoint128.sol'; + +import { FundingRateOverride } from '../libraries/FundingRateOverride.sol'; +import { SignedFullMath } from '../libraries/SignedFullMath.sol'; + +contract FundingRateOverrideTest { + using FundingRateOverride for FundingRateOverride.Info; + + FundingRateOverride.Info public fundingRateOverride; + + function PREFIX() external pure returns (bytes32) { + return FundingRateOverride.PREFIX; + } + + function NULL_VALUE() external pure returns (bytes32) { + return FundingRateOverride.NULL_VALUE; + } + + function setNull() external { + fundingRateOverride.setNull(); + } + + function setOracle(AggregatorV3Interface oracle) external { + fundingRateOverride.setOracle(oracle); + } + + function setValueX128(int256 fundingRateOverrideX128) external { + fundingRateOverride.setValueX128(fundingRateOverrideX128); + } + + function set(bytes32 data) external { + fundingRateOverride.set(data); + } + + function getValueX128() external view returns (bool success, int256 fundingRateX128) { + return fundingRateOverride.getValueX128(); + } + + function packOracleAddress(address oracleAddress) external pure returns (bytes32 data) { + return FundingRateOverride.packOracleAddress(oracleAddress); + } + + function packInt256(int256 fundingRateOverrideX128) external pure returns (bytes32 data) { + return FundingRateOverride.packInt256(fundingRateOverrideX128); + } + + function unpackOracleAddress(bytes32 data) external pure returns (address oracleAddress) { + return FundingRateOverride.unpackOracleAddress(data); + } + + function unpackInt256(bytes32 data) external pure returns (int256 fundingRateOverrideX128) { + return FundingRateOverride.unpackInt256(data); + } +} diff --git a/contracts/test/SignedMathTest.sol b/contracts/test/SignedMathTest.sol index b34aaf36..44d68cba 100644 --- a/contracts/test/SignedMathTest.sol +++ b/contracts/test/SignedMathTest.sol @@ -26,4 +26,8 @@ contract SignedMathTest { function extractSign(int256 a) external pure returns (uint256 _a, bool) { return SignedMath.extractSign(a); } + + function bound(int256 val, uint256 absoluteCap) external pure returns (int256) { + return SignedMath.bound(val, absoluteCap); + } } diff --git a/package.json b/package.json index aa0556ae..5083006a 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "@nomiclabs/hardhat-waffle": "^2.0.3", "@protodev-rage/hardhat-tenderly": "^1.0.13", "@ragetrade/sdk": "^0.5.10", - "@typechain/ethers-v5": "^10.0.0", - "@typechain/hardhat": "^6.0.0", + "@typechain/ethers-v5": "^10.1.0", + "@typechain/hardhat": "^6.1.2", "@types/chai": "^4.3.0", "@types/fs-extra": "^9.0.13", "@types/mocha": "^9.1.0", @@ -49,17 +49,17 @@ "ethereum-waffle": "^3.3.0", "ethers": "5.6.1", "fs-extra": "^10.1.0", - "hardhat": "^2.9.7", + "hardhat": "^2.10.1", "hardhat-contract-sizer": "^2.5.0", "hardhat-dependency-compiler": "^1.1.2", "hardhat-deploy": "^0.10.5", "hardhat-gas-reporter": "^1.0.8", - "hardhat-tracer": "^1.1.0-rc.3", + "hardhat-tracer": "^1.1.0-rc.6", "patch-package": "^6.4.7", "prettier": "^2.5.1", "solidity-coverage": "^0.7.20", "ts-node": "^10.7.0", - "typechain": "^8.0.0", + "typechain": "^8.1.0", "typescript": "^4.6.3" } } diff --git a/test/scenarios/ClearingHouseExtsload.spec.ts b/test/scenarios/ClearingHouseExtsload.spec.ts index 942b235b..0073e145 100644 --- a/test/scenarios/ClearingHouseExtsload.spec.ts +++ b/test/scenarios/ClearingHouseExtsload.spec.ts @@ -8,9 +8,9 @@ import { ClearingHouseTest, IOracle, IUniswapV3Pool, - IVPoolWrapper, IVToken, SettlementTokenMock, + VPoolWrapper, } from '../../typechain-types'; import { ClearingHouseExtsloadTest } from '../../typechain-types/artifacts/contracts/test/ClearingHouseExtsloadTest'; import { vEthFixture } from '../fixtures/vETH'; @@ -22,7 +22,7 @@ describe('Clearing House Extsload', () => { let oracle: IOracle; let settlementToken: SettlementTokenMock; let vPool: IUniswapV3Pool; - let vPoolWrapper: IVPoolWrapper; + let vPoolWrapper: VPoolWrapper; let vToken: IVToken; let test: ClearingHouseExtsloadTest; diff --git a/test/scenarios/ClearingHouseScenario1.spec.ts b/test/scenarios/ClearingHouseScenario1.spec.ts index 934161df..de81bde5 100644 --- a/test/scenarios/ClearingHouseScenario1.spec.ts +++ b/test/scenarios/ClearingHouseScenario1.spec.ts @@ -411,7 +411,7 @@ describe('Clearing House Scenario 1 (Base swaps and liquidity changes)', () => { } async function checkFundingRateAndTwapPrice(expectedFundingRate: BigNumberish, expectedTwapPrice: BigNumberish) { - const { fundingRateX128, virtualPriceX128 } = await vPoolWrapper.getFundingRateAndVirtualPrice(); + const [fundingRateX128, virtualPriceX128] = await vPoolWrapper.getFundingRateAndVirtualPrice(); expect(fundingRateX128.mul(10n ** 16n).div(1n << 128n)).to.eq(expectedFundingRate); expect(virtualPriceX128.mul(10n ** 18n).div(1n << 128n)).to.eq(expectedTwapPrice); } diff --git a/test/units/AccountRealistic.spec.ts b/test/units/AccountRealistic.spec.ts index 5effebb4..914e8a4b 100644 --- a/test/units/AccountRealistic.spec.ts +++ b/test/units/AccountRealistic.spec.ts @@ -667,8 +667,8 @@ describe('Account Library Test Realistic', () => { const priceCurrentX128 = await priceToNearestPriceX128(price, vQuote, vToken); const notionalAmountClosed = vQuoteAmount.add(vTokenAmount.mul(priceCurrentX128).div(1n << 128n)); - let fee = notionalAmountClosed.mul(liquidationParams.rangeLiquidationFeeFraction).div(1e5); - fee = fee.gt(liquidationParams.maxRangeLiquidationFees) + let fee = notionalAmountClosed.mul(await liquidationParams.rangeLiquidationFeeFraction).div(1e5); + fee = fee.gt(await liquidationParams.maxRangeLiquidationFees) ? BigNumber.from(liquidationParams.maxRangeLiquidationFees) : fee; const feeHalf = fee.div(2); @@ -707,8 +707,8 @@ describe('Account Library Test Realistic', () => { const priceCurrentX128 = await priceToNearestPriceX128(price, vQuote, vToken); const notionalAmountClosed = vQuoteAmount.add(vTokenAmount.mul(priceCurrentX128).div(1n << 128n)); - let fee = notionalAmountClosed.mul(liquidationParams.rangeLiquidationFeeFraction).div(1e5); - fee = fee.gt(liquidationParams.maxRangeLiquidationFees) + let fee = notionalAmountClosed.mul(await liquidationParams.rangeLiquidationFeeFraction).div(1e5); + fee = fee.gt(await liquidationParams.maxRangeLiquidationFees) ? BigNumber.from(liquidationParams.maxRangeLiquidationFees) : fee; const feeHalf = fee.div(2); @@ -780,8 +780,8 @@ describe('Account Library Test Realistic', () => { ); await test.liquidateLiquidityPositions(0); - let liquidationFee = notionalAmountClosed.mul(liquidationParams.rangeLiquidationFeeFraction).div(1e5); - liquidationFee = liquidationFee.gt(liquidationParams.maxRangeLiquidationFees) + let liquidationFee = notionalAmountClosed.mul(await liquidationParams.rangeLiquidationFeeFraction).div(1e5); + liquidationFee = liquidationFee.gt(await liquidationParams.maxRangeLiquidationFees) ? BigNumber.from(liquidationParams.maxRangeLiquidationFees) : liquidationFee; const expectedKeeperFee = liquidationFee @@ -818,8 +818,8 @@ describe('Account Library Test Realistic', () => { await test.liquidateLiquidityPositions(0); - let liquidationFee = notionalAmountClosed.mul(liquidationParams.rangeLiquidationFeeFraction).div(1e5); - liquidationFee = liquidationFee.gt(liquidationParams.maxRangeLiquidationFees) + let liquidationFee = notionalAmountClosed.mul(await liquidationParams.rangeLiquidationFeeFraction).div(1e5); + liquidationFee = liquidationFee.gt(await liquidationParams.maxRangeLiquidationFees) ? BigNumber.from(liquidationParams.maxRangeLiquidationFees) : liquidationFee; const expectedKeeperFee = liquidationFee diff --git a/test/units/FundingRateOverride.spec.ts b/test/units/FundingRateOverride.spec.ts new file mode 100644 index 00000000..3854ba77 --- /dev/null +++ b/test/units/FundingRateOverride.spec.ts @@ -0,0 +1,175 @@ +import { expect } from 'chai'; +import { parseUnits } from 'ethers/lib/utils'; +import hre, { ethers } from 'hardhat'; + +import { smock } from '@defi-wonderland/smock'; +import { BigNumber } from '@ethersproject/bignumber'; +import { bytes32, toQ128 } from '@ragetrade/sdk'; + +import { AggregatorV3Interface, FundingRateOverrideTest } from '../../typechain-types'; + +const PREFIX = '414444524553530000000000'; // "ADDRESS" uint96 + +describe('FundingRateOverride', () => { + let test: FundingRateOverrideTest; + beforeEach(async () => { + test = await (await hre.ethers.getContractFactory('FundingRateOverrideTest')).deploy(); + }); + + describe('#constants', () => { + it('check out prefix', async () => { + const result = await test.PREFIX(); + expect(result).to.equal('0x' + PREFIX + '0'.repeat(64 - PREFIX.length)); + }); + + it('check out null', async () => { + const result = await test.NULL_VALUE(); + expect(result).to.equal(ethers.constants.MaxInt256.toHexString()); + }); + }); + + describe('#packOracleAddress', () => { + it('works', async () => { + const result = await test.packOracleAddress('0x1111111111111111111111111111111111111111'); + expect(result).to.equal(`0x1111111111111111111111111111111111111111${PREFIX}`); + }); + + it('reverts for zero address', async () => { + await expect(test.packOracleAddress('0x0000000000000000000000000000000000000000')).to.be.revertedWith( + 'InvalidFundingRateOracle', + ); + }); + }); + + describe('#packInt256', () => { + it('works for positive numbers', async () => { + const result = await test.packInt256(2); + expect(result).to.equal(bytes32(2)); + }); + + it('works for negative numbers', async () => { + const result = await test.packInt256(-2); + expect(result).to.equal(ethers.utils.defaultAbiCoder.encode(['int256'], [-2])); + }); + + it('reverts if collides with null', async () => { + await expect(test.packInt256(ethers.constants.MaxInt256)).to.be.revertedWith( + `InvalidFundingRateValueX128(${ethers.constants.MaxInt256.toString()})`, + ); + }); + + it('reverts if collides with oracle', async () => { + const packedAddressBytes32 = await test.packOracleAddress('0x1111111111111111111111111111111111111111'); + await expect(test.packInt256(packedAddressBytes32)).to.be.revertedWith( + `InvalidFundingRateValueX128(${BigNumber.from(packedAddressBytes32).toString()})`, + ); + }); + }); + + describe('#unpackOracleAddress', () => { + it('works', async () => { + const packedAddressBytes32 = await test.packOracleAddress('0x1111111111111111111111111111111111111111'); + const result = await test.unpackOracleAddress(packedAddressBytes32); + expect(result).to.equal('0x1111111111111111111111111111111111111111'); + }); + + it('gives zero address if prefix does not match', async () => { + const result = await test.unpackOracleAddress(bytes32(12345678)); + expect(result).to.equal('0x0000000000000000000000000000000000000000'); + }); + }); + + describe('#unpackInt256', () => { + it('works for positive numbers', async () => { + const result = await test.unpackInt256(bytes32(2)); + expect(result).to.equal(2); + }); + + it('works for negative numbers', async () => { + const result = await test.unpackInt256(bytes32(2)); + expect(result).to.equal(2); + }); + }); + + describe('#setNull', () => { + it('works', async () => { + await test.setNull(); + const result = await test.fundingRateOverride(); + expect(result).to.equal(ethers.constants.MaxInt256.toHexString()); + }); + }); + + describe('#setOracle', () => { + it('works', async () => { + await test.setOracle('0x1111111111111111111111111111111111111111'); + const result = await test.fundingRateOverride(); + expect(result).to.equal(`0x1111111111111111111111111111111111111111${PREFIX}`); + }); + + it('reverts if zero address', async () => { + await expect(test.setOracle('0x0000000000000000000000000000000000000000')).to.be.revertedWith( + 'InvalidFundingRateOracle', + ); + }); + }); + + describe('#setValueX128', () => { + it('works', async () => { + const valueX128 = toQ128(0.5); + await test.setValueX128(valueX128); + const result = await test.fundingRateOverride(); + expect(BigNumber.from(result)).to.equal(valueX128); + }); + + it('reverts if collides with null', async () => { + await expect(test.setValueX128(ethers.constants.MaxInt256)).to.be.revertedWith( + `InvalidFundingRateValueX128(${ethers.constants.MaxInt256.toString()})`, + ); + }); + + it('reverts if collides with oracle value', async () => { + const packedAddressBytes32 = await test.packOracleAddress('0x1111111111111111111111111111111111111111'); + await expect(test.setValueX128(packedAddressBytes32)).to.be.revertedWith( + `InvalidFundingRateValueX128(${BigNumber.from(packedAddressBytes32).toString()})`, + ); + }); + }); + + describe('#getValueX128', () => { + it('works in NULL mode', async () => { + await test.setNull(); + const { success, fundingRateX128 } = await test.getValueX128(); + expect(success).to.be.false; + expect(fundingRateX128).to.equal(0); + }); + + it('works in ORACLE mode', async () => { + const chainlinkContract = await smock.fake('AggregatorV3Interface'); + chainlinkContract.latestRoundData.returns([1, parseUnits('0.5', 8), 1, 1, 1]); + await test.setOracle(chainlinkContract.address); + + const { success, fundingRateX128 } = await test.getValueX128(); + expect(success).to.be.true; + expect(fundingRateX128).to.eq(toQ128(0.5).div(3600)); + }); + + it('handles failure in ORACLE mode', async () => { + const chainlinkContract = await smock.fake('AggregatorV3Interface'); + chainlinkContract.latestRoundData.reverts(); + await test.setOracle(chainlinkContract.address); + + const { success, fundingRateX128 } = await test.getValueX128(); + expect(success).to.be.false; + expect(fundingRateX128).to.eq(0); + }); + + it('works in VALUE mode', async () => { + const valueX128 = toQ128(0.25); + await test.setValueX128(valueX128); + + const { success, fundingRateX128 } = await test.getValueX128(); + expect(success).to.be.true; + expect(fundingRateX128).to.eq(valueX128); + }); + }); +}); diff --git a/test/units/SignedMath.spec.ts b/test/units/SignedMath.spec.ts index febf1b6c..ef26d600 100644 --- a/test/units/SignedMath.spec.ts +++ b/test/units/SignedMath.spec.ts @@ -11,94 +11,116 @@ describe('SignedMath', () => { }); describe('#abs', () => { - it('abs(1) = 1', async () => { + it('abs(1) == 1', async () => { expect(await test.abs(1)).to.equal(1); }); - it('abs(-1) = 1', async () => { + it('abs(-1) == 1', async () => { expect(await test.abs(-1)).to.equal(1); }); - it('abs(3) = 3', async () => { + it('abs(3) == 3', async () => { expect(await test.abs(3)).to.equal(3); }); - it('abs(-3) = 3', async () => { + it('abs(-3) == 3', async () => { expect(await test.abs(-3)).to.equal(3); }); - it('abs(0) = 0', async () => { + it('abs(0) == 0', async () => { expect(await test.abs(0)).to.equal(0); }); }); describe('#absUint', () => { - it('absUint(1) = 1', async () => { + it('absUint(1) == 1', async () => { expect(await test.absUint(1)).to.equal(1); }); - it('absUint(-1) = 1', async () => { + it('absUint(-1) == 1', async () => { expect(await test.absUint(-1)).to.equal(1); }); - it('absUint(3) = 3', async () => { + it('absUint(3) == 3', async () => { expect(await test.absUint(3)).to.equal(3); }); - it('absUint(-3) = 3', async () => { + it('absUint(-3) == 3', async () => { expect(await test.absUint(-3)).to.equal(3); }); - it('absUint(0) = 0', async () => { + it('absUint(0) == 0', async () => { expect(await test.absUint(0)).to.equal(0); }); }); describe('#sign', () => { - it('sign(1) = 1', async () => { + it('sign(1) == 1', async () => { expect(await test.sign(1)).to.equal(1); }); - it('sign(-1) = -1', async () => { + it('sign(-1) == -1', async () => { expect(await test.sign(-1)).to.equal(-1); }); - it('sign(3) = 1', async () => { + it('sign(3) == 1', async () => { expect(await test.sign(3)).to.equal(1); }); - it('sign(-3) = -1', async () => { + it('sign(-3) == -1', async () => { expect(await test.sign(-3)).to.equal(-1); }); - it('sign(0) = 1', async () => { + it('sign(0) == 1', async () => { expect(await test.sign(0)).to.equal(1); }); }); describe('#extractSign', () => { - it('extractSign(1) = [1,true]', async () => { + it('extractSign(1) == [1,true]', async () => { const [val, sign] = await test['extractSign(int256)'](1); expect(val).to.equal(1); expect(sign).to.equal(true); }); - it('extractSign(-1) = [1,false]', async () => { + it('extractSign(-1) == [1,false]', async () => { const [val, sign] = await test['extractSign(int256)'](-1); expect(val).to.equal(1); expect(sign).to.equal(false); }); - it('extractSign(3) = [3,true]', async () => { + it('extractSign(3) == [3,true]', async () => { const [val, sign] = await test['extractSign(int256)'](3); expect(val).to.equal(3); expect(sign).to.equal(true); }); - it('extractSign(-3) = [3,false]', async () => { + it('extractSign(-3) == [3,false]', async () => { const [val, sign] = await test['extractSign(int256)'](-3); expect(val).to.equal(3); expect(sign).to.equal(false); }); }); + + describe('#bound', () => { + it('bound(1, 2) == 1', async () => { + const result = await test.bound(1, 2); + expect(result).to.equal(1); + }); + + it('bound(3, 2) == 2', async () => { + const result = await test.bound(3, 2); + expect(result).to.equal(2); + }); + + it('bound(-1, 2) == -1', async () => { + const result = await test.bound(-1, 2); + expect(result).to.equal(-1); + }); + + it('bound(-3, 2) == -2', async () => { + const result = await test.bound(-3, 2); + expect(result).to.equal(-2); + }); + }); }); diff --git a/test/units/StorageLayout.spec.ts b/test/units/StorageLayout.spec.ts index 39d21298..4f91357d 100644 --- a/test/units/StorageLayout.spec.ts +++ b/test/units/StorageLayout.spec.ts @@ -51,7 +51,7 @@ describe('StorageLayout', () => { { label: 'accruedProtocolFee', slot: 4 }, { label: 'fpGlobal', slot: 5 }, { label: 'sumFeeGlobalX128', slot: 9 }, - { label: 'fundingRateOverrideX128', slot: 10 }, + { label: 'fundingRateOverride', slot: 10 }, { label: 'ticksExtended', slot: 11 }, ]); }); diff --git a/test/units/VPoolWrapper.spec.ts b/test/units/VPoolWrapper.spec.ts index d0b5c104..482f7a75 100644 --- a/test/units/VPoolWrapper.spec.ts +++ b/test/units/VPoolWrapper.spec.ts @@ -2,14 +2,22 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; import hre from 'hardhat'; -import { MockContract } from '@defi-wonderland/smock'; +import { MockContract, smock } from '@defi-wonderland/smock'; import { BigNumber, BigNumberish } from '@ethersproject/bignumber'; import { ContractTransaction } from '@ethersproject/contracts'; import { parseUnits } from '@ethersproject/units'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { initializableTick, maxLiquidityForAmounts, priceToTick, Q128, tickToPrice, toQ128 } from '@ragetrade/sdk'; - -import { UniswapV3Pool, VPoolWrapperMock2, VQuote, VToken } from '../../typechain-types'; +import { + bytes32, + initializableTick, + maxLiquidityForAmounts, + priceToTick, + Q128, + tickToPrice, + toQ128, +} from '@ragetrade/sdk'; + +import { AggregatorV3Interface, UniswapV3Pool, VPoolWrapperMock2, VQuote, VToken } from '../../typechain-types'; import { TransferEvent } from '../../typechain-types/artifacts/@openzeppelin/contracts/token/ERC20/IERC20'; import { SwapEvent } from '../../typechain-types/artifacts/contracts/protocol/wrapper/VPoolWrapper'; import { setupWrapper } from '../helpers/setup-wrapper'; @@ -520,25 +528,56 @@ describe('PoolWrapper', () => { }); }); - describe('#fundingRateOverrideX128', () => { - it('should use actual prices in getFundingRate() when fundingRateOverrideX128 is null', async () => { - await vPoolWrapper.setFundingRateOverride(ethers.constants.MaxInt256); + describe('#fundingRateOverride', () => { + before(async () => { + ({ vPoolWrapper, vPool, vQuote, vToken } = await setupWrapper({ + rPriceInitial: 1, + vPriceInitial: 1, + })); + }); + + it('should use actual prices in getFundingRate() when fundingRateOverride is null', async () => { + await vPoolWrapper.unsetFundingRateOverride(); - const { fundingRateX128 } = await vPoolWrapper.getFundingRateAndVirtualPrice(); + const [fundingRateX128] = await vPoolWrapper.getFundingRateAndVirtualPrice(); expect(fundingRateX128).to.eq(0); // because real and virtual twap are equal }); it('should use fundingRateOverrideX128 in getFundingRate() when fundingRateOverrideX128 is not null', async () => { - await vPoolWrapper.setFundingRateOverride(100); + await vPoolWrapper['setFundingRateOverride(int256)'](100); - const { fundingRateX128 } = await vPoolWrapper.getFundingRateAndVirtualPrice(); + const [fundingRateX128] = await vPoolWrapper.getFundingRateAndVirtualPrice(); expect(fundingRateX128).to.eq(100); // since fundingRateOverrideX128 != MaxInt256 }); - it('should not allow fundingRateOverrideX128 to be updated if it is more than 100% per hour', async () => { - await expect(vPoolWrapper.setFundingRateOverride(ethers.constants.MaxInt256.sub(1))).to.revertedWith( - 'InvalidSetting(48)', - ); + it('should use bound funding rate fundingRateOverrideX128 to be updated if it is more than 100% annually', async () => { + // setting FR to a huge value + await vPoolWrapper['setFundingRateOverride(int256)'](toQ128(1000)); + + const [fundingRateX128] = await vPoolWrapper.getFundingRateAndVirtualPrice(); + expect(fundingRateX128).to.eq(toQ128(1).div(365 * 24 * 3600)); + }); + + it('should use funding rate from oracle', async () => { + const chainlinkContract = await smock.fake('AggregatorV3Interface'); + const hourlyFR = parseUnits('0.01', 8).div(100); // 0.24% per day, 87% per year + chainlinkContract.latestRoundData.returns([1, hourlyFR, 1, 1, 1]); + + await vPoolWrapper['setFundingRateOverride(address)'](chainlinkContract.address); + + const [fundingRateX128] = await vPoolWrapper.getFundingRateAndVirtualPrice(); + expect(fundingRateX128).to.eq(hourlyFR.shl(128).div(3600e8)); + }); + + it('should use funding rate from oracle bounded by 100% annualized', async () => { + const chainlinkContract = await smock.fake('AggregatorV3Interface'); + const hourlyFR = parseUnits('0.02', 8).div(100); // 0.48% per day, 175% per year + chainlinkContract.latestRoundData.returns([1, hourlyFR, 1, 1, 1]); + + await vPoolWrapper['setFundingRateOverride(address)'](chainlinkContract.address); + + const [fundingRateX128] = await vPoolWrapper.getFundingRateAndVirtualPrice(); + expect(fundingRateX128).to.eq(toQ128(1).div(365 * 24 * 3600)); }); }); @@ -560,8 +599,9 @@ describe('PoolWrapper', () => { }); it('fundingRateOverrideX128', async () => { - await vPoolWrapper.connect(owner).setFundingRateOverride(124); - expect(await vPoolWrapper.fundingRateOverrideX128()).to.eq(124); + await vPoolWrapper.connect(owner)['setFundingRateOverride(int256)'](124); + const fundingRateOverrideData = await vPoolWrapper.getFundingRateOverride(); + expect(fundingRateOverrideData).to.eq(bytes32(124)); }); it('setLiquidityFee owner check', async () => { @@ -572,8 +612,14 @@ describe('PoolWrapper', () => { await expect(vPoolWrapper.connect(stranger).setProtocolFee(123)).to.be.revertedWith('NotGovernance()'); }); - it('setFundingRateOverride owner check', async () => { - await expect(vPoolWrapper.connect(stranger).setFundingRateOverride(123)).to.be.revertedWith( + it('setFundingRateOverride(address) owner check', async () => { + await expect( + vPoolWrapper.connect(stranger)['setFundingRateOverride(address)'](stranger.address), + ).to.be.revertedWith('NotGovernanceOrTeamMultisig()'); + }); + + it('setFundingRateOverride(int256) owner check', async () => { + await expect(vPoolWrapper.connect(stranger)['setFundingRateOverride(int256)'](123)).to.be.revertedWith( 'NotGovernanceOrTeamMultisig()', ); }); diff --git a/yarn.lock b/yarn.lock index 238fca1a..a949add8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1407,6 +1407,16 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" +"@noble/hashes@1.1.2", "@noble/hashes@~1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" + integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== + +"@noble/secp256k1@1.6.3", "@noble/secp256k1@~1.6.0": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.6.3.tgz#7eed12d9f4404b416999d0c87686836c4c5c9b94" + integrity sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1545,6 +1555,28 @@ path-browserify "^1.0.0" url "^0.11.0" +"@scure/base@~1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@scure/bip32@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.0.tgz#dea45875e7fbc720c2b4560325f1cf5d2246d95b" + integrity sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q== + dependencies: + "@noble/hashes" "~1.1.1" + "@noble/secp256k1" "~1.6.0" + "@scure/base" "~1.1.0" + +"@scure/bip39@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.0.tgz#92f11d095bae025f166bef3defcc5bf4945d419a" + integrity sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w== + dependencies: + "@noble/hashes" "~1.1.1" + "@scure/base" "~1.1.0" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -1639,10 +1671,10 @@ dependencies: antlr4ts "^0.5.0-alpha.4" -"@solidity-parser/parser@^0.14.1": - version "0.14.1" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.1.tgz#179afb29f4e295a77cc141151f26b3848abc3c46" - integrity sha512-eLjj2L6AuQjBB6s/ibwCAc0DwrR5Ge+ys+wgWo+bviU7fV2nTMQhU63CGaDKXg9iTmMxwhkyoggdIR7ZGRfMgw== +"@solidity-parser/parser@^0.14.2": + version "0.14.3" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.3.tgz#0d627427b35a40d8521aaa933cc3df7d07bfa36f" + integrity sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw== dependencies: antlr4ts "^0.5.0-alpha.4" @@ -1696,10 +1728,10 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== -"@typechain/ethers-v5@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.0.0.tgz#1b6e292d2ed9afb0d2f7a4674cc199bb95bad714" - integrity sha512-Kot7fwAqnH96ZbI8xrRgj5Kpv9yCEdjo7mxRqrH7bYpEgijT5MmuOo8IVsdhOu7Uog4ONg7k/d5UdbAtTKUgsA== +"@typechain/ethers-v5@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.1.0.tgz#068d7dc7014502354696dab59590a7841091e951" + integrity sha512-3LIb+eUpV3mNCrjUKT5oqp8PBsZYSnVrkfk6pY/ZM0boRs2mKxjFZ7bktx42vfDye8PPz3NxtW4DL5NsNsFqlg== dependencies: lodash "^4.17.15" ts-essentials "^7.0.1" @@ -1711,10 +1743,10 @@ dependencies: ethers "^5.0.2" -"@typechain/hardhat@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.0.0.tgz#5e305641de67276efbfaa8c37c78e38f22b22ef4" - integrity sha512-AnhwODKHxx3+st5uc1j2NQh79Lv2OuvDQe4dKn8ZxhqYsAsTPnHTLBeI8KPZ+mfdE7v13D2QYssRTIkkGhK35A== +"@typechain/hardhat@^6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.2.tgz#d3beccc6937d93f9b437616b741f839a8b953693" + integrity sha512-k4Ea3pVITKB2DH8p1a5U38cyy7KZPD04Spo4q5b4wO+n2mT+uAz5dxckPtbczn/Kk5wiFq+ZkuOtw5ZKFhL/+w== dependencies: fs-extra "^9.1.0" lodash "^4.17.15" @@ -3078,6 +3110,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -3867,10 +3906,10 @@ debug@4, debug@^4.1.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@4.3.3, debug@^4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@4.3.4, debug@^4.3.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -3881,10 +3920,10 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.3.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -4521,7 +4560,7 @@ ethereum-common@^0.0.18: resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8= -ethereum-cryptography@^0.1.2, ethereum-cryptography@^0.1.3: +ethereum-cryptography@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== @@ -4542,6 +4581,16 @@ ethereum-cryptography@^0.1.2, ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" +ethereum-cryptography@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz#74f2ac0f0f5fe79f012c889b3b8446a9a6264e6d" + integrity sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ== + dependencies: + "@noble/hashes" "1.1.2" + "@noble/secp256k1" "1.6.3" + "@scure/bip32" "1.1.0" + "@scure/bip39" "1.1.0" + ethereum-waffle@^3.3.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ethereum-waffle/-/ethereum-waffle-3.4.0.tgz#990b3c6c26db9c2dd943bf26750a496f60c04720" @@ -5839,10 +5888,10 @@ hardhat-gas-reporter@^1.0.8: eth-gas-reporter "^0.2.24" sha1 "^1.1.1" -hardhat-tracer@^1.1.0-rc.3: - version "1.1.0-rc.3" - resolved "https://registry.yarnpkg.com/hardhat-tracer/-/hardhat-tracer-1.1.0-rc.3.tgz#b28fb471a240f57fdbdb91206a976ad5759ca249" - integrity sha512-UGOfwRXkdxWM66JqcyGDJfIjiDXNkqgiaomLPvILg3nppVTjxtBmddYtnvGhDrV9FE1NgWyBnP1B35NaRIKM8A== +hardhat-tracer@^1.1.0-rc.6: + version "1.1.0-rc.6" + resolved "https://registry.yarnpkg.com/hardhat-tracer/-/hardhat-tracer-1.1.0-rc.6.tgz#963f9058a2e1ca7f1dac19d8b00ab2c2e556a1f4" + integrity sha512-u1d8YpyYBCj/7xVMPDxsx+H1gBaothk/XNLeTYuEmxC6WmVMEwVjpdnmTYZiRQ2ntUfwSIjwKhDkLOqewBqaQA== dependencies: ethers "^5.6.1" @@ -5853,10 +5902,10 @@ hardhat-watcher@^2.1.1: dependencies: chokidar "^3.4.3" -hardhat@^2.9.7: - version "2.9.7" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.9.7.tgz#b31302b089486ec1c13c5a3dff18fe71f955f33b" - integrity sha512-PVSgTlM4Mtc4HNEoISpcM6rRNAK3ngqhxUaTmSw9eCtuVmtxTK86Tqnuq4zNPmlrtcuReXry9k3LGEnk2gJgbA== +hardhat@^2.10.1: + version "2.10.1" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.10.1.tgz#37fdc0c96d6a5d16b322269db2ad8f9f115c4046" + integrity sha512-0FN9TyCtn7Lt25SB2ei2G7nA2rZjP+RN6MvFOm+zYwherxLZNo6RbD8nDz88eCbhRapevmXqOiL2nM8INKsjmA== dependencies: "@ethereumjs/block" "^3.6.2" "@ethereumjs/blockchain" "^5.5.2" @@ -5866,7 +5915,7 @@ hardhat@^2.9.7: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" "@sentry/node" "^5.18.1" - "@solidity-parser/parser" "^0.14.1" + "@solidity-parser/parser" "^0.14.2" "@types/bn.js" "^5.1.0" "@types/lru-cache" "^5.1.0" abort-controller "^3.0.0" @@ -5879,7 +5928,7 @@ hardhat@^2.9.7: debug "^4.1.1" enquirer "^2.3.0" env-paths "^2.2.0" - ethereum-cryptography "^0.1.2" + ethereum-cryptography "^1.0.3" ethereumjs-abi "^0.6.8" ethereumjs-util "^7.1.4" find-up "^2.1.0" @@ -5891,7 +5940,7 @@ hardhat@^2.9.7: lodash "^4.17.11" merkle-patricia-tree "^4.2.4" mnemonist "^0.38.0" - mocha "^9.2.0" + mocha "^10.0.0" p-map "^4.0.0" qs "^6.7.0" raw-body "^2.4.1" @@ -5903,7 +5952,7 @@ hardhat@^2.9.7: stacktrace-parser "^0.1.10" "true-case-path" "^2.2.1" tsort "0.0.1" - undici "^4.14.1" + undici "^5.4.0" uuid "^8.3.2" ws "^7.4.6" @@ -7439,6 +7488,13 @@ minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -7503,6 +7559,34 @@ mnemonist@^0.38.0: dependencies: obliterator "^1.6.1" +mocha@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + mocha@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" @@ -7533,36 +7617,6 @@ mocha@^7.1.1: yargs-parser "13.1.2" yargs-unparser "1.6.0" -mocha@^9.2.0: - version "9.2.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.1.tgz#a1abb675aa9a8490798503af57e8782a78f1338e" - integrity sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.3" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - growl "1.10.5" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "3.0.4" - ms "2.1.3" - nanoid "3.2.0" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - which "2.0.2" - workerpool "6.2.0" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - mock-fs@^4.1.0: version "4.14.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" @@ -7647,10 +7701,10 @@ nano-json-stream-parser@^0.1.2: resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" integrity sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18= -nanoid@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" - integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== nanomatch@^1.2.9: version "1.2.13" @@ -9995,10 +10049,10 @@ typechain@^3.0.0: ts-essentials "^6.0.3" ts-generator "^0.1.1" -typechain@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.0.0.tgz#a5dbe754717a7e16247df52b5285903de600e8ff" - integrity sha512-rqDfDYc9voVAhmfVfAwzg3VYFvhvs5ck1X9T/iWkX745Cul4t+V/smjnyqrbDzWDbzD93xfld1epg7Y/uFAesQ== +typechain@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.1.0.tgz#fc4902ce596519cb2ccfd012e4ddf92a9945b569" + integrity sha512-5jToLgKTjHdI1VKqs/K8BLYy42Sr3o8bV5ojh4MnR9ExHO83cyyUdw+7+vMJCpKXUiVUvARM4qmHTFuyaCMAZQ== dependencies: "@types/prettier" "^2.1.1" debug "^4.3.1" @@ -10090,6 +10144,11 @@ undici@^4.14.1: resolved "https://registry.yarnpkg.com/undici/-/undici-4.15.0.tgz#507ec94bce46bec5c76e934938c50b825eda8258" integrity sha512-kHppwh/y49FLEXl/zYCCbGB0D3nrcWNBczNYCsDdNYzWPs80aQgfKic1PVkJEIc2YlR7m0Lf5i559zbr0AA7FQ== +undici@^5.4.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.8.0.tgz#dec9a8ccd90e5a1d81d43c0eab6503146d649a4f" + integrity sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -10889,13 +10948,6 @@ which@1.3.1, which@^1.1.1, which@^1.2.9, which@^1.3.1: dependencies: isexe "^2.0.0" -which@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - wide-align@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -10933,10 +10985,10 @@ wordwrapjs@^4.0.0: reduce-flatten "^2.0.0" typical "^5.2.0" -workerpool@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" - integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^2.0.0: version "2.1.0"