From 2b4ea994dcc4ef7a3eaff5c83696fe4476de677a Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Fri, 24 Feb 2023 17:38:40 +0000 Subject: [PATCH] USD reference asset --- contracts/Constants.sol | 3 ++ contracts/modules/RiskManager.sol | 15 ++++-- test/chainlinkPriceFeed.js | 83 +++++++++++++++++++++++++++++++ test/twap.js | 23 +++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/contracts/Constants.sol b/contracts/Constants.sol index a734c563..36bc42d0 100644 --- a/contracts/Constants.sol +++ b/contracts/Constants.sol @@ -37,6 +37,9 @@ abstract contract Constants { uint8 internal constant DEFERLIQUIDITY__CLEAN = 1; uint8 internal constant DEFERLIQUIDITY__DIRTY = 2; + // Fiat currency Reference Assets, as per ISO 4217 + // https://en.wikipedia.org/wiki/ISO_4217 + address internal constant REFERENCE_ASSET__USD = address(840); // Pricing types diff --git a/contracts/modules/RiskManager.sol b/contracts/modules/RiskManager.sol index 6929a0f8..15657b91 100644 --- a/contracts/modules/RiskManager.sol +++ b/contracts/modules/RiskManager.sol @@ -115,6 +115,11 @@ contract RiskManager is IRiskManager, BaseLogic { } price = uint(answer); + + // if reference asset is USD, it implies that Chainlink asset/USD price feeds are used. + // Chainlink asset/USD price feeds are 8 decimals, so we need to scale them up to 18 decimals + if (referenceAsset == REFERENCE_ASSET__USD) price = price * 1e10; + if (price > 1e36) price = 1e36; } @@ -147,20 +152,22 @@ contract RiskManager is IRiskManager, BaseLogic { twap = 1e18; twapPeriod = twapWindow; } else if (pricingType == PRICINGTYPE__UNISWAP3_TWAP) { - (twap, twapPeriod) = callUniswapObserve(underlying, pricingParameters, twapWindow, underlyingDecimalsScaler); + if (uniswapFactory != address(0)) { + (twap, twapPeriod) = callUniswapObserve(underlying, pricingParameters, twapWindow, underlyingDecimalsScaler); + } } else if (pricingType == PRICINGTYPE__CHAINLINK) { twap = callChainlinkLatestAnswer(chainlinkPriceFeedLookup[underlying]); twapPeriod = 0; // if price invalid and uniswap fallback pool configured get the price from uniswap - if (twap == 0 && uint24(pricingParameters) != 0) { + if (twap == 0 && uint24(pricingParameters) != 0 && uniswapFactory != address(0)) { (twap, twapPeriod) = callUniswapObserve(underlying, pricingParameters, twapWindow, underlyingDecimalsScaler); } - - require(twap != 0, "e/unable-to-get-the-price"); } else { revert("e/unknown-pricing-type"); } + + require(twap != 0, "e/unable-to-get-the-price"); } function getPrice(address underlying) external view override returns (uint twap, uint twapPeriod) { diff --git a/test/chainlinkPriceFeed.js b/test/chainlinkPriceFeed.js index 46bbada4..675481b3 100644 --- a/test/chainlinkPriceFeed.js +++ b/test/chainlinkPriceFeed.js @@ -216,4 +216,87 @@ et.testSet({ ], }) +.test({ + desc: "chainlink price scaling when USD is the reference asset", + actions: ctx => [ + { action: 'cb', cb: async () => { + + // Install RiskManager with the USD as the reference asset + const riskManagerSettings = { + referenceAsset: et.ethers.utils.hexZeroPad(et.BN(840).toHexString(), 20), + uniswapFactory: ethers.constants.AddressZero, + uniswapPoolInitCodeHash: et.ethers.utils.hexZeroPad('0x', 32), + } + + ctx.contracts.modules.riskManager = await (await ctx.factories.RiskManager.deploy( + et.ethers.utils.hexZeroPad('0x', 32), + riskManagerSettings + )).deployed() + + await (await ctx.contracts.installer.connect(ctx.wallet) + .installModules([ctx.contracts.modules.riskManager.address])).wait(); + }}, + + // Set up the price feed + + { send: 'governance.setChainlinkPriceFeed', args: + [ctx.contracts.tokens.TST.address, ctx.contracts.AggregatorTST.address], onLogs: logs => { + et.expect(logs.length).to.equal(1); + et.expect(logs[0].name).to.equal('GovSetChainlinkPriceFeed'); + et.expect(logs[0].args.underlying).to.equal(ctx.contracts.tokens.TST.address); + et.expect(logs[0].args.chainlinkAggregator).to.equal(ctx.contracts.AggregatorTST.address); + }}, + + // Set pool pricing configuration + + { send: 'governance.setPricingConfig', args: [ctx.contracts.tokens.TST.address, PRICINGTYPE__CHAINLINK, et.DefaultUniswapFee], onLogs: logs => { + et.expect(logs.length).to.equal(1); + et.expect(logs[0].name).to.equal('GovSetPricingConfig'); + et.expect(logs[0].args.underlying).to.equal(ctx.contracts.tokens.TST.address); + et.expect(logs[0].args.newPricingType).to.equal(PRICINGTYPE__CHAINLINK); + et.expect(logs[0].args.newPricingParameter).to.equal(et.DefaultUniswapFee); + }}, + + // test getPrice + + { action: 'cb', cb: async () => { + await ctx.contracts.AggregatorTST.mockSetData([1, et.ethers.utils.parseUnits('1', 8), 0, 0, 0]); + const resultTST = await ctx.contracts.exec.getPrice(ctx.contracts.tokens.TST.address); + et.expect(resultTST.twap).to.equal(et.ethers.utils.parseUnits('1', 18)); + et.expect(resultTST.twapPeriod).to.equal(0); + }}, + + // test getPriceFull + + { action: 'cb', cb: async () => { + // Set up the price equal to the max price of 1e36 + + await ctx.contracts.AggregatorTST.mockSetData([1, et.ethers.utils.parseUnits('1', 8), 0, 0, 0]); + const resultTST = await ctx.contracts.exec.getPriceFull(ctx.contracts.tokens.TST.address); + et.expect(resultTST.twap).to.equal(et.ethers.utils.parseUnits('1', 18)); + et.expect(resultTST.currPrice).to.equal(resultTST.twap); + et.expect(resultTST.twapPeriod).to.equal(0); + }}, + + // test getPrice + + { action: 'cb', cb: async () => { + await ctx.contracts.AggregatorTST.mockSetData([1, et.ethers.utils.parseUnits('1.2345', 8), 0, 0, 0]); + const resultTST = await ctx.contracts.exec.getPrice(ctx.contracts.tokens.TST.address); + et.expect(resultTST.twap).to.equal(et.ethers.utils.parseUnits('1.2345', 18)); + et.expect(resultTST.twapPeriod).to.equal(0); + }}, + + // test getPriceFull + + { action: 'cb', cb: async () => { + await ctx.contracts.AggregatorTST.mockSetData([1, et.ethers.utils.parseUnits('1.2345', 8), 0, 0, 0]); + const resultTST = await ctx.contracts.exec.getPriceFull(ctx.contracts.tokens.TST.address); + et.expect(resultTST.twap).to.equal(et.ethers.utils.parseUnits('1.2345', 18)); + et.expect(resultTST.currPrice).to.equal(resultTST.twap); + et.expect(resultTST.twapPeriod).to.equal(0); + }} + ], +}) + .run(); diff --git a/test/twap.js b/test/twap.js index 04430b12..92c0d440 100644 --- a/test/twap.js +++ b/test/twap.js @@ -42,7 +42,30 @@ et.testSet({ }) +.test({ + desc: "no uniswap configured", + actions: ctx => [ + { action: 'cb', cb: async () => { + // Install RiskManager without uniswap configured + const riskManagerSettings = { + referenceAsset: ctx.contracts.tokens['WETH'].address, + uniswapFactory: ethers.constants.AddressZero, + uniswapPoolInitCodeHash: et.ethers.utils.hexZeroPad('0x', 32), + } + + ctx.contracts.modules.riskManager = await (await ctx.factories.RiskManager.deploy( + et.ethers.utils.hexZeroPad('0x', 32), + riskManagerSettings + )).deployed() + + await (await ctx.contracts.installer.connect(ctx.wallet) + .installModules([ctx.contracts.modules.riskManager.address])).wait(); + }}, + { action: 'getPrice', underlying: 'TST', expectError: 'e/unable-to-get-the-price', }, + { action: 'getPrice', underlying: 'TST2', expectError: 'e/unable-to-get-the-price', }, + ], +}) .run();