Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions contracts/src/PriceFeeds/FXPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ contract FXPriceFeed is IPriceFeed, OwnableUpgradeable {
/// @notice Whether the contract has been shutdown due to an oracle failure
bool public isShutdown;

// @notice Whether the rate from the OracleAdapter should be inverted
bool public invertRateFeed;

/// @notice Thrown when the attempting to shutdown the contract when it is already shutdown
error IsShutDown();
/// @notice Thrown when a non-watchdog address attempts to shutdown the contract
Expand All @@ -47,6 +50,11 @@ contract FXPriceFeed is IPriceFeed, OwnableUpgradeable {
/// @param _newRateFeedID The new rate feed ID
event RateFeedIDUpdated(address indexed _oldRateFeedID, address indexed _newRateFeedID);

/// @notice Emitted when the invert rate feed flag is updated
/// @param _oldInvertRateFeed The previous invert rate feed flag
/// @param _newInvertRateFeed The new invert rate feed flag
event InvertRateFeedUpdated(bool _oldInvertRateFeed, bool _newInvertRateFeed);

/// @notice Emitted when the watchdog address is updated
/// @param _oldWatchdogAddress The previous watchdog address
/// @param _newWatchdogAddress The new watchdog address
Expand All @@ -69,13 +77,15 @@ contract FXPriceFeed is IPriceFeed, OwnableUpgradeable {
* @notice Initializes the FXPriceFeed contract
* @param _oracleAdapterAddress The address of the OracleAdapter contract
* @param _rateFeedID The address of the rate feed ID
* @param _invertRateFeed Whether the rate from the OracleAdapter should be inverted
* @param _borrowerOperationsAddress The address of the BorrowerOperations contract
* @param _watchdogAddress The address of the watchdog contract
* @param _initialOwner The address of the initial owner
*/
function initialize(
address _oracleAdapterAddress,
address _rateFeedID,
bool _invertRateFeed,
address _borrowerOperationsAddress,
address _watchdogAddress,
address _initialOwner
Expand All @@ -88,6 +98,7 @@ contract FXPriceFeed is IPriceFeed, OwnableUpgradeable {

oracleAdapter = IOracleAdapter(_oracleAdapterAddress);
rateFeedID = _rateFeedID;
invertRateFeed = _invertRateFeed;
borrowerOperations = IBorrowerOperations(_borrowerOperationsAddress);
watchdogAddress = _watchdogAddress;

Expand All @@ -105,6 +116,17 @@ contract FXPriceFeed is IPriceFeed, OwnableUpgradeable {
emit RateFeedIDUpdated(oldRateFeedID, _newRateFeedID);
}

/**
* @notice Sets the invert rate feed flag
* @param _invertRateFeed Whether the rate from the OracleAdapter should be inverted
*/
function setInvertRateFeed(bool _invertRateFeed) external onlyOwner {
bool oldInvertRateFeed = invertRateFeed;
invertRateFeed = _invertRateFeed;

emit InvertRateFeedUpdated(oldInvertRateFeed, _invertRateFeed);
}

/**
* @notice Sets the watchdog address
* @param _newWatchdogAddress The address of the new watchdog contract
Expand All @@ -121,15 +143,23 @@ contract FXPriceFeed is IPriceFeed, OwnableUpgradeable {
/**
* @notice Fetches the price of the FX rate, if valid
* @dev If the contract is shutdown due to oracle failure, the last valid price is returned
* @return The price of the FX rate
* @return price The price of the FX rate
*/
function fetchPrice() public returns (uint256) {
function fetchPrice() public returns (uint256 price) {
if (isShutdown) {
return lastValidPrice;
}

// Denominator is always 1e18, so we only use the numerator as the price
(uint256 price,) = oracleAdapter.getFXRateIfValid(rateFeedID);
(uint256 numerator, uint256 denominator) = oracleAdapter.getFXRateIfValid(rateFeedID);

if (invertRateFeed) {
// Multiply by 1e18 to get the price in 18 decimals
price = (denominator * 1e18) / numerator;
} else {
// Denominator is always 1e18, so we only use the numerator as the price
assert(denominator == 1e18);
price = numerator;
}

lastValidPrice = price;

Expand Down
56 changes: 56 additions & 0 deletions contracts/test/FXPriceFeed.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ contract MockOracleAdapter {
contract FXPriceFeedTest is Test {

event WatchdogAddressUpdated(address indexed _oldWatchdogAddress, address indexed _newWatchdogAddress);
event InvertRateFeedUpdated(bool _oldInvertRateFeed, bool _newInvertRateFeed);
event FXPriceFeedShutdown();

FXPriceFeed public fxPriceFeed;
Expand All @@ -54,6 +55,7 @@ contract FXPriceFeedTest is Test {
fxPriceFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(mockBorrowerOperations),
watchdog,
owner
Expand All @@ -78,6 +80,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(mockBorrowerOperations),
watchdog,
owner
Expand All @@ -91,6 +94,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(0),
rateFeedID,
false,
address(mockBorrowerOperations),
watchdog,
owner
Expand All @@ -104,6 +108,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
address(0),
false,
address(mockBorrowerOperations),
watchdog,
owner
Expand All @@ -117,6 +122,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(0),
watchdog,
owner
Expand All @@ -130,6 +136,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(mockBorrowerOperations),
address(0),
owner
Expand All @@ -143,6 +150,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(mockBorrowerOperations),
watchdog,
address(0)
Expand All @@ -157,6 +165,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(mockBorrowerOperations),
watchdog,
owner
Expand All @@ -176,6 +185,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(mockBorrowerOperations),
watchdog,
owner
Expand All @@ -185,6 +195,7 @@ contract FXPriceFeedTest is Test {
newFeed.initialize(
address(mockOracleAdapter),
rateFeedID,
false,
address(mockBorrowerOperations),
watchdog,
owner
Expand Down Expand Up @@ -218,6 +229,34 @@ contract FXPriceFeedTest is Test {
assertEq(fxPriceFeed.rateFeedID(), newRateFeedID);
}

function test_setInvertRateFeed_whenCalledByNonOwner_shouldRevert() initialized public {
address notOwner = makeAddr("notOwner");
bool newInvertRateFeed = true;

vm.prank(notOwner);
vm.expectRevert("Ownable: caller is not the owner");
fxPriceFeed.setInvertRateFeed(newInvertRateFeed);
vm.stopPrank();
}

function test_setInvertRateFeed_whenCalledByOwner_shouldSucceed() initialized public {
vm.startPrank(owner);
vm.expectEmit();
emit InvertRateFeedUpdated(false, true);
fxPriceFeed.setInvertRateFeed(true);
vm.stopPrank();

assertEq(fxPriceFeed.invertRateFeed(), true);

vm.startPrank(owner);
vm.expectEmit();
emit InvertRateFeedUpdated(true, false);
fxPriceFeed.setInvertRateFeed(false);
vm.stopPrank();

assertEq(fxPriceFeed.invertRateFeed(), false);
}

function test_setWatchdogAddress_whenCalledByNonOwner_shouldRevert() initialized public {
address notOwner = makeAddr("notOwner");
address newWatchdog = makeAddr("newWatchdog");
Expand Down Expand Up @@ -270,6 +309,23 @@ contract FXPriceFeedTest is Test {
assertEq(fxPriceFeed.lastValidPrice(), initialPrice);
}

function test_fetchPrice_whenInvertRateFeedIsTrue_shouldReturnInvertedPrice() initialized public {
vm.startPrank(owner);
fxPriceFeed.setInvertRateFeed(true);
vm.stopPrank();

uint256 price = fxPriceFeed.fetchPrice();

assertEq(price, (mockRateDenominator * 1e18) / mockRateNumerator);
assertEq(fxPriceFeed.lastValidPrice(), (mockRateDenominator * 1e18) / mockRateNumerator);

uint256 XOFUSDRateNumerator = 1771165426850867; // 0.001771 USD = ~1 XOF
mockOracleAdapter.setFXRate(XOFUSDRateNumerator, 1e18);

assertEq(fxPriceFeed.fetchPrice(), 564600000000000277670); // 1 USD = ~564 XOF
assertEq(fxPriceFeed.lastValidPrice(), 564600000000000277670);
}

function test_shutdown_whenCalledByNonWatchdog_shouldRevert() initialized public {
address notWatchdog = makeAddr("notWatchdog");

Expand Down