From f853c806d3d09a707f0ff12436c400de406ef117 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Thu, 28 Jul 2022 15:33:40 +0530 Subject: [PATCH 01/13] feat: draft funding rate override for oracle --- contracts/interfaces/IVPoolWrapper.sol | 4 - contracts/libraries/FundingRateOverride.sol | 115 ++++++++++++++++++++ contracts/protocol/wrapper/VPoolWrapper.sol | 43 ++++---- 3 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 contracts/libraries/FundingRateOverride.sol 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..58e7736e --- /dev/null +++ b/contracts/libraries/FundingRateOverride.sol @@ -0,0 +1,115 @@ +// 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); + + function setNull(FundingRateOverride.Info storage info) internal { + info.set(NULL_VALUE); + } + + function setOracle(FundingRateOverride.Info storage info, AggregatorV3Interface oracle) internal { + info.set(packOracleAddress(address(oracle))); // reverts if zero address + } + + 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 fundingRateOverrideX128 The funding rate override. + function getValueX128(FundingRateOverride.Info storage info) + internal + view + returns (bool success, int256 fundingRateOverrideX128) + { + // 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)) { + (, int256 fundingRateD8, , , ) = AggregatorV3Interface(oracle).latestRoundData(); + return (true, (fundingRateD8 << 128) / 10**8); + } + + // 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) private pure returns (bytes32 data) { + if (oracleAddress == address(0)) revert InvalidFundingRateOracle(oracleAddress); + assembly { + data := or(shr(PREFIX, 160), shl(oracleAddress, 96)) + } + } + + /// @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) private pure returns (bytes32 data) { + assembly { + fundingRateOverrideX128 := data + } + // 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) private pure returns (address oracleAddress) { + assembly { + if eq(PREFIX, shl(160, data)) { + oracleAddress := shr(96, data) + } + } + } + + /// @notice Unpacks the slot into int256. + /// @param data The funding rate override variable. + /// @return fundingRateOverrideX128 bytes32 parsed into int256. + function unpackInt256(bytes32 data) private pure returns (int256 fundingRateOverrideX128) { + assembly { + fundingRateOverrideX128 := data + } + } +} diff --git a/contracts/protocol/wrapper/VPoolWrapper.sol b/contracts/protocol/wrapper/VPoolWrapper.sol index cb751e97..3e00a2e1 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 public 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({ @@ -179,15 +182,16 @@ 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_); + function unsetFundingRateOverride() external onlyGovernanceOrTeamMultisig { + fundingRateOverride.setNull(); + } + + function setFundingRateOverride(AggregatorV3Interface oracle) external onlyGovernanceOrTeamMultisig { + fundingRateOverride.setOracle(oracle); + } + + function setFundingRateOverride(int256 fundingRateOverrideX128) external onlyGovernanceOrTeamMultisig { + fundingRateOverride.setValueX128(fundingRateOverrideX128); } /** @@ -361,21 +365,18 @@ 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; } + + return (fundingRateX128, virtualPriceX128); } function getSumAX128() external view returns (int256) { From f5675b20e4b4cef4ad559067d08908b1280931a6 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Fri, 29 Jul 2022 11:50:10 +0530 Subject: [PATCH 02/13] feat: add bound util to signed math lib --- contracts/libraries/SignedMath.sol | 13 +++++++ contracts/test/SignedMathTest.sol | 4 ++ test/units/SignedMath.spec.ts | 60 ++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 19 deletions(-) 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/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/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); + }); + }); }); From 665d2067ceb466327da262c2d8fcdd1ff84a6d5f Mon Sep 17 00:00:00 2001 From: LeadDev Date: Fri, 29 Jul 2022 16:04:16 +0530 Subject: [PATCH 03/13] chore: update dependencies --- package.json | 10 +- test/units/AccountRealistic.spec.ts | 16 +-- yarn.lock | 216 +++++++++++++++++----------- 3 files changed, 147 insertions(+), 95 deletions(-) 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/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/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" From 24fcb10cfcb1feca72e288f0c5205dec1c0524b9 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Fri, 29 Jul 2022 20:10:39 +0530 Subject: [PATCH 04/13] test: funding rate override lib --- contracts/libraries/FundingRateOverride.sol | 26 ++- contracts/test/FundingRateOverrideTest.sol | 60 +++++++ test/units/FundingRateOverride.spec.ts | 175 ++++++++++++++++++++ 3 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 contracts/test/FundingRateOverrideTest.sol create mode 100644 test/units/FundingRateOverride.spec.ts diff --git a/contracts/libraries/FundingRateOverride.sol b/contracts/libraries/FundingRateOverride.sol index 58e7736e..c4eb028b 100644 --- a/contracts/libraries/FundingRateOverride.sol +++ b/contracts/libraries/FundingRateOverride.sol @@ -61,8 +61,17 @@ library FundingRateOverride { // 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)) { - (, int256 fundingRateD8, , , ) = AggregatorV3Interface(oracle).latestRoundData(); - return (true, (fundingRateD8 << 128) / 10**8); + try AggregatorV3Interface(oracle).latestRoundData() returns ( + uint80, + int256 fundingRateD8, + uint256, + uint256, + uint80 + ) { + return (true, (fundingRateD8 << 128) / 10**8); + } catch { + return (false, 0); + } } // VALUE mode: use the value in the data slot @@ -73,19 +82,19 @@ library FundingRateOverride { /// @dev Packed into bytes32 as: . /// @param oracleAddress The address to pack. /// @return data The packed address. - function packOracleAddress(address oracleAddress) private pure returns (bytes32 data) { + function packOracleAddress(address oracleAddress) internal pure returns (bytes32 data) { if (oracleAddress == address(0)) revert InvalidFundingRateOracle(oracleAddress); assembly { - data := or(shr(PREFIX, 160), shl(oracleAddress, 96)) + 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) private pure returns (bytes32 data) { + function packInt256(int256 fundingRateOverrideX128) internal pure returns (bytes32 data) { assembly { - fundingRateOverrideX128 := data + data := fundingRateOverrideX128 } // ensure the value being packed does not collide with Address or NULL_VALUE if (fundingRateOverrideX128 == type(int256).max || unpackOracleAddress(data) != address(0)) { @@ -96,7 +105,7 @@ library FundingRateOverride { /// @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) private pure returns (address oracleAddress) { + function unpackOracleAddress(bytes32 data) internal pure returns (address oracleAddress) { assembly { if eq(PREFIX, shl(160, data)) { oracleAddress := shr(96, data) @@ -105,9 +114,10 @@ library FundingRateOverride { } /// @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) private pure returns (int256 fundingRateOverrideX128) { + function unpackInt256(bytes32 data) internal pure returns (int256 fundingRateOverrideX128) { assembly { fundingRateOverrideX128 := data } diff --git a/contracts/test/FundingRateOverrideTest.sol b/contracts/test/FundingRateOverrideTest.sol new file mode 100644 index 00000000..d8124688 --- /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 fundingRateOverrideX128) { + 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/test/units/FundingRateOverride.spec.ts b/test/units/FundingRateOverride.spec.ts new file mode 100644 index 00000000..8bc2c0b1 --- /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, fundingRateOverrideX128 } = await test.getValueX128(); + expect(success).to.be.false; + expect(fundingRateOverrideX128).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, fundingRateOverrideX128 } = await test.getValueX128(); + expect(success).to.be.true; + expect(fundingRateOverrideX128).to.eq(toQ128(0.5)); + }); + + it('handles failure in ORACLE mode', async () => { + const chainlinkContract = await smock.fake('AggregatorV3Interface'); + chainlinkContract.latestRoundData.reverts(); + await test.setOracle(chainlinkContract.address); + + const { success, fundingRateOverrideX128 } = await test.getValueX128(); + expect(success).to.be.false; + expect(fundingRateOverrideX128).to.eq(0); + }); + + it('works in VALUE mode', async () => { + const valueX128 = toQ128(0.25); + await test.setValueX128(valueX128); + + const { success, fundingRateOverrideX128 } = await test.getValueX128(); + expect(success).to.be.true; + expect(fundingRateOverrideX128).to.eq(valueX128); + }); + }); +}); From 4b1a379a83535a6926ebe9c31580683baba7d3b1 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Fri, 29 Jul 2022 22:01:42 +0530 Subject: [PATCH 05/13] fix: use funding rate per sec in override --- contracts/libraries/FundingRateOverride.sol | 16 +++++++++++++--- contracts/test/FundingRateOverrideTest.sol | 2 +- test/units/FundingRateOverride.spec.ts | 16 ++++++++-------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/contracts/libraries/FundingRateOverride.sol b/contracts/libraries/FundingRateOverride.sol index c4eb028b..d61a93fc 100644 --- a/contracts/libraries/FundingRateOverride.sol +++ b/contracts/libraries/FundingRateOverride.sol @@ -26,14 +26,23 @@ library FundingRateOverride { /// @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 } @@ -46,11 +55,11 @@ library FundingRateOverride { /// @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 fundingRateOverrideX128 The funding rate override. + /// @return fundingRateX128 The funding rate override. function getValueX128(FundingRateOverride.Info storage info) internal view - returns (bool success, int256 fundingRateOverrideX128) + returns (bool success, int256 fundingRateX128) { // NULL mode: if the data is set to NULL value, then no funding rate override bytes32 data = info.data; @@ -68,7 +77,8 @@ library FundingRateOverride { uint256, uint80 ) { - return (true, (fundingRateD8 << 128) / 10**8); + // the oracle gives hourly funding rates in D8, we need to convert to X128 per secs + return (true, (fundingRateD8 << 128) / 1 hours / 10**8); } catch { return (false, 0); } diff --git a/contracts/test/FundingRateOverrideTest.sol b/contracts/test/FundingRateOverrideTest.sol index d8124688..2f28ddff 100644 --- a/contracts/test/FundingRateOverrideTest.sol +++ b/contracts/test/FundingRateOverrideTest.sol @@ -38,7 +38,7 @@ contract FundingRateOverrideTest { fundingRateOverride.set(data); } - function getValueX128() external view returns (bool success, int256 fundingRateOverrideX128) { + function getValueX128() external view returns (bool success, int256 fundingRateX128) { return fundingRateOverride.getValueX128(); } diff --git a/test/units/FundingRateOverride.spec.ts b/test/units/FundingRateOverride.spec.ts index 8bc2c0b1..3854ba77 100644 --- a/test/units/FundingRateOverride.spec.ts +++ b/test/units/FundingRateOverride.spec.ts @@ -138,9 +138,9 @@ describe('FundingRateOverride', () => { describe('#getValueX128', () => { it('works in NULL mode', async () => { await test.setNull(); - const { success, fundingRateOverrideX128 } = await test.getValueX128(); + const { success, fundingRateX128 } = await test.getValueX128(); expect(success).to.be.false; - expect(fundingRateOverrideX128).to.equal(0); + expect(fundingRateX128).to.equal(0); }); it('works in ORACLE mode', async () => { @@ -148,9 +148,9 @@ describe('FundingRateOverride', () => { chainlinkContract.latestRoundData.returns([1, parseUnits('0.5', 8), 1, 1, 1]); await test.setOracle(chainlinkContract.address); - const { success, fundingRateOverrideX128 } = await test.getValueX128(); + const { success, fundingRateX128 } = await test.getValueX128(); expect(success).to.be.true; - expect(fundingRateOverrideX128).to.eq(toQ128(0.5)); + expect(fundingRateX128).to.eq(toQ128(0.5).div(3600)); }); it('handles failure in ORACLE mode', async () => { @@ -158,18 +158,18 @@ describe('FundingRateOverride', () => { chainlinkContract.latestRoundData.reverts(); await test.setOracle(chainlinkContract.address); - const { success, fundingRateOverrideX128 } = await test.getValueX128(); + const { success, fundingRateX128 } = await test.getValueX128(); expect(success).to.be.false; - expect(fundingRateOverrideX128).to.eq(0); + expect(fundingRateX128).to.eq(0); }); it('works in VALUE mode', async () => { const valueX128 = toQ128(0.25); await test.setValueX128(valueX128); - const { success, fundingRateOverrideX128 } = await test.getValueX128(); + const { success, fundingRateX128 } = await test.getValueX128(); expect(success).to.be.true; - expect(fundingRateOverrideX128).to.eq(valueX128); + expect(fundingRateX128).to.eq(valueX128); }); }); }); From 48c0ae0291e3e210729c190b27e968eade65fc55 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Fri, 29 Jul 2022 22:41:00 +0530 Subject: [PATCH 06/13] test: vPoolWrapper and add comments --- contracts/libraries/FundingRateOverride.sol | 2 +- contracts/protocol/wrapper/VPoolWrapper.sol | 10 +++- test/units/VPoolWrapper.spec.ts | 63 +++++++++++++++------ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/contracts/libraries/FundingRateOverride.sol b/contracts/libraries/FundingRateOverride.sol index d61a93fc..9fd5b2f2 100644 --- a/contracts/libraries/FundingRateOverride.sol +++ b/contracts/libraries/FundingRateOverride.sol @@ -78,7 +78,7 @@ library FundingRateOverride { uint80 ) { // the oracle gives hourly funding rates in D8, we need to convert to X128 per secs - return (true, (fundingRateD8 << 128) / 1 hours / 10**8); + return (true, (fundingRateD8 << 128) / 3600e8); // divide by 10**8 and 1 hours } catch { return (false, 0); } diff --git a/contracts/protocol/wrapper/VPoolWrapper.sol b/contracts/protocol/wrapper/VPoolWrapper.sol index 3e00a2e1..494296f9 100644 --- a/contracts/protocol/wrapper/VPoolWrapper.sol +++ b/contracts/protocol/wrapper/VPoolWrapper.sol @@ -182,14 +182,20 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa emit ProtocolFeeUpdated(protocolFeePips_); } + /// @notice Updates state to not use any funding rate override. function unsetFundingRateOverride() external onlyGovernanceOrTeamMultisig { fundingRateOverride.setNull(); } - function setFundingRateOverride(AggregatorV3Interface oracle) external onlyGovernanceOrTeamMultisig { - fundingRateOverride.setOracle(oracle); + /// @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 { + 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 { fundingRateOverride.setValueX128(fundingRateOverrideX128); } diff --git a/test/units/VPoolWrapper.spec.ts b/test/units/VPoolWrapper.spec.ts index d0b5c104..7e3b7544 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,37 @@ 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, + })); + }); - const { fundingRateX128 } = await vPoolWrapper.getFundingRateAndVirtualPrice(); + it('should use actual prices in getFundingRate() when fundingRateOverride is null', async () => { + await vPoolWrapper.unsetFundingRateOverride(); + + 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 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)); }); }); @@ -560,8 +580,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.fundingRateOverride(); + expect(fundingRateOverrideData).to.eq(bytes32(124)); }); it('setLiquidityFee owner check', async () => { @@ -572,8 +593,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()', ); }); From 8e599cc8e3f14f86dbb26bbfaec6993eb5a99c29 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Fri, 29 Jul 2022 23:20:04 +0530 Subject: [PATCH 07/13] feat: add funding rate bound --- contracts/protocol/wrapper/VPoolWrapper.sol | 3 +++ test/units/VPoolWrapper.spec.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/contracts/protocol/wrapper/VPoolWrapper.sol b/contracts/protocol/wrapper/VPoolWrapper.sol index 494296f9..b7b3b837 100644 --- a/contracts/protocol/wrapper/VPoolWrapper.sol +++ b/contracts/protocol/wrapper/VPoolWrapper.sol @@ -382,6 +382,9 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa fundingRateX128 = FundingPayment.getFundingRate(realPriceX128, virtualPriceX128); } + // ensure that abs(funding rate) < 100% APR + fundingRateX128 = fundingRateX128.bound(FixedPoint128.Q128 / (365 days)); + return (fundingRateX128, virtualPriceX128); } diff --git a/test/units/VPoolWrapper.spec.ts b/test/units/VPoolWrapper.spec.ts index 7e3b7544..608e5049 100644 --- a/test/units/VPoolWrapper.spec.ts +++ b/test/units/VPoolWrapper.spec.ts @@ -550,6 +550,14 @@ describe('PoolWrapper', () => { expect(fundingRateX128).to.eq(100); // since fundingRateOverrideX128 != MaxInt256 }); + 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 @@ -560,6 +568,17 @@ describe('PoolWrapper', () => { 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)); + }); }); describe('#admin', () => { From 7ba46ac61ba7054ba70c4b707e40a3d8564c8229 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Sat, 30 Jul 2022 00:05:52 +0530 Subject: [PATCH 08/13] test: change storage var name --- test/units/StorageLayout.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }, ]); }); From 84d689f332b68170331adfa390d6aaf789855336 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Sat, 30 Jul 2022 00:07:10 +0530 Subject: [PATCH 09/13] test: fix typescript errors --- test/scenarios/ClearingHouseExtsload.spec.ts | 4 ++-- test/scenarios/ClearingHouseScenario1.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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); } From c34443bbbed5380bc532c9bdd3bbfc9c269296a4 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Mon, 1 Aug 2022 17:48:45 +0530 Subject: [PATCH 10/13] ref: updateGlobalFundingState method --- contracts/protocol/wrapper/VPoolWrapper.sol | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/contracts/protocol/wrapper/VPoolWrapper.sol b/contracts/protocol/wrapper/VPoolWrapper.sol index b7b3b837..f345ab45 100644 --- a/contracts/protocol/wrapper/VPoolWrapper.sol +++ b/contracts/protocol/wrapper/VPoolWrapper.sol @@ -155,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); } /** @@ -293,7 +286,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()); @@ -324,7 +317,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()); @@ -539,14 +532,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 }); } From eb5f84bbfd3bd6bcb623b0b5ad88d73a76504472 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Mon, 1 Aug 2022 17:49:35 +0530 Subject: [PATCH 11/13] feat: update global fs before updating override --- contracts/protocol/wrapper/VPoolWrapper.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/protocol/wrapper/VPoolWrapper.sol b/contracts/protocol/wrapper/VPoolWrapper.sol index f345ab45..6521a9cd 100644 --- a/contracts/protocol/wrapper/VPoolWrapper.sol +++ b/contracts/protocol/wrapper/VPoolWrapper.sol @@ -177,6 +177,7 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa /// @notice Updates state to not use any funding rate override. function unsetFundingRateOverride() external onlyGovernanceOrTeamMultisig { + _updateGlobalFundingState({ useZeroFundingRate: true }); fundingRateOverride.setNull(); } @@ -184,12 +185,14 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa /// @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); } From f577b5a760a8c12bb45508358c9c4fe3bb514016 Mon Sep 17 00:00:00 2001 From: LeadDev Date: Thu, 18 Aug 2022 12:01:52 +0530 Subject: [PATCH 12/13] perf: remove struct encoding due to accessor --- contracts/protocol/wrapper/VPoolWrapper.sol | 6 +++++- test/units/VPoolWrapper.spec.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/protocol/wrapper/VPoolWrapper.sol b/contracts/protocol/wrapper/VPoolWrapper.sol index 6521a9cd..e7f43727 100644 --- a/contracts/protocol/wrapper/VPoolWrapper.sol +++ b/contracts/protocol/wrapper/VPoolWrapper.sol @@ -68,7 +68,7 @@ contract VPoolWrapper is IVPoolWrapper, IUniswapV3MintCallback, IUniswapV3SwapCa FundingPayment.Info public fpGlobal; uint256 public sumFeeGlobalX128; - FundingRateOverride.Info public fundingRateOverride; + FundingRateOverride.Info internal fundingRateOverride; mapping(int24 => TickExtended.Info) public ticksExtended; @@ -441,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 */ diff --git a/test/units/VPoolWrapper.spec.ts b/test/units/VPoolWrapper.spec.ts index 608e5049..482f7a75 100644 --- a/test/units/VPoolWrapper.spec.ts +++ b/test/units/VPoolWrapper.spec.ts @@ -600,7 +600,7 @@ describe('PoolWrapper', () => { it('fundingRateOverrideX128', async () => { await vPoolWrapper.connect(owner)['setFundingRateOverride(int256)'](124); - const fundingRateOverrideData = await vPoolWrapper.fundingRateOverride(); + const fundingRateOverrideData = await vPoolWrapper.getFundingRateOverride(); expect(fundingRateOverrideData).to.eq(bytes32(124)); }); From 0a989c14acdb321a66d226c38e052c40c534981b Mon Sep 17 00:00:00 2001 From: LeadDev Date: Thu, 18 Aug 2022 12:04:39 +0530 Subject: [PATCH 13/13] perf: make low level static call to reduce code Does not expand memory, uses scratch space. Also does not copy entire return data to memory, basically uses default static call's ret offset and size to use scratch space only. --- contracts/libraries/FundingRateOverride.sol | 40 ++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/contracts/libraries/FundingRateOverride.sol b/contracts/libraries/FundingRateOverride.sol index 9fd5b2f2..1ab5ec8a 100644 --- a/contracts/libraries/FundingRateOverride.sol +++ b/contracts/libraries/FundingRateOverride.sol @@ -70,18 +70,7 @@ library FundingRateOverride { // 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)) { - try AggregatorV3Interface(oracle).latestRoundData() returns ( - uint80, - int256 fundingRateD8, - uint256, - uint256, - uint80 - ) { - // the oracle gives hourly funding rates in D8, we need to convert to X128 per secs - return (true, (fundingRateD8 << 128) / 3600e8); // divide by 10**8 and 1 hours - } catch { - return (false, 0); - } + return getValueX128FromOracle(AggregatorV3Interface(oracle)); } // VALUE mode: use the value in the data slot @@ -132,4 +121,31 @@ library FundingRateOverride { 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 + } + } + } }