From cf1ba2a71fac35723ec3e76abcfb81ed369946ec Mon Sep 17 00:00:00 2001 From: codinghistorian Date: Thu, 28 Mar 2024 18:30:34 +0400 Subject: [PATCH] FXD-95 adding setPriceForBatch --- contracts/main/interfaces/ISetPrice.sol | 1 + .../main/stablecoin-core/PriceOracle.sol | 27 ++++-- contracts/tests/mocks/MockPriceOracle.sol | 27 ++++-- .../unit/stablecoin-core/PriceOracle.test.js | 89 +++++++++++++++++++ 4 files changed, 130 insertions(+), 14 deletions(-) diff --git a/contracts/main/interfaces/ISetPrice.sol b/contracts/main/interfaces/ISetPrice.sol index 4ab3efb0..5bb2c908 100644 --- a/contracts/main/interfaces/ISetPrice.sol +++ b/contracts/main/interfaces/ISetPrice.sol @@ -3,4 +3,5 @@ pragma solidity 0.8.17; interface ISetPrice { function setPrice(bytes32 _collateralPoolId) external; + function setPriceForBatch(bytes32[] calldata _collateralPoolIds) external; } diff --git a/contracts/main/stablecoin-core/PriceOracle.sol b/contracts/main/stablecoin-core/PriceOracle.sol index 1ea96c91..a80e4f17 100644 --- a/contracts/main/stablecoin-core/PriceOracle.sol +++ b/contracts/main/stablecoin-core/PriceOracle.sol @@ -38,6 +38,8 @@ contract PriceOracle is CommonMath, PausableUpgradeable, IPriceOracle, ICagable, uint256 _priceWithSafetyMargin // Price with safety margin [ray] ); + event LogSetPriceForBatch(bytes32[] _poolIds); + event LogSetStableCoinReferencePrice(address indexed _caller, uint256 _data); event LogSetBookKeeper(address _newAddress); @@ -95,13 +97,14 @@ contract PriceOracle is CommonMath, PausableUpgradeable, IPriceOracle, ICagable, } function setPrice(bytes32 _collateralPoolId) external override whenNotPaused isLive { - IPriceFeed _priceFeed = IPriceFeed(ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).collateralPools(_collateralPoolId).priceFeed); - uint256 _liquidationRatio = ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).getLiquidationRatio(_collateralPoolId); - (uint256 _rawPrice, bool _hasPrice) = _priceFeed.peekPrice(); - uint256 _priceWithSafetyMargin = _hasPrice ? rdiv(rdiv(_rawPrice * (10 ** 9), stableCoinReferencePrice), _liquidationRatio) : 0; - address _collateralPoolConfig = address(bookKeeper.collateralPoolConfig()); - ICollateralPoolConfig(_collateralPoolConfig).setPriceWithSafetyMargin(_collateralPoolId, _priceWithSafetyMargin); - emit LogSetPrice(_collateralPoolId, _rawPrice, _priceWithSafetyMargin); + _setPrice(_collateralPoolId); + } + + function setPriceForBatch(bytes32[] calldata _collateralPoolIds) external override whenNotPaused isLive { + for (uint256 i = 0; i < _collateralPoolIds.length; i++) { + _setPrice(_collateralPoolIds[i]); + } + emit LogSetPriceForBatch(_collateralPoolIds); } /// @dev Cage function halts priceOracle contract for good. @@ -122,4 +125,14 @@ contract PriceOracle is CommonMath, PausableUpgradeable, IPriceOracle, ICagable, function unpause() external override onlyOwnerOrGov { _unpause(); } + + function _setPrice(bytes32 _collateralPoolId) internal { + IPriceFeed _priceFeed = IPriceFeed(ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).collateralPools(_collateralPoolId).priceFeed); + uint256 _liquidationRatio = ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).getLiquidationRatio(_collateralPoolId); + (uint256 _rawPrice, bool _hasPrice) = _priceFeed.peekPrice(); + uint256 _priceWithSafetyMargin = _hasPrice ? rdiv(rdiv(_rawPrice * (10 ** 9), stableCoinReferencePrice), _liquidationRatio) : 0; + address _collateralPoolConfig = address(bookKeeper.collateralPoolConfig()); + ICollateralPoolConfig(_collateralPoolConfig).setPriceWithSafetyMargin(_collateralPoolId, _priceWithSafetyMargin); + emit LogSetPrice(_collateralPoolId, _rawPrice, _priceWithSafetyMargin); + } } diff --git a/contracts/tests/mocks/MockPriceOracle.sol b/contracts/tests/mocks/MockPriceOracle.sol index 7b14f32d..fdf43bf3 100644 --- a/contracts/tests/mocks/MockPriceOracle.sol +++ b/contracts/tests/mocks/MockPriceOracle.sol @@ -38,6 +38,8 @@ contract MockPriceOracle is CommonMath, PausableUpgradeable, IPriceOracle, ICaga uint256 _priceWithSafetyMargin // Price with safety margin [ray] ); + event LogSetPriceForBatch(bytes32[] _poolIds); + event LogSetStableCoinReferencePrice(address indexed _caller, uint256 _data); event LogSetBookKeeper(address _newAddress); @@ -91,13 +93,14 @@ contract MockPriceOracle is CommonMath, PausableUpgradeable, IPriceOracle, ICaga } function setPrice(bytes32 _collateralPoolId) external override whenNotPaused isLive { - IPriceFeed _priceFeed = IPriceFeed(ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).collateralPools(_collateralPoolId).priceFeed); - uint256 _liquidationRatio = ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).getLiquidationRatio(_collateralPoolId); - (uint256 _rawPrice, bool _hasPrice) = _priceFeed.peekPrice(); - uint256 _priceWithSafetyMargin = _hasPrice ? rdiv(rdiv(_rawPrice * (10 ** 9), stableCoinReferencePrice), _liquidationRatio) : 0; - address _collateralPoolConfig = address(bookKeeper.collateralPoolConfig()); - ICollateralPoolConfig(_collateralPoolConfig).setPriceWithSafetyMargin(_collateralPoolId, _priceWithSafetyMargin); - emit LogSetPrice(_collateralPoolId, _rawPrice, _priceWithSafetyMargin); + _setPrice(_collateralPoolId); + } + + function setPriceForBatch(bytes32[] calldata _collateralPoolIds) external override whenNotPaused isLive { + for (uint256 i = 0; i < _collateralPoolIds.length; i++) { + _setPrice(_collateralPoolIds[i]); + } + emit LogSetPriceForBatch(_collateralPoolIds); } /// @dev Cage function halts priceOracle contract for good. @@ -118,4 +121,14 @@ contract MockPriceOracle is CommonMath, PausableUpgradeable, IPriceOracle, ICaga function unpause() external override onlyOwnerOrGov { _unpause(); } + + function _setPrice(bytes32 _collateralPoolId) internal { + IPriceFeed _priceFeed = IPriceFeed(ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).collateralPools(_collateralPoolId).priceFeed); + uint256 _liquidationRatio = ICollateralPoolConfig(bookKeeper.collateralPoolConfig()).getLiquidationRatio(_collateralPoolId); + (uint256 _rawPrice, bool _hasPrice) = _priceFeed.peekPrice(); + uint256 _priceWithSafetyMargin = _hasPrice ? rdiv(rdiv(_rawPrice * (10 ** 9), stableCoinReferencePrice), _liquidationRatio) : 0; + address _collateralPoolConfig = address(bookKeeper.collateralPoolConfig()); + ICollateralPoolConfig(_collateralPoolConfig).setPriceWithSafetyMargin(_collateralPoolId, _priceWithSafetyMargin); + emit LogSetPrice(_collateralPoolId, _rawPrice, _priceWithSafetyMargin); + } } diff --git a/scripts/tests/unit/stablecoin-core/PriceOracle.test.js b/scripts/tests/unit/stablecoin-core/PriceOracle.test.js index 876ff301..d3cc2ac2 100644 --- a/scripts/tests/unit/stablecoin-core/PriceOracle.test.js +++ b/scripts/tests/unit/stablecoin-core/PriceOracle.test.js @@ -149,6 +149,95 @@ describe("PriceOracle", () => { }) }) + describe("#setPriceBatch()", () => { + context("when price from price feed is 1", () => { + context("and price with safety margin is 0", () => { + it("should be success", async () => { + await mockedPriceFeed.mock.peekPrice.returns(formatBytes32BigNumber(One), false) + await mockedBookKeeper.mock.accessControlConfig.returns(mockedCollateralPoolConfig.address) + + await mockedCollateralPoolConfig.mock.collateralPools.returns({ + totalDebtShare: 0, + debtAccumulatedRate: WeiPerRay, + priceWithSafetyMargin: WeiPerRay, + debtCeiling: 0, + debtFloor: 0, + priceFeed: mockedPriceFeed.address, + liquidationRatio: WeiPerRay, + stabilityFeeRate: WeiPerRay, + lastAccumulationTime: 0, + adapter: AddressZero, + closeFactorBps: 5000, + liquidatorIncentiveBps: 10250, + treasuryFeesBps: 5000, + strategy: AddressZero, + positionDebtCeiling: WeiPerRad.mul(1000000) + + }) + await mockedBookKeeper.mock.collateralPoolConfig.returns(mockedCollateralPoolConfig.address) + await mockedCollateralPoolConfig.mock.setPriceWithSafetyMargin.withArgs( + formatBytes32String("WXDC"), + BigNumber.from("0") + ).returns() + await mockedCollateralPoolConfig.mock.setPriceWithSafetyMargin.withArgs( + formatBytes32String("JEJU"), + BigNumber.from("0") + ).returns() + + await expect(priceOracle.setPriceForBatch([formatBytes32String("WXDC"), formatBytes32String("JEJU")])) + .to.emit(priceOracle, "LogSetPriceForBatch") + .withArgs([formatBytes32String("WXDC"), formatBytes32String("JEJU")]) + }) + }) + }) + + context("when price from price feed is 7 * 10^11", () => { + context("and price with safety margin is 0", () => { + it("should be success", async () => { + await mockedBookKeeper.mock.collateralPoolConfig.returns(mockedCollateralPoolConfig.address) + await mockedBookKeeper.mock.accessControlConfig.returns(mockedAccessControlConfig.address) + await mockedAccessControlConfig.mock.hasRole.returns(true) + + await mockedCollateralPoolConfig.mock.collateralPools.returns({ + totalDebtShare: 0, + debtAccumulatedRate: WeiPerRay, + priceWithSafetyMargin: WeiPerRay, + debtCeiling: 0, + debtFloor: 0, + priceFeed: mockedPriceFeed.address, + liquidationRatio: 10 ** 10, + stabilityFeeRate: WeiPerRay, + lastAccumulationTime: 0, + adapter: AddressZero, + closeFactorBps: 5000, + liquidatorIncentiveBps: 10250, + treasuryFeesBps: 5000, + strategy: AddressZero, + positionDebtCeiling: WeiPerRad.mul(1000000) + }) + + await mockedPriceFeed.mock.peekPrice.returns( + formatBytes32BigNumber(BigNumber.from("700000000000")), + false, + ) + + await mockedCollateralPoolConfig.mock.setPriceWithSafetyMargin.withArgs( + formatBytes32String("WXDC"), + BigNumber.from("0") + ).returns() + await mockedCollateralPoolConfig.mock.setPriceWithSafetyMargin.withArgs( + formatBytes32String("JEJU"), + BigNumber.from("0") + ).returns() + await expect(priceOracle.setPriceForBatch([formatBytes32String("WXDC"), formatBytes32String("JEJU")])) + .to.emit(priceOracle, "LogSetPriceForBatch") + .withArgs([formatBytes32String("WXDC"), formatBytes32String("JEJU")]) + }) + }) + }) + }) + + describe("#setStableCoinReferencePrice", () => { context("when the caller is not the owner", async () => { it("should revert", async () => {