From e532c797ff6129ab411e64afaf41c65384d7ab9b Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 22 Jun 2023 10:49:56 +0300 Subject: [PATCH 01/25] feat: cvi and deployment scripts for providers --- src/v2/oracles/CVIPriceProvider.sol | 54 ++++++++++++++++++ src/v2/oracles/DIAPriceProvider.sol | 49 ++++++++++++++++ test/V2/e2e/EndToEndGenericTest.t.sol | 31 ++++++++++ test/V2/oracles/CVIPriceProvider.t.sol | 78 ++++++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 src/v2/oracles/CVIPriceProvider.sol create mode 100644 src/v2/oracles/DIAPriceProvider.sol create mode 100644 test/V2/oracles/CVIPriceProvider.t.sol diff --git a/src/v2/oracles/CVIPriceProvider.sol b/src/v2/oracles/CVIPriceProvider.sol new file mode 100644 index 00000000..a2c54182 --- /dev/null +++ b/src/v2/oracles/CVIPriceProvider.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IVaultFactoryV2} from "../interfaces/IVaultFactoryV2.sol"; +import {IConditionProvider} from "../interfaces/IConditionProvider.sol"; +import {ICVIPriceFeed} from "../interfaces/ICVIPriceFeed.sol"; + +contract CVIPriceProvider is IConditionProvider { + uint256 public immutable timeOut; + ICVIPriceFeed public priceFeedAdapter; + + constructor(address _priceFeed, uint256 _timeOut) { + if (_priceFeed == address(0)) revert ZeroAddress(); + if (_timeOut == 0) revert InvalidInput(); + priceFeedAdapter = ICVIPriceFeed(_priceFeed); + timeOut = _timeOut; + } + + /** @notice Fetch token price from priceFeedAdapter (Redston oracle address) + * @return int256 Current token price + */ + function getLatestPrice() public view virtual returns (int256) { + (uint256 price, , uint256 updatedAt) = priceFeedAdapter + .getCVILatestRoundData(); + if (price == 0) revert OraclePriceZero(); + + // TODO: What is a suitable timeframe to set timeout as based on this info? Update at always timestamp? + if ((block.timestamp - updatedAt) > timeOut) revert PriceTimedOut(); + + return int256(price); + } + + // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future + /** @notice Fetch price and return condition + * @param _strike Strike price + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256 _strike + ) public view virtual returns (bool, int256 price) { + price = getLatestPrice(); + return (int256(_strike) < price, price); + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + error ZeroAddress(); + error InvalidInput(); + error OraclePriceZero(); + error RoundIdOutdated(); + error PriceTimedOut(); +} diff --git a/src/v2/oracles/DIAPriceProvider.sol b/src/v2/oracles/DIAPriceProvider.sol new file mode 100644 index 00000000..86b7e2c7 --- /dev/null +++ b/src/v2/oracles/DIAPriceProvider.sol @@ -0,0 +1,49 @@ +/******************************************************* +NOTE: Development in progress by JG. Reached functional milestone; Live VST data is accessible. +***/ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IConditionProvider} from "../interfaces/IConditionProvider.sol"; +import {IDIAPriceFeed} from "../interfaces/IDIAPriceFeed.sol"; + +contract DIAPriceProvider is IConditionProvider { + string public constant PAIR_NAME = "BTC/USD"; + IDIAPriceFeed public diaPriceFeed; + + constructor(address _priceFeed) { + if (_priceFeed == address(0)) revert ZeroAddress(); + diaPriceFeed = IDIAPriceFeed(_priceFeed); + } + + function _getLatestPrice() private view returns (uint256 price) { + (price, ) = diaPriceFeed.getValue(PAIR_NAME); + return price; + } + + /** @notice Fetch token price from priceFeedAdapter (Using string name) + * @return int256 Current token price + */ + function getLatestPrice() public view override returns (int256) { + return int256(_getLatestPrice()); + } + + /** @notice Fetch price and return condition + * @dev The strike is hashed as an int256 to enable comparison vs. price for earthquake + and conditional check vs. strike to ensure vaidity + * @param _strike Strike price + * @return condition boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256 _strike + ) public view virtual returns (bool condition, int256) { + uint256 price = _getLatestPrice(); + return (_strike > price, int256(price)); + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + error ZeroAddress(); +} diff --git a/test/V2/e2e/EndToEndGenericTest.t.sol b/test/V2/e2e/EndToEndGenericTest.t.sol index 4523ddc7..58bc1b1a 100644 --- a/test/V2/e2e/EndToEndGenericTest.t.sol +++ b/test/V2/e2e/EndToEndGenericTest.t.sol @@ -302,6 +302,37 @@ contract EndToEndV2GenericTest is Helper { MintableToken(UNDERLYING).mint(USER); } + function _setupCVI() internal { + vm.selectFork(arbForkId); + UNDERLYING = address(new MintableToken("CVI Volatility", "CVI")); + address timelock = address(new TimeLock(ADMIN)); + factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); + controller = new ControllerGeneric(address(factory), TREASURY); + factory.whitelistController(address(controller)); + + cviPriceProvider = new CVIPriceProvider(CVI_ORACLE, TIME_OUT); + int256 cviStrike = cviPriceProvider.getLatestPrice() - 1; + + depegStrike = uint256(cviStrike); + string memory name = string("CVI Volatility"); + string memory symbol = string("CVI"); + (depegPremium, depegCollateral, depegMarketId) = factory + .createNewMarket( + VaultFactoryV2.MarketConfigurationCalldata( + UNDERLYING, + depegStrike, + address(cviPriceProvider), + UNDERLYING, + name, + symbol, + address(controller) + ) + ); + + (depegEpochId, ) = factory.createEpoch(depegMarketId, begin, end, fee); + MintableToken(UNDERLYING).mint(USER); + } + function helperCalculateFeeAdjustedValue( uint256 _amount, uint16 _fee diff --git a/test/V2/oracles/CVIPriceProvider.t.sol b/test/V2/oracles/CVIPriceProvider.t.sol new file mode 100644 index 00000000..8d2a3962 --- /dev/null +++ b/test/V2/oracles/CVIPriceProvider.t.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Helper} from "../Helper.sol"; +import {VaultFactoryV2} from "../../../src/v2/VaultFactoryV2.sol"; +import {CVIPriceProvider} from "../../../src/v2/oracles/CVIPriceProvider.sol"; +import {TimeLock} from "../../../src/v2/TimeLock.sol"; +import {MockOracleAnswerZeroCVI, MockOracleTimeOutCVI} from "./MockOracles.sol"; + +contract CVIPriceProviderTest is Helper { + uint256 public arbForkId; + VaultFactoryV2 public factory; + CVIPriceProvider public cviPriceProvider; + + //////////////////////////////////////////////// + // HELPERS // + //////////////////////////////////////////////// + + function setUp() public { + arbForkId = vm.createFork(ARBITRUM_RPC_URL); + vm.selectFork(arbForkId); + + address timelock = address(new TimeLock(ADMIN)); + factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); + cviPriceProvider = new CVIPriceProvider(CVI_ORACLE, TIME_OUT); + } + + //////////////////////////////////////////////// + // STATE // + //////////////////////////////////////////////// + function testCVICreation() public { + assertEq(cviPriceProvider.timeOut(), TIME_OUT); + assertEq(address(cviPriceProvider.priceFeedAdapter()), CVI_ORACLE); + } + + //////////////////////////////////////////////// + // FUNCTIONS // + //////////////////////////////////////////////// + + function testLatestPriceCVI() public { + int256 price = cviPriceProvider.getLatestPrice(); + assertTrue(price != 0); + } + + function testConditionMetCVI() public { + (bool condition, int256 price) = cviPriceProvider.conditionMet(100); + assertTrue(price != 0); + assertEq(condition, true); + } + + //////////////////////////////////////////////// + // REVERT CASES // + //////////////////////////////////////////////// + + function testRevertConstructorInputs() public { + vm.expectRevert(CVIPriceProvider.ZeroAddress.selector); + new CVIPriceProvider(address(0), TIME_OUT); + + vm.expectRevert(CVIPriceProvider.InvalidInput.selector); + new CVIPriceProvider(CVI_ORACLE, 0); + } + + function testRevertOraclePriceZeroCVI() public { + address mockOracle = address(new MockOracleAnswerZeroCVI()); + cviPriceProvider = new CVIPriceProvider(mockOracle, TIME_OUT); + vm.expectRevert(CVIPriceProvider.OraclePriceZero.selector); + cviPriceProvider.getLatestPrice(); + } + + function testRevertTimeOut() public { + address mockOracle = address( + new MockOracleTimeOutCVI(block.timestamp, TIME_OUT) + ); + cviPriceProvider = new CVIPriceProvider(mockOracle, TIME_OUT); + vm.expectRevert(CVIPriceProvider.PriceTimedOut.selector); + cviPriceProvider.getLatestPrice(); + } +} From abd1fbfa70cfb44154a2ad94ee3c9a06b31c7a17 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 29 Jun 2023 11:59:33 +0300 Subject: [PATCH 02/25] feat: Chainlink interface variables added to providers --- src/v2/oracles/CVIPriceProvider.sol | 42 +++++++++++++++--- src/v2/oracles/DIAPriceProvider.sol | 60 ++++++++++++++++++++------ test/V2/e2e/EndToEndGenericTest.t.sol | 6 ++- test/V2/oracles/CVIPriceProvider.t.sol | 38 +++++++++++++--- 4 files changed, 123 insertions(+), 23 deletions(-) diff --git a/src/v2/oracles/CVIPriceProvider.sol b/src/v2/oracles/CVIPriceProvider.sol index a2c54182..72f50930 100644 --- a/src/v2/oracles/CVIPriceProvider.sol +++ b/src/v2/oracles/CVIPriceProvider.sol @@ -8,26 +8,58 @@ import {ICVIPriceFeed} from "../interfaces/ICVIPriceFeed.sol"; contract CVIPriceProvider is IConditionProvider { uint256 public immutable timeOut; ICVIPriceFeed public priceFeedAdapter; + uint256 public immutable decimals; + string public description; - constructor(address _priceFeed, uint256 _timeOut) { + constructor(address _priceFeed, uint256 _timeOut, uint256 _decimals) { if (_priceFeed == address(0)) revert ZeroAddress(); if (_timeOut == 0) revert InvalidInput(); priceFeedAdapter = ICVIPriceFeed(_priceFeed); timeOut = _timeOut; + decimals = _decimals; + description = "CVI"; + } + + function latestRoundData() + public + view + returns ( + uint80 roundId, + int256 price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + uint32 cviValue; + (cviValue, roundId, updatedAt) = priceFeedAdapter + .getCVILatestRoundData(); + price = int32(cviValue); + startedAt = 1; + answeredInRound = roundId; } /** @notice Fetch token price from priceFeedAdapter (Redston oracle address) - * @return int256 Current token price + * @return price Current token price */ - function getLatestPrice() public view virtual returns (int256) { - (uint256 price, , uint256 updatedAt) = priceFeedAdapter + function getLatestPrice() public view virtual returns (int256 price) { + (uint256 uintPrice, , uint256 updatedAt) = priceFeedAdapter .getCVILatestRoundData(); + price = int256(uintPrice); if (price == 0) revert OraclePriceZero(); // TODO: What is a suitable timeframe to set timeout as based on this info? Update at always timestamp? if ((block.timestamp - updatedAt) > timeOut) revert PriceTimedOut(); - return int256(price); + if (decimals < 18) { + uint256 calcDecimals = 10 ** (18 - (decimals)); + price = price * int256(calcDecimals); + } else if (decimals > 18) { + uint256 calcDecimals = 10 ** ((decimals - 18)); + price = price / int256(calcDecimals); + } + + return price; } // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future diff --git a/src/v2/oracles/DIAPriceProvider.sol b/src/v2/oracles/DIAPriceProvider.sol index 86b7e2c7..27ca916e 100644 --- a/src/v2/oracles/DIAPriceProvider.sol +++ b/src/v2/oracles/DIAPriceProvider.sol @@ -8,24 +8,38 @@ import {IConditionProvider} from "../interfaces/IConditionProvider.sol"; import {IDIAPriceFeed} from "../interfaces/IDIAPriceFeed.sol"; contract DIAPriceProvider is IConditionProvider { - string public constant PAIR_NAME = "BTC/USD"; IDIAPriceFeed public diaPriceFeed; + uint256 public immutable decimals; + string public constant description = "BTC/USD"; - constructor(address _priceFeed) { + constructor(address _priceFeed, uint256 _decimals) { if (_priceFeed == address(0)) revert ZeroAddress(); diaPriceFeed = IDIAPriceFeed(_priceFeed); + decimals = _decimals; } - function _getLatestPrice() private view returns (uint256 price) { - (price, ) = diaPriceFeed.getValue(PAIR_NAME); - return price; + function latestRoundData() + public + view + returns ( + uint80 roundId, + int256 price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + (price, updatedAt) = _getLatestPrice(); + startedAt = 1; + roundId = 1; + answeredInRound = 1; } /** @notice Fetch token price from priceFeedAdapter (Using string name) - * @return int256 Current token price + * @return price Current token price */ - function getLatestPrice() public view override returns (int256) { - return int256(_getLatestPrice()); + function getLatestPrice() public view override returns (int256 price) { + (price, ) = _getLatestPrice(); } /** @notice Fetch price and return condition @@ -33,13 +47,35 @@ contract DIAPriceProvider is IConditionProvider { and conditional check vs. strike to ensure vaidity * @param _strike Strike price * @return condition boolean If condition is met i.e. strike > price - * @return price Current price for token + * @return price current price for token */ function conditionMet( uint256 _strike - ) public view virtual returns (bool condition, int256) { - uint256 price = _getLatestPrice(); - return (_strike > price, int256(price)); + ) public view virtual returns (bool condition, int256 price) { + (price, ) = _getLatestPrice(); + return (_strike > uint256(price), price); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////*/ + + function _getLatestPrice() + private + view + returns (int256 price, uint256 timestamp) + { + uint256 uintPrice; + (uintPrice, timestamp) = diaPriceFeed.getValue(description); + price = int256(uintPrice); + + if (decimals < 18) { + uint256 calcDecimals = 10 ** (18 - (decimals)); + price = price * int256(calcDecimals); + } else if (decimals > 18) { + uint256 calcDecimals = 10 ** ((decimals - 18)); + price = price / int256(calcDecimals); + } } /*////////////////////////////////////////////////////////////// diff --git a/test/V2/e2e/EndToEndGenericTest.t.sol b/test/V2/e2e/EndToEndGenericTest.t.sol index 58bc1b1a..fc45db03 100644 --- a/test/V2/e2e/EndToEndGenericTest.t.sol +++ b/test/V2/e2e/EndToEndGenericTest.t.sol @@ -310,7 +310,11 @@ contract EndToEndV2GenericTest is Helper { controller = new ControllerGeneric(address(factory), TREASURY); factory.whitelistController(address(controller)); - cviPriceProvider = new CVIPriceProvider(CVI_ORACLE, TIME_OUT); + cviPriceProvider = new CVIPriceProvider( + CVI_ORACLE, + TIME_OUT, + CVI_DECIMALS + ); int256 cviStrike = cviPriceProvider.getLatestPrice() - 1; depegStrike = uint256(cviStrike); diff --git a/test/V2/oracles/CVIPriceProvider.t.sol b/test/V2/oracles/CVIPriceProvider.t.sol index 8d2a3962..4527193b 100644 --- a/test/V2/oracles/CVIPriceProvider.t.sol +++ b/test/V2/oracles/CVIPriceProvider.t.sol @@ -22,7 +22,11 @@ contract CVIPriceProviderTest is Helper { address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - cviPriceProvider = new CVIPriceProvider(CVI_ORACLE, TIME_OUT); + cviPriceProvider = new CVIPriceProvider( + CVI_ORACLE, + TIME_OUT, + CVI_DECIMALS + ); } //////////////////////////////////////////////// @@ -31,11 +35,27 @@ contract CVIPriceProviderTest is Helper { function testCVICreation() public { assertEq(cviPriceProvider.timeOut(), TIME_OUT); assertEq(address(cviPriceProvider.priceFeedAdapter()), CVI_ORACLE); + assertEq(cviPriceProvider.decimals(), CVI_DECIMALS); + assertEq(cviPriceProvider.description(), "CVI"); } //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// + function testLatestRoundDataCVI() public { + ( + uint80 roundId, + int256 price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = cviPriceProvider.latestRoundData(); + assertTrue(price != 0); + assertTrue(roundId != 0); + assertEq(startedAt, 1); + assertTrue(updatedAt != 0); + assertTrue(answeredInRound != 0); + } function testLatestPriceCVI() public { int256 price = cviPriceProvider.getLatestPrice(); @@ -54,15 +74,19 @@ contract CVIPriceProviderTest is Helper { function testRevertConstructorInputs() public { vm.expectRevert(CVIPriceProvider.ZeroAddress.selector); - new CVIPriceProvider(address(0), TIME_OUT); + new CVIPriceProvider(address(0), TIME_OUT, CVI_DECIMALS); vm.expectRevert(CVIPriceProvider.InvalidInput.selector); - new CVIPriceProvider(CVI_ORACLE, 0); + new CVIPriceProvider(CVI_ORACLE, 0, CVI_DECIMALS); } function testRevertOraclePriceZeroCVI() public { address mockOracle = address(new MockOracleAnswerZeroCVI()); - cviPriceProvider = new CVIPriceProvider(mockOracle, TIME_OUT); + cviPriceProvider = new CVIPriceProvider( + mockOracle, + TIME_OUT, + CVI_DECIMALS + ); vm.expectRevert(CVIPriceProvider.OraclePriceZero.selector); cviPriceProvider.getLatestPrice(); } @@ -71,7 +95,11 @@ contract CVIPriceProviderTest is Helper { address mockOracle = address( new MockOracleTimeOutCVI(block.timestamp, TIME_OUT) ); - cviPriceProvider = new CVIPriceProvider(mockOracle, TIME_OUT); + cviPriceProvider = new CVIPriceProvider( + mockOracle, + TIME_OUT, + CVI_DECIMALS + ); vm.expectRevert(CVIPriceProvider.PriceTimedOut.selector); cviPriceProvider.getLatestPrice(); } From 08130a7f71e671ce32f628db4d19f4c224460d3f Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Fri, 1 Sep 2023 15:40:12 +0100 Subject: [PATCH 03/25] feat: uma assertion implementation --- src/v2/interfaces/IOptimisticOracleV3.sol | 20 ++ .../oracles/individual/UmaPriceProvider.sol | 244 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 src/v2/interfaces/IOptimisticOracleV3.sol create mode 100644 src/v2/oracles/individual/UmaPriceProvider.sol diff --git a/src/v2/interfaces/IOptimisticOracleV3.sol b/src/v2/interfaces/IOptimisticOracleV3.sol new file mode 100644 index 00000000..b43484e5 --- /dev/null +++ b/src/v2/interfaces/IOptimisticOracleV3.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IOptimisticOracleV3 { + function assertTruth( + bytes calldata claim, + address asserter, + address callBackAddress, + address sovereignSecurity, + uint64 assertionLiveness, + IERC20 currency, + uint256 bond, + bytes32 defaultIdentifier, + bytes32 domain + ) external payable returns (bytes32 assertionId); + + function getMinimumBond(address currency) external returns (uint256); +} diff --git a/src/v2/oracles/individual/UmaPriceProvider.sol b/src/v2/oracles/individual/UmaPriceProvider.sol new file mode 100644 index 00000000..932d2afe --- /dev/null +++ b/src/v2/oracles/individual/UmaPriceProvider.sol @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IVaultFactoryV2} from "../../interfaces/IVaultFactoryV2.sol"; +import {IConditionProvider} from "../../interfaces/IConditionProvider.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IOptimisticOracleV3} from "../../interfaces/IOptimisticOracleV3.sol"; +import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol"; +import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract UmaPriceProvider is Ownable, IConditionProvider { + using SafeTransferLib for ERC20; + struct MarketAnswer { + uint128 updatedAt; + uint128 answer; + uint256 requiredBond; + bool activeAssertion; + } + + // Uma V3 + uint64 public constant assertionLiveness = 7200; // 2 hours. + address immutable currency; // Currency used for all prediction markets + bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. + IOptimisticOracleV3 public immutable umaV3; + + // Market info + uint256 public immutable timeOut; + IVaultFactoryV2 public immutable vaultFactory; + uint256 public immutable decimals; + string public description; + + string public outcome; + string public assertedOutcome; + bytes public assertionDescription; + MarketAnswer public marketAnswer; + + mapping(uint256 => uint256) public marketIdToConditionType; + + event MarketAsserted( + uint256 marketId, + string assertedOutcome, + bytes32 assertionId + ); + event AnswerResolved(bytes32 assertionId, bool assertion); + event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + + constructor( + address _factory, + uint256 _timeOut, + address _umaV3, + string memory _description, + bytes32 _defaultIdentifier, + uint256 _decimals, + address _currency + ) { + if (_factory == address(0)) revert ZeroAddress(); + if (_timeOut == 0) revert InvalidInput(); + if (_umaV3 == address(0)) revert ZeroAddress(); + if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) + revert InvalidInput(); + if ( + keccak256(abi.encodePacked(_defaultIdentifier)) == + keccak256(bytes("")) + ) revert InvalidInput(); + if (_decimals == 0) revert InvalidInput(); + if (_currency == address(0)) revert InvalidInput(); + + vaultFactory = IVaultFactoryV2(_factory); + timeOut = _timeOut; + + umaV3 = IOptimisticOracleV3(_umaV3); + description = _description; + decimals = _decimals; + currency = _currency; + defaultIdentifier = _defaultIdentifier; + } + + /*////////////////////////////////////////////////////////////// + ADMIN + //////////////////////////////////////////////////////////////*/ + function setConditionType( + uint256 _marketId, + uint256 _condition + ) external onlyOwner { + if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); + if (_condition != 1 && _condition != 2) revert InvalidInput(); + marketIdToConditionType[_marketId] = _condition; + emit MarketConditionSet(_marketId, _condition); + } + + /*////////////////////////////////////////////////////////////// + PUBLIC + //////////////////////////////////////////////////////////////*/ + // Callback from settled assertion. + // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. + // Otherwise, assertedOutcomeId is reset and the market can be asserted again. + function assertionResolvedCallback( + bytes32 _assertionId, + bool _assertedTruthfully + ) public { + if (msg.sender != address(umaV3)) revert InvalidCaller(); + + marketAnswer.updatedAt = uint128(block.timestamp); + marketAnswer.answer = _assertedTruthfully ? 1 : 0; + marketAnswer.activeAssertion = false; + + emit AnswerResolved(_assertionId, _assertedTruthfully); + } + + // Dispute callback does nothing. + function assertionDisputedCallback(bytes32 assertionId) public {} + + function fetchAssertion( + uint256 _marketId + ) external returns (bytes32 assertionId) { + if (marketAnswer.activeAssertion == true) revert AssertionActive(); + // Configure bond and claim information + uint256 minimumBond = umaV3.getMinimumBond(address(currency)); + + uint256 requiredBond = marketAnswer.requiredBond; + uint256 bond = requiredBond > minimumBond ? requiredBond : minimumBond; + bytes memory claim = _composeClaim(); + + // Transfer bond from sender and request assertion + ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); + ERC20(currency).safeApprove(address(umaV3), bond); + assertionId = umaV3.assertTruth( + claim, + msg.sender, // Asserter + address(this), // Receive callback to this contract + address(0), // No sovereign security + assertionLiveness, + IERC20(currency), + bond, + defaultIdentifier, + bytes32(0) // No domain + ); + + marketAnswer.activeAssertion = true; + emit MarketAsserted(_marketId, assertedOutcome, assertionId); + } + + /** @notice Fetch the assertion state of the market + * @return bool If assertion is true or false for the market condition + */ + function checkAssertion() public view virtual returns (bool) { + MarketAnswer memory market = marketAnswer; + + if ((block.timestamp - market.updatedAt) > timeOut) + revert PriceTimedOut(); + + if (market.answer == 1) return true; + else return true; + } + + // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future + /** @notice Fetch price and return condition + * @param _strike Strike price + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256 _strike, + uint256 _marketId + ) public view virtual returns (bool, int256 price) { + uint256 conditionType = marketIdToConditionType[_marketId]; + bool condition = checkAssertion(); + + if (conditionType == 1) return (condition, price); + else if (conditionType == 2) return (condition, price); + else revert ConditionTypeNotSet(); + } + + // Unused + function getLatestPrice() external view returns (int256) {} + + function latestRoundData() + public + view + returns ( + uint80 roundId, + int256 price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + {} + + /*////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////*/ + function _composeClaim() internal view returns (bytes memory) { + return + abi.encodePacked( + "As of assertion timestamp ", + _toUtf8BytesUint(block.timestamp), + ", the described prediction market outcome is: ", + outcome, + ". The market description is: ", + assertionDescription + ); + } + + /** + * @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type. + * @dev This method is based off of this code: https://stackoverflow.com/a/65707309. + * @dev Pulled from UMA protocol packages: https://github.com/UMAprotocol/protocol/blob/9bfbbe98bed0ac7d9c924115018bb0e26987e2b5/packages/core/contracts/common/implementation/AncillaryData.sol + */ + function _toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { + if (x == 0) { + return "0"; + } + uint256 j = x; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len; + while (x != 0) { + k = k - 1; + uint8 temp = (48 + uint8(x - (x / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + x /= 10; + } + return bstr; + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + error ZeroAddress(); + error InvalidInput(); + error ConditionNotMet(); + error RoundIdOutdated(); + error PriceTimedOut(); + error ConditionTypeNotSet(); + error ConditionTypeSet(); + error InvalidCaller(); + error AssertionActive(); +} From 9f7d3c865795cd17a090eb27376acbe2b0f0e3ec Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Fri, 1 Sep 2023 18:02:07 +0100 Subject: [PATCH 04/25] tests: wip --- .../oracles/individual/UmaPriceProvider.sol | 62 ++-- test/V2/Helper.sol | 1 + .../oracles/individual/UmaPriceProvider.t.sol | 292 ++++++++++++++++++ 3 files changed, 333 insertions(+), 22 deletions(-) create mode 100644 test/V2/oracles/individual/UmaPriceProvider.t.sol diff --git a/src/v2/oracles/individual/UmaPriceProvider.sol b/src/v2/oracles/individual/UmaPriceProvider.sol index 932d2afe..8d4d1a18 100644 --- a/src/v2/oracles/individual/UmaPriceProvider.sol +++ b/src/v2/oracles/individual/UmaPriceProvider.sol @@ -16,11 +16,12 @@ contract UmaPriceProvider is Ownable, IConditionProvider { uint128 answer; uint256 requiredBond; bool activeAssertion; + bytes32 assertionId; } // Uma V3 uint64 public constant assertionLiveness = 7200; // 2 hours. - address immutable currency; // Currency used for all prediction markets + address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; @@ -29,13 +30,14 @@ contract UmaPriceProvider is Ownable, IConditionProvider { IVaultFactoryV2 public immutable vaultFactory; uint256 public immutable decimals; string public description; + bytes public assertionDescription; - string public outcome; + string public constant OUTCOME = "true"; string public assertedOutcome; - bytes public assertionDescription; - MarketAnswer public marketAnswer; mapping(uint256 => uint256) public marketIdToConditionType; + mapping(uint256 => MarketAnswer) public marketIdToAnswer; + mapping(bytes32 => uint256) public assertionIdToMarket; event MarketAsserted( uint256 marketId, @@ -47,33 +49,38 @@ contract UmaPriceProvider is Ownable, IConditionProvider { constructor( address _factory, + uint256 _decimals, + string memory _description, uint256 _timeOut, address _umaV3, - string memory _description, bytes32 _defaultIdentifier, - uint256 _decimals, - address _currency + address _currency, + bytes memory _assertionDescription ) { if (_factory == address(0)) revert ZeroAddress(); - if (_timeOut == 0) revert InvalidInput(); - if (_umaV3 == address(0)) revert ZeroAddress(); + if (_decimals == 0) revert InvalidInput(); if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) revert InvalidInput(); + if (_timeOut == 0) revert InvalidInput(); + if (_umaV3 == address(0)) revert ZeroAddress(); if ( keccak256(abi.encodePacked(_defaultIdentifier)) == + keccak256(abi.encodePacked(bytes32(""))) + ) revert InvalidInput(); + if (_currency == address(0)) revert ZeroAddress(); + if ( + keccak256(abi.encodePacked(_assertionDescription)) == keccak256(bytes("")) ) revert InvalidInput(); - if (_decimals == 0) revert InvalidInput(); - if (_currency == address(0)) revert InvalidInput(); vaultFactory = IVaultFactoryV2(_factory); + decimals = _decimals; + description = _description; timeOut = _timeOut; - umaV3 = IOptimisticOracleV3(_umaV3); - description = _description; - decimals = _decimals; - currency = _currency; defaultIdentifier = _defaultIdentifier; + currency = _currency; + assertionDescription = _assertionDescription; } /*////////////////////////////////////////////////////////////// @@ -101,6 +108,10 @@ contract UmaPriceProvider is Ownable, IConditionProvider { ) public { if (msg.sender != address(umaV3)) revert InvalidCaller(); + uint256 marketId = assertionIdToMarket[_assertionId]; + MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; + if (_assertionId != marketAnswer.assertionId) revert InvalidCallback(); + marketAnswer.updatedAt = uint128(block.timestamp); marketAnswer.answer = _assertedTruthfully ? 1 : 0; marketAnswer.activeAssertion = false; @@ -114,6 +125,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { function fetchAssertion( uint256 _marketId ) external returns (bytes32 assertionId) { + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; if (marketAnswer.activeAssertion == true) revert AssertionActive(); // Configure bond and claim information uint256 minimumBond = umaV3.getMinimumBond(address(currency)); @@ -137,20 +149,25 @@ contract UmaPriceProvider is Ownable, IConditionProvider { bytes32(0) // No domain ); - marketAnswer.activeAssertion = true; + assertionIdToMarket[assertionId] = _marketId; + marketIdToAnswer[_marketId].activeAssertion = true; + marketIdToAnswer[_marketId].assertionId = assertionId; + emit MarketAsserted(_marketId, assertedOutcome, assertionId); } /** @notice Fetch the assertion state of the market * @return bool If assertion is true or false for the market condition */ - function checkAssertion() public view virtual returns (bool) { - MarketAnswer memory market = marketAnswer; + function checkAssertion( + uint256 _marketId + ) public view virtual returns (bool) { + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; - if ((block.timestamp - market.updatedAt) > timeOut) + if ((block.timestamp - marketAnswer.updatedAt) > timeOut) revert PriceTimedOut(); - if (market.answer == 1) return true; + if (marketAnswer.answer == 1) return true; else return true; } @@ -165,7 +182,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { uint256 _marketId ) public view virtual returns (bool, int256 price) { uint256 conditionType = marketIdToConditionType[_marketId]; - bool condition = checkAssertion(); + bool condition = checkAssertion(_marketId); if (conditionType == 1) return (condition, price); else if (conditionType == 2) return (condition, price); @@ -196,7 +213,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { "As of assertion timestamp ", _toUtf8BytesUint(block.timestamp), ", the described prediction market outcome is: ", - outcome, + OUTCOME, ". The market description is: ", assertionDescription ); @@ -241,4 +258,5 @@ contract UmaPriceProvider is Ownable, IConditionProvider { error ConditionTypeSet(); error InvalidCaller(); error AssertionActive(); + error InvalidCallback(); } diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index 59e0d0ba..d4c4aa80 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -63,6 +63,7 @@ contract Helper is Test { address public constant RELAYER = address(0x55); address public UNDERLYING = address(0x123); address public TOKEN = address(new MintableToken("Token", "tkn")); + address public WETH_ADDRESS = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // keeper variables address public ops = 0xB3f5503f93d5Ef84b06993a1975B9D21B962892F; address public treasuryTask = 0xB2f34fd4C16e656163dADFeEaE4Ae0c1F13b140A; diff --git a/test/V2/oracles/individual/UmaPriceProvider.t.sol b/test/V2/oracles/individual/UmaPriceProvider.t.sol new file mode 100644 index 00000000..dc715c52 --- /dev/null +++ b/test/V2/oracles/individual/UmaPriceProvider.t.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Helper} from "../../Helper.sol"; +import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; +import { + UmaPriceProvider +} from "../../../../src/v2/oracles/individual/UmaPriceProvider.sol"; +import {TimeLock} from "../../../../src/v2/TimeLock.sol"; +import { + MockOracleAnswerZero, + MockOracleRoundOutdated, + MockOracleTimeOut +} from "../MockOracles.sol"; + +contract UmaPriceProviderTest is Helper { + uint256 public arbForkId; + VaultFactoryV2 public factory; + UmaPriceProvider public umaPriceProvider; + uint256 public marketId = 2; + + //////////////////////////////////////////////// + // HELPERS // + //////////////////////////////////////////////// + uint256 public UMA_DECIMALS = 18; + address public UMA_OO_V3 = address(0x123); + string public UMA_DESCRIPTION = "USDC"; + bytes32 public defaultIdentifier = bytes32("abc"); + bytes public assertionDescription; + + function setUp() public { + arbForkId = vm.createFork(ARBITRUM_RPC_URL); + vm.selectFork(arbForkId); + + // TODO: Should this be encoded or encode packed? + assertionDescription = abi.encode("USDC/USD price is less than 0.97"); + + address timelock = address(new TimeLock(ADMIN)); + factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); + umaPriceProvider = new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription + ); + + uint256 condition = 2; + umaPriceProvider.setConditionType(marketId, condition); + } + + //////////////////////////////////////////////// + // STATE // + //////////////////////////////////////////////// + function testUmaCreation() public { + assertEq(address(umaPriceProvider.vaultFactory()), address(factory)); + assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); + assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); + assertEq(umaPriceProvider.timeOut(), TIME_OUT); + assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); + assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); + assertEq(umaPriceProvider.currency(), WETH_ADDRESS); + } + + //////////////////////////////////////////////// + // FUNCTIONS // + //////////////////////////////////////////////// + function testLatestRoundDataUma() public { + ( + uint80 roundId, + int256 price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = umaPriceProvider.latestRoundData(); + assertTrue(price == 0); + assertTrue(roundId == 0); + assertTrue(startedAt == 0); + assertTrue(updatedAt == 0); + assertTrue(answeredInRound == 0); + } + + function testLatestPriceUma() public { + int256 price = umaPriceProvider.getLatestPrice(); + assertTrue(price == 0); + } + + function testConditionMetUma() public { + // Configuring the assertionInfo + // TODO: Need mock umaOOV3 to return an assertionId + vm.prank(UMA_OO_V3); + bytes32 assertionId = bytes32(""); + umaPriceProvider.assertionResolvedCallback(assertionId, true); + + (bool condition, int256 price) = umaPriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price != 0); + assertEq(condition, true); + } + + function testConditionOneMetUma() public { + uint256 conditionType = 1; + uint256 marketIdOne = 1; + umaPriceProvider.setConditionType(marketIdOne, conditionType); + + // Configuring the assertionInfo + vm.prank(UMA_OO_V3); + // TODO: Need mock umaOOV3 to return an assertionId + bytes32 assertionId = bytes32(""); + umaPriceProvider.assertionResolvedCallback(assertionId, true); + + (bool condition, int256 price) = umaPriceProvider.conditionMet( + 0.01 ether, + marketIdOne + ); + assertTrue(price != 0); + assertEq(condition, true); + } + + function testConditionTwoMetUma() public { + // Configuring the assertionInfo + vm.prank(UMA_OO_V3); + // TODO: Need mock umaOOV3 to return an assertionId + bytes32 assertionId = bytes32(""); + umaPriceProvider.assertionResolvedCallback(assertionId, true); + (bool condition, int256 price) = umaPriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price != 0); + assertEq(condition, true); + } + + //////////////////////////////////////////////// + // REVERT CASES // + //////////////////////////////////////////////// + + function testRevertConstructorInputsUma() public { + vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); + new UmaPriceProvider( + address(0), + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription + ); + + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + new UmaPriceProvider( + address(factory), + 0, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription + ); + + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + string(""), + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription + ); + + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + UMA_DESCRIPTION, + 0, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription + ); + + vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); + new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(0), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription + ); + + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + bytes32(""), + WETH_ADDRESS, + assertionDescription + ); + + vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); + new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + address(0), + assertionDescription + ); + + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + bytes("") + ); + } + + function testRevertConditionTypeSetUma() public { + vm.expectRevert(UmaPriceProvider.ConditionTypeSet.selector); + umaPriceProvider.setConditionType(2, 0); + } + + function testRevertInvalidInputConditionUma() public { + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + umaPriceProvider.setConditionType(0, 0); + + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + umaPriceProvider.setConditionType(0, 3); + } + + function testRevertInvalidCallerCallback() public { + vm.expectRevert(UmaPriceProvider.InvalidCaller.selector); + umaPriceProvider.assertionResolvedCallback(bytes32(""), true); + } + + function testRevertInvalidCallbackCallback() public { + vm.expectRevert(UmaPriceProvider.InvalidCallback.selector); + + bytes32 assertionId = bytes32("12"); + vm.prank(UMA_OO_V3); + umaPriceProvider.assertionResolvedCallback(assertionId, true); + } + + function testRevertAssertionActive() public { + uint256 marketId = 1; + // TODO: Need to create an active assertion + + vm.expectRevert(UmaPriceProvider.AssertionActive.selector); + umaPriceProvider.fetchAssertion(_marketId); + } + + function testRevertTimeOutUma() public { + address mockOracle = address( + new MockOracleTimeOut(block.timestamp, TIME_OUT) + ); + umaPriceProvider = new UmaPriceProvider( + address(factory), + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription + ); + vm.expectRevert(UmaPriceProvider.PriceTimedOut.selector); + umaPriceProvider.checkAssertion(123); + } +} From d89fc7331062faacdd75bab6d2c302fccaa51997 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Tue, 5 Sep 2023 18:27:34 -0300 Subject: [PATCH 05/25] feat: tests completed --- .../oracles/individual/UmaPriceProvider.sol | 64 ++-- test/V2/Helper.sol | 3 + test/V2/oracles/MockUma.sol | 42 +++ .../oracles/individual/UmaPriceProvider.t.sol | 274 +++++++++++++----- 4 files changed, 283 insertions(+), 100 deletions(-) create mode 100644 test/V2/oracles/MockUma.sol diff --git a/src/v2/oracles/individual/UmaPriceProvider.sol b/src/v2/oracles/individual/UmaPriceProvider.sol index 8d4d1a18..ffd2d292 100644 --- a/src/v2/oracles/individual/UmaPriceProvider.sol +++ b/src/v2/oracles/individual/UmaPriceProvider.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {IVaultFactoryV2} from "../../interfaces/IVaultFactoryV2.sol"; import {IConditionProvider} from "../../interfaces/IConditionProvider.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IOptimisticOracleV3} from "../../interfaces/IOptimisticOracleV3.sol"; @@ -12,52 +11,46 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract UmaPriceProvider is Ownable, IConditionProvider { using SafeTransferLib for ERC20; struct MarketAnswer { - uint128 updatedAt; - uint128 answer; - uint256 requiredBond; bool activeAssertion; + uint128 updatedAt; + uint8 answer; bytes32 assertionId; } + string public constant OUTCOME_1 = "true"; + string public constant OUTCOME_2 = "false"; + // Uma V3 uint64 public constant assertionLiveness = 7200; // 2 hours. address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; + uint256 public immutable requiredBond; // Bond required to assert on a market // Market info uint256 public immutable timeOut; - IVaultFactoryV2 public immutable vaultFactory; uint256 public immutable decimals; string public description; bytes public assertionDescription; - string public constant OUTCOME = "true"; - string public assertedOutcome; - mapping(uint256 => uint256) public marketIdToConditionType; mapping(uint256 => MarketAnswer) public marketIdToAnswer; mapping(bytes32 => uint256) public assertionIdToMarket; - event MarketAsserted( - uint256 marketId, - string assertedOutcome, - bytes32 assertionId - ); - event AnswerResolved(bytes32 assertionId, bool assertion); + event MarketAsserted(uint256 marketId, bytes32 assertionId); + event AssertionResolved(bytes32 assertionId, bool assertion); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); constructor( - address _factory, uint256 _decimals, string memory _description, uint256 _timeOut, address _umaV3, bytes32 _defaultIdentifier, address _currency, - bytes memory _assertionDescription + bytes memory _assertionDescription, + uint256 _requiredBond ) { - if (_factory == address(0)) revert ZeroAddress(); if (_decimals == 0) revert InvalidInput(); if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) revert InvalidInput(); @@ -72,8 +65,8 @@ contract UmaPriceProvider is Ownable, IConditionProvider { keccak256(abi.encodePacked(_assertionDescription)) == keccak256(bytes("")) ) revert InvalidInput(); + if (_requiredBond == 0) revert InvalidInput(); - vaultFactory = IVaultFactoryV2(_factory); decimals = _decimals; description = _description; timeOut = _timeOut; @@ -81,6 +74,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { defaultIdentifier = _defaultIdentifier; currency = _currency; assertionDescription = _assertionDescription; + requiredBond = _requiredBond; } /*////////////////////////////////////////////////////////////// @@ -97,7 +91,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { } /*////////////////////////////////////////////////////////////// - PUBLIC + EXTERNAL //////////////////////////////////////////////////////////////*/ // Callback from settled assertion. // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. @@ -105,23 +99,21 @@ contract UmaPriceProvider is Ownable, IConditionProvider { function assertionResolvedCallback( bytes32 _assertionId, bool _assertedTruthfully - ) public { + ) external { if (msg.sender != address(umaV3)) revert InvalidCaller(); uint256 marketId = assertionIdToMarket[_assertionId]; MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; - if (_assertionId != marketAnswer.assertionId) revert InvalidCallback(); + if (marketAnswer.activeAssertion == false) revert AssertionInactive(); marketAnswer.updatedAt = uint128(block.timestamp); marketAnswer.answer = _assertedTruthfully ? 1 : 0; marketAnswer.activeAssertion = false; + marketIdToAnswer[marketId] = marketAnswer; - emit AnswerResolved(_assertionId, _assertedTruthfully); + emit AssertionResolved(_assertionId, _assertedTruthfully); } - // Dispute callback does nothing. - function assertionDisputedCallback(bytes32 assertionId) public {} - function fetchAssertion( uint256 _marketId ) external returns (bytes32 assertionId) { @@ -130,9 +122,11 @@ contract UmaPriceProvider is Ownable, IConditionProvider { // Configure bond and claim information uint256 minimumBond = umaV3.getMinimumBond(address(currency)); - uint256 requiredBond = marketAnswer.requiredBond; - uint256 bond = requiredBond > minimumBond ? requiredBond : minimumBond; - bytes memory claim = _composeClaim(); + uint256 reqBond = requiredBond; + uint256 bond = reqBond > minimumBond ? reqBond : minimumBond; + + uint256 conditionType = marketIdToConditionType[_marketId]; + bytes memory claim = _composeClaim(conditionType); // Transfer bond from sender and request assertion ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); @@ -149,11 +143,12 @@ contract UmaPriceProvider is Ownable, IConditionProvider { bytes32(0) // No domain ); + // TODO: Do we need this? assertionIdToMarket[assertionId] = _marketId; marketIdToAnswer[_marketId].activeAssertion = true; marketIdToAnswer[_marketId].assertionId = assertionId; - emit MarketAsserted(_marketId, assertedOutcome, assertionId); + emit MarketAsserted(_marketId, assertionId); } /** @notice Fetch the assertion state of the market @@ -168,7 +163,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { revert PriceTimedOut(); if (marketAnswer.answer == 1) return true; - else return true; + else return false; } // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future @@ -207,13 +202,15 @@ contract UmaPriceProvider is Ownable, IConditionProvider { /*////////////////////////////////////////////////////////////// INTERNAL //////////////////////////////////////////////////////////////*/ - function _composeClaim() internal view returns (bytes memory) { + function _composeClaim( + uint256 _conditionType + ) internal view returns (bytes memory) { return abi.encodePacked( "As of assertion timestamp ", _toUtf8BytesUint(block.timestamp), ", the described prediction market outcome is: ", - OUTCOME, + _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, ". The market description is: ", assertionDescription ); @@ -251,12 +248,11 @@ contract UmaPriceProvider is Ownable, IConditionProvider { //////////////////////////////////////////////////////////////*/ error ZeroAddress(); error InvalidInput(); - error ConditionNotMet(); - error RoundIdOutdated(); error PriceTimedOut(); error ConditionTypeNotSet(); error ConditionTypeSet(); error InvalidCaller(); error AssertionActive(); + error AssertionInactive(); error InvalidCallback(); } diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index d4c4aa80..c7edb456 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -7,6 +7,9 @@ import {VaultV2} from "../../src/v2/VaultV2.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract Helper is Test { + event MarketAsserted(uint256 marketId, bytes32 assertionId); + event AssertionResolved(bytes32 assertionId, bool assertion); + uint256 public constant STRIKE = 1000000000000000000; uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; uint256 public constant COLLATERAL_MINUS_FEES_DIV10 = 2198999999839855145; diff --git a/test/V2/oracles/MockUma.sol b/test/V2/oracles/MockUma.sol new file mode 100644 index 00000000..d37cc310 --- /dev/null +++ b/test/V2/oracles/MockUma.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IUmaPriceProvider { + function assertionResolvedCallback( + bytes32 _assertionId, + bool _assertedTruthfully + ) external; +} + +contract MockUma { + function assertionResolvedCallback( + address _receiver, + bytes32 _assertionId, + bool _assertedTruthfully + ) external { + IUmaPriceProvider(_receiver).assertionResolvedCallback( + _assertionId, + _assertedTruthfully + ); + } + + function assertTruth( + bytes calldata claim, + address asserter, + address callBackAddress, + address sovereignSecurity, + uint64 assertionLiveness, + IERC20 currency, + uint256 bond, + bytes32 defaultIdentifier, + bytes32 domain + ) external payable returns (bytes32 assertionId) { + assertionId = bytes32(abi.encode(0x12)); + } + + function getMinimumBond(address currency) external pure returns (uint256) { + return 1e6; + } +} diff --git a/test/V2/oracles/individual/UmaPriceProvider.t.sol b/test/V2/oracles/individual/UmaPriceProvider.t.sol index dc715c52..eefd82cd 100644 --- a/test/V2/oracles/individual/UmaPriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaPriceProvider.t.sol @@ -12,12 +12,15 @@ import { MockOracleRoundOutdated, MockOracleTimeOut } from "../MockOracles.sol"; +import {MockUma} from "../MockUma.sol"; +import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; contract UmaPriceProviderTest is Helper { uint256 public arbForkId; VaultFactoryV2 public factory; UmaPriceProvider public umaPriceProvider; uint256 public marketId = 2; + ERC20 public wethAsset; //////////////////////////////////////////////// // HELPERS // @@ -25,12 +28,14 @@ contract UmaPriceProviderTest is Helper { uint256 public UMA_DECIMALS = 18; address public UMA_OO_V3 = address(0x123); string public UMA_DESCRIPTION = "USDC"; + uint256 public REQUIRED_BOND = 1e6; bytes32 public defaultIdentifier = bytes32("abc"); bytes public assertionDescription; function setUp() public { arbForkId = vm.createFork(ARBITRUM_RPC_URL); vm.selectFork(arbForkId); + wethAsset = ERC20(WETH_ADDRESS); // TODO: Should this be encoded or encode packed? assertionDescription = abi.encode("USDC/USD price is less than 0.97"); @@ -38,14 +43,14 @@ contract UmaPriceProviderTest is Helper { address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); umaPriceProvider = new UmaPriceProvider( - address(factory), UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, defaultIdentifier, WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND ); uint256 condition = 2; @@ -56,13 +61,18 @@ contract UmaPriceProviderTest is Helper { // STATE // //////////////////////////////////////////////// function testUmaCreation() public { - assertEq(address(umaPriceProvider.vaultFactory()), address(factory)); - assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); - assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); - assertEq(umaPriceProvider.timeOut(), TIME_OUT); + assertEq(umaPriceProvider.assertionLiveness(), 7200); + assertEq(umaPriceProvider.currency(), WETH_ADDRESS); assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); - assertEq(umaPriceProvider.currency(), WETH_ADDRESS); + assertEq(umaPriceProvider.requiredBond(), REQUIRED_BOND); + + assertEq(umaPriceProvider.timeOut(), TIME_OUT); + assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); + assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); + assertEq(umaPriceProvider.assertionDescription(), assertionDescription); + + assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); } //////////////////////////////////////////////// @@ -88,153 +98,266 @@ contract UmaPriceProviderTest is Helper { assertTrue(price == 0); } - function testConditionMetUma() public { + function testConditionOneMetUma() public { + MockUma mockUma = new MockUma(); + + // Deploying new umaPriceProvider + umaPriceProvider = new UmaPriceProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 1); + // Configuring the assertionInfo - // TODO: Need mock umaOOV3 to return an assertionId - vm.prank(UMA_OO_V3); - bytes32 assertionId = bytes32(""); - umaPriceProvider.assertionResolvedCallback(assertionId, true); + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + vm.expectEmit(true, true, false, false); + emit MarketAsserted(marketId, bytes32(abi.encode(0x12))); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + + // Checking assertion links to marketId + uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); + assertEq(_marketId, marketId); + + // Checking marketId info is correct + ( + bool activeAssertion, + uint128 updatedAt, + uint8 answer, + bytes32 assertionIdReturned + ) = umaPriceProvider.marketIdToAnswer(_marketId); + assertEq(activeAssertion, true); + assertEq(updatedAt, uint128(0)); + assertEq(answer, 0); + assertEq(assertionIdReturned, _assertionId); + + vm.expectEmit(true, true, false, false); + emit AssertionResolved(_assertionId, true); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + // Checking resolved callback info + ( + activeAssertion, + updatedAt, + answer, + assertionIdReturned + ) = umaPriceProvider.marketIdToAnswer(_marketId); + assertEq(activeAssertion, false); + assertEq(updatedAt, uint128(block.timestamp)); + assertEq(answer, 1); (bool condition, int256 price) = umaPriceProvider.conditionMet( 2 ether, marketId ); - assertTrue(price != 0); + assertTrue(price == 0); assertEq(condition, true); } - function testConditionOneMetUma() public { - uint256 conditionType = 1; - uint256 marketIdOne = 1; - umaPriceProvider.setConditionType(marketIdOne, conditionType); + function testConditionTwoMetUma() public { + MockUma mockUma = new MockUma(); + + // Deploying new umaPriceProvider + umaPriceProvider = new UmaPriceProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); // Configuring the assertionInfo - vm.prank(UMA_OO_V3); - // TODO: Need mock umaOOV3 to return an assertionId - bytes32 assertionId = bytes32(""); - umaPriceProvider.assertionResolvedCallback(assertionId, true); + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); (bool condition, int256 price) = umaPriceProvider.conditionMet( - 0.01 ether, - marketIdOne + 2 ether, + marketId ); - assertTrue(price != 0); + assertTrue(price == 0); assertEq(condition, true); } - function testConditionTwoMetUma() public { + function testCheckAssertionTrue() public { + MockUma mockUma = new MockUma(); + + // Deploying new umaPriceProvider + umaPriceProvider = new UmaPriceProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + // Configuring the assertionInfo - vm.prank(UMA_OO_V3); - // TODO: Need mock umaOOV3 to return an assertionId - bytes32 assertionId = bytes32(""); - umaPriceProvider.assertionResolvedCallback(assertionId, true); - (bool condition, int256 price) = umaPriceProvider.conditionMet( - 2 ether, - marketId + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true ); - assertTrue(price != 0); + + bool condition = umaPriceProvider.checkAssertion(marketId); assertEq(condition, true); } - //////////////////////////////////////////////// - // REVERT CASES // - //////////////////////////////////////////////// + function testCheckAssertionFalse() public { + MockUma mockUma = new MockUma(); - function testRevertConstructorInputsUma() public { - vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); - new UmaPriceProvider( - address(0), + // Deploying new umaPriceProvider + umaPriceProvider = new UmaPriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, - UMA_OO_V3, + address(mockUma), defaultIdentifier, WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + false ); + bool condition = umaPriceProvider.checkAssertion(marketId); + assertEq(condition, false); + } + + //////////////////////////////////////////////// + // REVERT CASES // + //////////////////////////////////////////////// + + function testRevertConstructorInputsUma() public { vm.expectRevert(UmaPriceProvider.InvalidInput.selector); new UmaPriceProvider( - address(factory), 0, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, defaultIdentifier, WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND ); vm.expectRevert(UmaPriceProvider.InvalidInput.selector); new UmaPriceProvider( - address(factory), UMA_DECIMALS, string(""), TIME_OUT, UMA_OO_V3, defaultIdentifier, WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND ); vm.expectRevert(UmaPriceProvider.InvalidInput.selector); new UmaPriceProvider( - address(factory), UMA_DECIMALS, UMA_DESCRIPTION, 0, UMA_OO_V3, defaultIdentifier, WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND ); vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); new UmaPriceProvider( - address(factory), UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, address(0), defaultIdentifier, WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND ); vm.expectRevert(UmaPriceProvider.InvalidInput.selector); new UmaPriceProvider( - address(factory), UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, bytes32(""), WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND ); vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); new UmaPriceProvider( - address(factory), UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, defaultIdentifier, address(0), - assertionDescription + assertionDescription, + REQUIRED_BOND ); vm.expectRevert(UmaPriceProvider.InvalidInput.selector); new UmaPriceProvider( - address(factory), UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, defaultIdentifier, WETH_ADDRESS, - bytes("") + bytes(""), + REQUIRED_BOND + ); + + vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + new UmaPriceProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + 0 ); } @@ -256,20 +379,39 @@ contract UmaPriceProviderTest is Helper { umaPriceProvider.assertionResolvedCallback(bytes32(""), true); } - function testRevertInvalidCallbackCallback() public { - vm.expectRevert(UmaPriceProvider.InvalidCallback.selector); - - bytes32 assertionId = bytes32("12"); + function testRevertAssertionInactive() public { vm.prank(UMA_OO_V3); - umaPriceProvider.assertionResolvedCallback(assertionId, true); + + vm.expectRevert(UmaPriceProvider.AssertionInactive.selector); + umaPriceProvider.assertionResolvedCallback( + bytes32(abi.encode(0x12)), + true + ); } function testRevertAssertionActive() public { - uint256 marketId = 1; - // TODO: Need to create an active assertion + MockUma mockUma = new MockUma(); + + // Deploying new umaPriceProvider + umaPriceProvider = new UmaPriceProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 1); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + umaPriceProvider.fetchAssertion(marketId); vm.expectRevert(UmaPriceProvider.AssertionActive.selector); - umaPriceProvider.fetchAssertion(_marketId); + umaPriceProvider.fetchAssertion(marketId); } function testRevertTimeOutUma() public { @@ -277,14 +419,14 @@ contract UmaPriceProviderTest is Helper { new MockOracleTimeOut(block.timestamp, TIME_OUT) ); umaPriceProvider = new UmaPriceProvider( - address(factory), UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, defaultIdentifier, WETH_ADDRESS, - assertionDescription + assertionDescription, + REQUIRED_BOND ); vm.expectRevert(UmaPriceProvider.PriceTimedOut.selector); umaPriceProvider.checkAssertion(123); From fbba6571e981d7df4b1290d8530e505e8ac8e042 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Wed, 6 Sep 2023 10:12:06 -0300 Subject: [PATCH 06/25] tests: comments removed and balance check --- src/v2/oracles/individual/UmaPriceProvider.sol | 13 +++++-------- test/V2/oracles/MockUma.sol | 1 + test/V2/oracles/individual/UmaPriceProvider.t.sol | 9 +++++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/v2/oracles/individual/UmaPriceProvider.sol b/src/v2/oracles/individual/UmaPriceProvider.sol index ffd2d292..25a9865b 100644 --- a/src/v2/oracles/individual/UmaPriceProvider.sol +++ b/src/v2/oracles/individual/UmaPriceProvider.sol @@ -21,7 +21,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { string public constant OUTCOME_2 = "false"; // Uma V3 - uint64 public constant assertionLiveness = 7200; // 2 hours. + uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; @@ -119,14 +119,12 @@ contract UmaPriceProvider is Ownable, IConditionProvider { ) external returns (bytes32 assertionId) { MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; if (marketAnswer.activeAssertion == true) revert AssertionActive(); + // Configure bond and claim information uint256 minimumBond = umaV3.getMinimumBond(address(currency)); - uint256 reqBond = requiredBond; uint256 bond = reqBond > minimumBond ? reqBond : minimumBond; - - uint256 conditionType = marketIdToConditionType[_marketId]; - bytes memory claim = _composeClaim(conditionType); + bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); // Transfer bond from sender and request assertion ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); @@ -136,14 +134,13 @@ contract UmaPriceProvider is Ownable, IConditionProvider { msg.sender, // Asserter address(this), // Receive callback to this contract address(0), // No sovereign security - assertionLiveness, + ASSERTION_LIVENESS, IERC20(currency), bond, defaultIdentifier, bytes32(0) // No domain ); - // TODO: Do we need this? assertionIdToMarket[assertionId] = _marketId; marketIdToAnswer[_marketId].activeAssertion = true; marketIdToAnswer[_marketId].assertionId = assertionId; @@ -184,7 +181,7 @@ contract UmaPriceProvider is Ownable, IConditionProvider { else revert ConditionTypeNotSet(); } - // Unused + // NOTE: Unused logic as no prices returned by Uma function getLatestPrice() external view returns (int256) {} function latestRoundData() diff --git a/test/V2/oracles/MockUma.sol b/test/V2/oracles/MockUma.sol index d37cc310..a37f3a7a 100644 --- a/test/V2/oracles/MockUma.sol +++ b/test/V2/oracles/MockUma.sol @@ -33,6 +33,7 @@ contract MockUma { bytes32 defaultIdentifier, bytes32 domain ) external payable returns (bytes32 assertionId) { + currency.transferFrom(msg.sender, address(this), bond); assertionId = bytes32(abi.encode(0x12)); } diff --git a/test/V2/oracles/individual/UmaPriceProvider.t.sol b/test/V2/oracles/individual/UmaPriceProvider.t.sol index eefd82cd..d84c15f1 100644 --- a/test/V2/oracles/individual/UmaPriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaPriceProvider.t.sol @@ -61,10 +61,10 @@ contract UmaPriceProviderTest is Helper { // STATE // //////////////////////////////////////////////// function testUmaCreation() public { - assertEq(umaPriceProvider.assertionLiveness(), 7200); + assertEq(umaPriceProvider.ASSERTION_LIVENESS(), 7200); assertEq(umaPriceProvider.currency(), WETH_ADDRESS); - assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); + assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); assertEq(umaPriceProvider.requiredBond(), REQUIRED_BOND); assertEq(umaPriceProvider.timeOut(), TIME_OUT); @@ -118,13 +118,14 @@ contract UmaPriceProviderTest is Helper { deal(WETH_ADDRESS, address(this), 1e18); wethAsset.approve(address(umaPriceProvider), 1e18); - vm.expectEmit(true, true, false, false); + vm.expectEmit(true, false, false, true); emit MarketAsserted(marketId, bytes32(abi.encode(0x12))); bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); // Checking assertion links to marketId uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); assertEq(_marketId, marketId); + assertEq(wethAsset.balanceOf(address(mockUma)), 1e6); // Checking marketId info is correct ( @@ -138,7 +139,7 @@ contract UmaPriceProviderTest is Helper { assertEq(answer, 0); assertEq(assertionIdReturned, _assertionId); - vm.expectEmit(true, true, false, false); + vm.expectEmit(true, false, false, true); emit AssertionResolved(_assertionId, true); mockUma.assertionResolvedCallback( address(umaPriceProvider), From dbcf6705b1067fe8e1262b6d6279138111ed9be1 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Wed, 20 Sep 2023 12:04:45 -0600 Subject: [PATCH 07/25] fix: removed unused price functions --- .../oracles/individual/UmaPriceProvider.sol | 17 +---------- test/V2/oracles/MockUma.sol | 28 ++++++++++++++++++- .../oracles/individual/UmaPriceProvider.t.sol | 23 --------------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/v2/oracles/individual/UmaPriceProvider.sol b/src/v2/oracles/individual/UmaPriceProvider.sol index 25a9865b..e41f06d2 100644 --- a/src/v2/oracles/individual/UmaPriceProvider.sol +++ b/src/v2/oracles/individual/UmaPriceProvider.sol @@ -8,7 +8,7 @@ import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -contract UmaPriceProvider is Ownable, IConditionProvider { +contract UmaPriceProvider is Ownable { using SafeTransferLib for ERC20; struct MarketAnswer { bool activeAssertion; @@ -181,21 +181,6 @@ contract UmaPriceProvider is Ownable, IConditionProvider { else revert ConditionTypeNotSet(); } - // NOTE: Unused logic as no prices returned by Uma - function getLatestPrice() external view returns (int256) {} - - function latestRoundData() - public - view - returns ( - uint80 roundId, - int256 price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) - {} - /*////////////////////////////////////////////////////////////// INTERNAL //////////////////////////////////////////////////////////////*/ diff --git a/test/V2/oracles/MockUma.sol b/test/V2/oracles/MockUma.sol index a37f3a7a..26e39294 100644 --- a/test/V2/oracles/MockUma.sol +++ b/test/V2/oracles/MockUma.sol @@ -35,9 +35,35 @@ contract MockUma { ) external payable returns (bytes32 assertionId) { currency.transferFrom(msg.sender, address(this), bond); assertionId = bytes32(abi.encode(0x12)); + + removeUnusedWarning( + claim, + asserter, + callBackAddress, + sovereignSecurity, + assertionLiveness, + defaultIdentifier, + domain + ); + } + + function removeUnusedWarning( + bytes calldata claim, + address asserter, + address callBackAddress, + address sovereignSecurity, + uint64 assertionLiveness, + bytes32 defaultIdentifier, + bytes32 domain + ) internal pure { + asserter = callBackAddress; + sovereignSecurity = callBackAddress; + assertionLiveness += 1; + defaultIdentifier = domain; + domain = keccak256(claim); } - function getMinimumBond(address currency) external pure returns (uint256) { + function getMinimumBond() external pure returns (uint256) { return 1e6; } } diff --git a/test/V2/oracles/individual/UmaPriceProvider.t.sol b/test/V2/oracles/individual/UmaPriceProvider.t.sol index d84c15f1..e0af120b 100644 --- a/test/V2/oracles/individual/UmaPriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaPriceProvider.t.sol @@ -78,26 +78,6 @@ contract UmaPriceProviderTest is Helper { //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// - function testLatestRoundDataUma() public { - ( - uint80 roundId, - int256 price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) = umaPriceProvider.latestRoundData(); - assertTrue(price == 0); - assertTrue(roundId == 0); - assertTrue(startedAt == 0); - assertTrue(updatedAt == 0); - assertTrue(answeredInRound == 0); - } - - function testLatestPriceUma() public { - int256 price = umaPriceProvider.getLatestPrice(); - assertTrue(price == 0); - } - function testConditionOneMetUma() public { MockUma mockUma = new MockUma(); @@ -416,9 +396,6 @@ contract UmaPriceProviderTest is Helper { } function testRevertTimeOutUma() public { - address mockOracle = address( - new MockOracleTimeOut(block.timestamp, TIME_OUT) - ); umaPriceProvider = new UmaPriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, From 7a4f6c1c58beeefa4d0403a8522670f020515f54 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 21 Sep 2023 16:43:02 -0600 Subject: [PATCH 08/25] feat: uma deploy script added --- script/v2/V2DeployContracts.s.sol | 21 +++++++++++++++++++++ test/V2/oracles/MockUma.sol | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index 1e0c4ce8..fc7d3d5a 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -14,6 +14,7 @@ import "../../src/v2/oracles/individual/RedstonePriceProvider.sol"; import "../../src/v2/oracles/individual/DIAPriceProvider.sol"; import "../../src/v2/oracles/individual/CVIPriceProvider.sol"; import "../../src/v2/oracles/individual/GdaiPriceProvider.sol"; +import "../../src/v2/oracles/individual/UmaPriceProvider.sol"; import "../../src/v2/TimeLock.sol"; import "./V2Helper.sol"; import { @@ -126,6 +127,25 @@ contract V2DeployContracts is Script, HelperV2 { // address diaOracleV2 = 0xd041478644048d9281f88558E6088e9da97df624; // DIAPriceProvider diaPriceProvider = new DIAPriceProvider(diaOracleV2); + uint256 timeOut = 2 hours; + uint256 umaDecimals = 18; + address umaOOV3 = address(0x123); + string memory umaDescription = "USDC"; + uint256 requiredBond = 1e6; + bytes32 defaultIdentifier = bytes32("abc"); + bytes memory assertionDescription = "abc"; + address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS + UmaPriceProvider umaPriceProvider = new UmaPriceProvider( + umaDecimals, + umaDescription, + timeOut, + umaOOV3, + defaultIdentifier, + currency, + assertionDescription, + requiredBond + ); + // vaultFactory.whitelistController(address(controller)); // KeeperV2 resolveKeeper = new KeeperV2( // payable(addresses.gelatoOpsV2), @@ -167,6 +187,7 @@ contract V2DeployContracts is Script, HelperV2 { console2.log("Gdai Price Provider", address(gdaiPriceProvider)); // console2.log("CVI Price Provider", address(cviPriceProvider)); // console2.log("Dia Price Provider", address(diaPriceProvider)); + console2.log("Uma Price Provider", address(umaPriceProvider)); // console2.log("resolveKeeper address", address(resolveKeeper)); // console2.log("resolveKeeperGenericController address", address(resolveKeeperGenericController)); diff --git a/test/V2/oracles/MockUma.sol b/test/V2/oracles/MockUma.sol index 26e39294..d7d82385 100644 --- a/test/V2/oracles/MockUma.sol +++ b/test/V2/oracles/MockUma.sol @@ -63,7 +63,9 @@ contract MockUma { domain = keccak256(claim); } - function getMinimumBond() external pure returns (uint256) { + function getMinimumBond(address addr) external pure returns (uint256) { + address muteWarning = addr; + addr = muteWarning; return 1e6; } } From bcc78dbe1768392b6bcadbb60b93ddf293404665 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Fri, 22 Sep 2023 12:52:51 -0600 Subject: [PATCH 09/25] feat: uma split into v1(true at timestamp) and v2 (true after timestamp - req weekly config) --- script/v2/V2DeployContracts.s.sol | 7 +- ...riceProvider.sol => UmaAssertProvider.sol} | 27 +- .../individual/UmaAssertProviderV2.sol | 267 +++++++++++ ...Provider.t.sol => UmaAssertProvider.t.sol} | 82 ++-- .../individual/UmaAssertProviderV2.t.sol | 424 ++++++++++++++++++ 5 files changed, 759 insertions(+), 48 deletions(-) rename src/v2/oracles/individual/{UmaPriceProvider.sol => UmaAssertProvider.sol} (85%) create mode 100644 src/v2/oracles/individual/UmaAssertProviderV2.sol rename test/V2/oracles/individual/{UmaPriceProvider.t.sol => UmaAssertProvider.t.sol} (84%) create mode 100644 test/V2/oracles/individual/UmaAssertProviderV2.t.sol diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index fc7d3d5a..7300e5d9 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -14,7 +14,7 @@ import "../../src/v2/oracles/individual/RedstonePriceProvider.sol"; import "../../src/v2/oracles/individual/DIAPriceProvider.sol"; import "../../src/v2/oracles/individual/CVIPriceProvider.sol"; import "../../src/v2/oracles/individual/GdaiPriceProvider.sol"; -import "../../src/v2/oracles/individual/UmaPriceProvider.sol"; +import "../../src/v2/oracles/individual/UmaAssertProvider.sol"; import "../../src/v2/TimeLock.sol"; import "./V2Helper.sol"; import { @@ -133,9 +133,10 @@ contract V2DeployContracts is Script, HelperV2 { string memory umaDescription = "USDC"; uint256 requiredBond = 1e6; bytes32 defaultIdentifier = bytes32("abc"); - bytes memory assertionDescription = "abc"; + bytes + memory assertionDescription = "The USDC/USD exchange is above 0.997"; address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS - UmaPriceProvider umaPriceProvider = new UmaPriceProvider( + UmaAssertProvider umaPriceProvider = new UmaAssertProvider( umaDecimals, umaDescription, timeOut, diff --git a/src/v2/oracles/individual/UmaPriceProvider.sol b/src/v2/oracles/individual/UmaAssertProvider.sol similarity index 85% rename from src/v2/oracles/individual/UmaPriceProvider.sol rename to src/v2/oracles/individual/UmaAssertProvider.sol index e41f06d2..99818dc5 100644 --- a/src/v2/oracles/individual/UmaPriceProvider.sol +++ b/src/v2/oracles/individual/UmaAssertProvider.sol @@ -8,7 +8,9 @@ import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -contract UmaPriceProvider is Ownable { +/// @notice Assertion provider where the condition can be checked without a required begin time e.g. 1 PEPE > $1000 or BTC market > 2x ETH market cap +/// @dev This provider would not work if you needed to check if x happened between time y and z +contract UmaAssertProvider is Ownable { using SafeTransferLib for ERC20; struct MarketAnswer { bool activeAssertion; @@ -17,8 +19,8 @@ contract UmaPriceProvider is Ownable { bytes32 assertionId; } - string public constant OUTCOME_1 = "true"; - string public constant OUTCOME_2 = "false"; + string public constant OUTCOME_1 = "true. "; + string public constant OUTCOME_2 = "false. "; // Uma V3 uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. @@ -41,6 +43,16 @@ contract UmaPriceProvider is Ownable { event AssertionResolved(bytes32 assertionId, bool assertion); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + /** + @param _decimals is decimals for the provider maker if relevant + @param _description is for the price provider market + @param _timeOut is the max time between receiving callback and resolving market condition + @param _umaV3 is the V3 Uma Optimistic Oracle + @param _defaultIdentifier is UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. + @param _currency is currency used to post the bond + @param _assertionDescription is description used for the market + @param _requiredBond is bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This must be >= getMinimumBond(address(currency)). + */ constructor( uint256 _decimals, string memory _description, @@ -184,6 +196,12 @@ contract UmaPriceProvider is Ownable { /*////////////////////////////////////////////////////////////// INTERNAL //////////////////////////////////////////////////////////////*/ + /** + @param _conditionType is the condition type for the market + @dev encode claim would look like: "As of assertion timestamp , " + Where inputs could be: "As of assertion timestamp 1625097600, <0.997>" + @return bytes for the claim + */ function _composeClaim( uint256 _conditionType ) internal view returns (bytes memory) { @@ -191,9 +209,8 @@ contract UmaPriceProvider is Ownable { abi.encodePacked( "As of assertion timestamp ", _toUtf8BytesUint(block.timestamp), - ", the described prediction market outcome is: ", + ", the following statement is", _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, - ". The market description is: ", assertionDescription ); } diff --git a/src/v2/oracles/individual/UmaAssertProviderV2.sol b/src/v2/oracles/individual/UmaAssertProviderV2.sol new file mode 100644 index 00000000..75713c55 --- /dev/null +++ b/src/v2/oracles/individual/UmaAssertProviderV2.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IConditionProvider} from "../../interfaces/IConditionProvider.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IOptimisticOracleV3} from "../../interfaces/IOptimisticOracleV3.sol"; +import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol"; +import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @notice This provider is build to work where you need to define between x and y e.g. there was a hack between Aug31st +contract UmaAssertProviderV2 is Ownable { + using SafeTransferLib for ERC20; + struct MarketAnswer { + bool activeAssertion; + uint128 updatedAt; + uint8 answer; + bytes32 assertionId; + } + + string public constant OUTCOME_1 = "true. "; + string public constant OUTCOME_2 = "false. "; + + // Uma V3 + uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. + address public immutable currency; // Currency used for all prediction markets + bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. + IOptimisticOracleV3 public immutable umaV3; + uint256 public immutable requiredBond; // Bond required to assert on a market + + // Market info + uint256 public immutable timeOut; + uint256 public immutable decimals; + string public description; + bytes public assertionDescription; + uint256 public coverageStart; + + mapping(uint256 => uint256) public marketIdToConditionType; + mapping(uint256 => MarketAnswer) public marketIdToAnswer; + mapping(bytes32 => uint256) public assertionIdToMarket; + + event MarketAsserted(uint256 marketId, bytes32 assertionId); + event AssertionResolved(bytes32 assertionId, bool assertion); + event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + event CoverageStartUpdated(uint256 startTime); + + /** + @param _decimals is decimals for the provider maker if relevant + @param _description is for the price provider market + @param _timeOut is the max time between receiving callback and resolving market condition + @param _umaV3 is the V3 Uma Optimistic Oracle + @param _defaultIdentifier is UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. + @param _currency is currency used to post the bond + @param _assertionDescription is description used for the market + @param _requiredBond is bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This must be >= getMinimumBond(address(currency)). + */ + constructor( + uint256 _decimals, + string memory _description, + uint256 _timeOut, + address _umaV3, + bytes32 _defaultIdentifier, + address _currency, + bytes memory _assertionDescription, + uint256 _requiredBond + ) { + if (_decimals == 0) revert InvalidInput(); + if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) + revert InvalidInput(); + if (_timeOut == 0) revert InvalidInput(); + if (_umaV3 == address(0)) revert ZeroAddress(); + if ( + keccak256(abi.encodePacked(_defaultIdentifier)) == + keccak256(abi.encodePacked(bytes32(""))) + ) revert InvalidInput(); + if (_currency == address(0)) revert ZeroAddress(); + if ( + keccak256(abi.encodePacked(_assertionDescription)) == + keccak256(bytes("")) + ) revert InvalidInput(); + if (_requiredBond == 0) revert InvalidInput(); + + decimals = _decimals; + description = _description; + timeOut = _timeOut; + umaV3 = IOptimisticOracleV3(_umaV3); + defaultIdentifier = _defaultIdentifier; + currency = _currency; + assertionDescription = _assertionDescription; + requiredBond = _requiredBond; + coverageStart = block.timestamp; + } + + /*////////////////////////////////////////////////////////////// + ADMIN + //////////////////////////////////////////////////////////////*/ + function setConditionType( + uint256 _marketId, + uint256 _condition + ) external onlyOwner { + if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); + if (_condition != 1 && _condition != 2) revert InvalidInput(); + marketIdToConditionType[_marketId] = _condition; + emit MarketConditionSet(_marketId, _condition); + } + + function updateCoverageStart(uint256 _coverageStart) external onlyOwner { + if (_coverageStart < coverageStart) revert InvalidInput(); + coverageStart = _coverageStart; + emit CoverageStartUpdated(_coverageStart); + } + + /*////////////////////////////////////////////////////////////// + EXTERNAL + //////////////////////////////////////////////////////////////*/ + // Callback from settled assertion. + // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. + // Otherwise, assertedOutcomeId is reset and the market can be asserted again. + function assertionResolvedCallback( + bytes32 _assertionId, + bool _assertedTruthfully + ) external { + if (msg.sender != address(umaV3)) revert InvalidCaller(); + + uint256 marketId = assertionIdToMarket[_assertionId]; + MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; + if (marketAnswer.activeAssertion == false) revert AssertionInactive(); + + marketAnswer.updatedAt = uint128(block.timestamp); + marketAnswer.answer = _assertedTruthfully ? 1 : 0; + marketAnswer.activeAssertion = false; + marketIdToAnswer[marketId] = marketAnswer; + + emit AssertionResolved(_assertionId, _assertedTruthfully); + } + + function fetchAssertion( + uint256 _marketId + ) external returns (bytes32 assertionId) { + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; + if (marketAnswer.activeAssertion == true) revert AssertionActive(); + + // Configure bond and claim information + uint256 minimumBond = umaV3.getMinimumBond(address(currency)); + uint256 reqBond = requiredBond; + uint256 bond = reqBond > minimumBond ? reqBond : minimumBond; + bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); + + // Transfer bond from sender and request assertion + ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); + ERC20(currency).safeApprove(address(umaV3), bond); + assertionId = umaV3.assertTruth( + claim, + msg.sender, // Asserter + address(this), // Receive callback to this contract + address(0), // No sovereign security + ASSERTION_LIVENESS, + IERC20(currency), + bond, + defaultIdentifier, + bytes32(0) // No domain + ); + + assertionIdToMarket[assertionId] = _marketId; + marketIdToAnswer[_marketId].activeAssertion = true; + marketIdToAnswer[_marketId].assertionId = assertionId; + + emit MarketAsserted(_marketId, assertionId); + } + + /** @notice Fetch the assertion state of the market + * @return bool If assertion is true or false for the market condition + */ + function checkAssertion( + uint256 _marketId + ) public view virtual returns (bool) { + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; + + if ((block.timestamp - marketAnswer.updatedAt) > timeOut) + revert PriceTimedOut(); + + if (marketAnswer.answer == 1) return true; + else return false; + } + + // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future + /** @notice Fetch price and return condition + * @param _strike Strike price + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256 _strike, + uint256 _marketId + ) public view virtual returns (bool, int256 price) { + uint256 conditionType = marketIdToConditionType[_marketId]; + bool condition = checkAssertion(_marketId); + + if (conditionType == 1) return (condition, price); + else if (conditionType == 2) return (condition, price); + else revert ConditionTypeNotSet(); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////*/ + /** + @param _conditionType is the condition type for the market + @dev encode claim would look like: "As of assertion timestamp , " + Where inputs could be: "As of assertion timestamp 1625097600, <0.997>" + @return bytes for the claim + */ + function _composeClaim( + uint256 _conditionType + ) internal view returns (bytes memory) { + return + abi.encodePacked( + "As of assertion timestamp ", + _toUtf8BytesUint(block.timestamp), + ", the following statement is", + _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, + assertionDescription, + "This occured after the timestamp of ", + coverageStart + ); + } + + /** + * @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type. + * @dev This method is based off of this code: https://stackoverflow.com/a/65707309. + * @dev Pulled from UMA protocol packages: https://github.com/UMAprotocol/protocol/blob/9bfbbe98bed0ac7d9c924115018bb0e26987e2b5/packages/core/contracts/common/implementation/AncillaryData.sol + */ + function _toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { + if (x == 0) { + return "0"; + } + uint256 j = x; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len; + while (x != 0) { + k = k - 1; + uint8 temp = (48 + uint8(x - (x / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + x /= 10; + } + return bstr; + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + error ZeroAddress(); + error InvalidInput(); + error PriceTimedOut(); + error ConditionTypeNotSet(); + error ConditionTypeSet(); + error InvalidCaller(); + error AssertionActive(); + error AssertionInactive(); + error InvalidCallback(); +} diff --git a/test/V2/oracles/individual/UmaPriceProvider.t.sol b/test/V2/oracles/individual/UmaAssertProvider.t.sol similarity index 84% rename from test/V2/oracles/individual/UmaPriceProvider.t.sol rename to test/V2/oracles/individual/UmaAssertProvider.t.sol index e0af120b..b0b04c0d 100644 --- a/test/V2/oracles/individual/UmaPriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaAssertProvider.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; import { - UmaPriceProvider -} from "../../../../src/v2/oracles/individual/UmaPriceProvider.sol"; + UmaAssertProvider +} from "../../../../src/v2/oracles/individual/UmaAssertProvider.sol"; import {TimeLock} from "../../../../src/v2/TimeLock.sol"; import { MockOracleAnswerZero, @@ -15,10 +15,10 @@ import { import {MockUma} from "../MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; -contract UmaPriceProviderTest is Helper { +contract UmaAssertProviderTest is Helper { uint256 public arbForkId; VaultFactoryV2 public factory; - UmaPriceProvider public umaPriceProvider; + UmaAssertProvider public umaPriceProvider; uint256 public marketId = 2; ERC20 public wethAsset; @@ -38,11 +38,13 @@ contract UmaPriceProviderTest is Helper { wethAsset = ERC20(WETH_ADDRESS); // TODO: Should this be encoded or encode packed? - assertionDescription = abi.encode("USDC/USD price is less than 0.97"); + assertionDescription = abi.encode( + "USDC/USD exchange rate is above 0.997" + ); address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaPriceProvider = new UmaPriceProvider( + umaPriceProvider = new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -81,8 +83,8 @@ contract UmaPriceProviderTest is Helper { function testConditionOneMetUma() public { MockUma mockUma = new MockUma(); - // Deploying new umaPriceProvider - umaPriceProvider = new UmaPriceProvider( + // Deploying new UmaAssertProvider + umaPriceProvider = new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -149,8 +151,8 @@ contract UmaPriceProviderTest is Helper { function testConditionTwoMetUma() public { MockUma mockUma = new MockUma(); - // Deploying new umaPriceProvider - umaPriceProvider = new UmaPriceProvider( + // Deploying new UmaAssertProvider + umaPriceProvider = new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -183,8 +185,8 @@ contract UmaPriceProviderTest is Helper { function testCheckAssertionTrue() public { MockUma mockUma = new MockUma(); - // Deploying new umaPriceProvider - umaPriceProvider = new UmaPriceProvider( + // Deploying new UmaAssertProvider + umaPriceProvider = new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -213,8 +215,8 @@ contract UmaPriceProviderTest is Helper { function testCheckAssertionFalse() public { MockUma mockUma = new MockUma(); - // Deploying new umaPriceProvider - umaPriceProvider = new UmaPriceProvider( + // Deploying new UmaAssertProvider + umaPriceProvider = new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -245,8 +247,8 @@ contract UmaPriceProviderTest is Helper { //////////////////////////////////////////////// function testRevertConstructorInputsUma() public { - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + new UmaAssertProvider( 0, UMA_DESCRIPTION, TIME_OUT, @@ -257,8 +259,8 @@ contract UmaPriceProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + new UmaAssertProvider( UMA_DECIMALS, string(""), TIME_OUT, @@ -269,8 +271,8 @@ contract UmaPriceProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, 0, @@ -281,8 +283,8 @@ contract UmaPriceProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.ZeroAddress.selector); + new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -293,8 +295,8 @@ contract UmaPriceProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -305,8 +307,8 @@ contract UmaPriceProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.ZeroAddress.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.ZeroAddress.selector); + new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -317,8 +319,8 @@ contract UmaPriceProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -329,8 +331,8 @@ contract UmaPriceProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); - new UmaPriceProvider( + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -343,27 +345,27 @@ contract UmaPriceProviderTest is Helper { } function testRevertConditionTypeSetUma() public { - vm.expectRevert(UmaPriceProvider.ConditionTypeSet.selector); + vm.expectRevert(UmaAssertProvider.ConditionTypeSet.selector); umaPriceProvider.setConditionType(2, 0); } function testRevertInvalidInputConditionUma() public { - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); umaPriceProvider.setConditionType(0, 0); - vm.expectRevert(UmaPriceProvider.InvalidInput.selector); + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); umaPriceProvider.setConditionType(0, 3); } function testRevertInvalidCallerCallback() public { - vm.expectRevert(UmaPriceProvider.InvalidCaller.selector); + vm.expectRevert(UmaAssertProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); } function testRevertAssertionInactive() public { vm.prank(UMA_OO_V3); - vm.expectRevert(UmaPriceProvider.AssertionInactive.selector); + vm.expectRevert(UmaAssertProvider.AssertionInactive.selector); umaPriceProvider.assertionResolvedCallback( bytes32(abi.encode(0x12)), true @@ -373,8 +375,8 @@ contract UmaPriceProviderTest is Helper { function testRevertAssertionActive() public { MockUma mockUma = new MockUma(); - // Deploying new umaPriceProvider - umaPriceProvider = new UmaPriceProvider( + // Deploying new UmaAssertProvider + umaPriceProvider = new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -391,12 +393,12 @@ contract UmaPriceProviderTest is Helper { wethAsset.approve(address(umaPriceProvider), 1e18); umaPriceProvider.fetchAssertion(marketId); - vm.expectRevert(UmaPriceProvider.AssertionActive.selector); + vm.expectRevert(UmaAssertProvider.AssertionActive.selector); umaPriceProvider.fetchAssertion(marketId); } function testRevertTimeOutUma() public { - umaPriceProvider = new UmaPriceProvider( + umaPriceProvider = new UmaAssertProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -406,7 +408,7 @@ contract UmaPriceProviderTest is Helper { assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaPriceProvider.PriceTimedOut.selector); + vm.expectRevert(UmaAssertProvider.PriceTimedOut.selector); umaPriceProvider.checkAssertion(123); } } diff --git a/test/V2/oracles/individual/UmaAssertProviderV2.t.sol b/test/V2/oracles/individual/UmaAssertProviderV2.t.sol new file mode 100644 index 00000000..ebf11d86 --- /dev/null +++ b/test/V2/oracles/individual/UmaAssertProviderV2.t.sol @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Helper} from "../../Helper.sol"; +import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; +import { + UmaAssertProviderV2 +} from "../../../../src/v2/oracles/individual/UmaAssertProviderV2.sol"; +import {TimeLock} from "../../../../src/v2/TimeLock.sol"; +import { + MockOracleAnswerZero, + MockOracleRoundOutdated, + MockOracleTimeOut +} from "../MockOracles.sol"; +import {MockUma} from "../MockUma.sol"; +import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; + +contract UmaAssertProviderV2Test is Helper { + uint256 public arbForkId; + VaultFactoryV2 public factory; + UmaAssertProviderV2 public umaPriceProvider; + uint256 public marketId = 2; + ERC20 public wethAsset; + + //////////////////////////////////////////////// + // HELPERS // + //////////////////////////////////////////////// + uint256 public UMA_DECIMALS = 18; + address public UMA_OO_V3 = address(0x123); + string public UMA_DESCRIPTION = "USDC"; + uint256 public REQUIRED_BOND = 1e6; + bytes32 public defaultIdentifier = bytes32("abc"); + bytes public assertionDescription; + + function setUp() public { + arbForkId = vm.createFork(ARBITRUM_RPC_URL); + vm.selectFork(arbForkId); + wethAsset = ERC20(WETH_ADDRESS); + + // TODO: Should this be encoded or encode packed? + assertionDescription = abi.encode( + "USDC/USD exchange rate is above 0.997" + ); + + address timelock = address(new TimeLock(ADMIN)); + factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); + umaPriceProvider = new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + uint256 condition = 2; + umaPriceProvider.setConditionType(marketId, condition); + } + + //////////////////////////////////////////////// + // STATE // + //////////////////////////////////////////////// + function testUmaCreation() public { + assertEq(umaPriceProvider.ASSERTION_LIVENESS(), 7200); + assertEq(umaPriceProvider.currency(), WETH_ADDRESS); + assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); + assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); + assertEq(umaPriceProvider.requiredBond(), REQUIRED_BOND); + + assertEq(umaPriceProvider.timeOut(), TIME_OUT); + assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); + assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); + assertEq(umaPriceProvider.assertionDescription(), assertionDescription); + + assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); + } + + function testUpdateCoverageTime() public { + umaPriceProvider.updateCoverageStart(block.timestamp * 2); + assertEq(umaPriceProvider.coverageStart(), block.timestamp * 2); + } + + //////////////////////////////////////////////// + // FUNCTIONS // + //////////////////////////////////////////////// + function testConditionOneMetUma() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaAssertProviderV2 + umaPriceProvider = new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 1); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + vm.expectEmit(true, false, false, true); + emit MarketAsserted(marketId, bytes32(abi.encode(0x12))); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + + // Checking assertion links to marketId + uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); + assertEq(_marketId, marketId); + assertEq(wethAsset.balanceOf(address(mockUma)), 1e6); + + // Checking marketId info is correct + ( + bool activeAssertion, + uint128 updatedAt, + uint8 answer, + bytes32 assertionIdReturned + ) = umaPriceProvider.marketIdToAnswer(_marketId); + assertEq(activeAssertion, true); + assertEq(updatedAt, uint128(0)); + assertEq(answer, 0); + assertEq(assertionIdReturned, _assertionId); + + vm.expectEmit(true, false, false, true); + emit AssertionResolved(_assertionId, true); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + // Checking resolved callback info + ( + activeAssertion, + updatedAt, + answer, + assertionIdReturned + ) = umaPriceProvider.marketIdToAnswer(_marketId); + assertEq(activeAssertion, false); + assertEq(updatedAt, uint128(block.timestamp)); + assertEq(answer, 1); + + (bool condition, int256 price) = umaPriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price == 0); + assertEq(condition, true); + } + + function testConditionTwoMetUma() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaAssertProviderV2 + umaPriceProvider = new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + (bool condition, int256 price) = umaPriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price == 0); + assertEq(condition, true); + } + + function testCheckAssertionTrue() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaAssertProviderV2 + umaPriceProvider = new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + bool condition = umaPriceProvider.checkAssertion(marketId); + assertEq(condition, true); + } + + function testCheckAssertionFalse() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaAssertProviderV2 + umaPriceProvider = new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + false + ); + + bool condition = umaPriceProvider.checkAssertion(marketId); + assertEq(condition, false); + } + + //////////////////////////////////////////////// + // REVERT CASES // + //////////////////////////////////////////////// + + function testRevertConstructorInputsUma() public { + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + new UmaAssertProviderV2( + 0, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + new UmaAssertProviderV2( + UMA_DECIMALS, + string(""), + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + 0, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaAssertProviderV2.ZeroAddress.selector); + new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(0), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + bytes32(""), + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaAssertProviderV2.ZeroAddress.selector); + new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + address(0), + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + bytes(""), + REQUIRED_BOND + ); + + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + 0 + ); + } + + function testRevertConditionTypeSetUma() public { + vm.expectRevert(UmaAssertProviderV2.ConditionTypeSet.selector); + umaPriceProvider.setConditionType(2, 0); + } + + function testRevertInvalidInputConditionUma() public { + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + umaPriceProvider.setConditionType(0, 0); + + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + umaPriceProvider.setConditionType(0, 3); + } + + function testRevertInvalidInputUpdateCoverageStart() public { + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + umaPriceProvider.updateCoverageStart(0); + } + + function testRevertInvalidCallerCallback() public { + vm.expectRevert(UmaAssertProviderV2.InvalidCaller.selector); + umaPriceProvider.assertionResolvedCallback(bytes32(""), true); + } + + function testRevertAssertionInactive() public { + vm.prank(UMA_OO_V3); + + vm.expectRevert(UmaAssertProviderV2.AssertionInactive.selector); + umaPriceProvider.assertionResolvedCallback( + bytes32(abi.encode(0x12)), + true + ); + } + + function testRevertAssertionActive() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaAssertProviderV2 + umaPriceProvider = new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 1); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + umaPriceProvider.fetchAssertion(marketId); + + vm.expectRevert(UmaAssertProviderV2.AssertionActive.selector); + umaPriceProvider.fetchAssertion(marketId); + } + + function testRevertTimeOutUma() public { + umaPriceProvider = new UmaAssertProviderV2( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + vm.expectRevert(UmaAssertProviderV2.PriceTimedOut.selector); + umaPriceProvider.checkAssertion(123); + } +} From 27138a17f5010f4f59ed461fe97fb547ffc16933 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 28 Sep 2023 19:05:31 -0600 Subject: [PATCH 10/25] feat: dynamic bond function added --- src/v2/oracles/individual/UmaAssertProvider.sol | 9 ++++++++- src/v2/oracles/individual/UmaAssertProviderV2.sol | 9 ++++++++- test/V2/Helper.sol | 4 ++-- .../V2/oracles/individual/UmaAssertProvider.t.sol | 15 ++++++++++++++- .../oracles/individual/UmaAssertProviderV2.t.sol | 15 ++++++++++++++- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/v2/oracles/individual/UmaAssertProvider.sol b/src/v2/oracles/individual/UmaAssertProvider.sol index 99818dc5..6b2c565f 100644 --- a/src/v2/oracles/individual/UmaAssertProvider.sol +++ b/src/v2/oracles/individual/UmaAssertProvider.sol @@ -27,13 +27,13 @@ contract UmaAssertProvider is Ownable { address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; - uint256 public immutable requiredBond; // Bond required to assert on a market // Market info uint256 public immutable timeOut; uint256 public immutable decimals; string public description; bytes public assertionDescription; + uint256 public requiredBond; // Bond required to assert on a market mapping(uint256 => uint256) public marketIdToConditionType; mapping(uint256 => MarketAnswer) public marketIdToAnswer; @@ -42,6 +42,7 @@ contract UmaAssertProvider is Ownable { event MarketAsserted(uint256 marketId, bytes32 assertionId); event AssertionResolved(bytes32 assertionId, bool assertion); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + event BondUpdated(uint256 newBond); /** @param _decimals is decimals for the provider maker if relevant @@ -102,6 +103,12 @@ contract UmaAssertProvider is Ownable { emit MarketConditionSet(_marketId, _condition); } + function updateRequiredBond(uint256 newBond) external onlyOwner { + if (newBond == 0) revert InvalidInput(); + requiredBond = newBond; + emit BondUpdated(newBond); + } + /*////////////////////////////////////////////////////////////// EXTERNAL //////////////////////////////////////////////////////////////*/ diff --git a/src/v2/oracles/individual/UmaAssertProviderV2.sol b/src/v2/oracles/individual/UmaAssertProviderV2.sol index 75713c55..348adf7b 100644 --- a/src/v2/oracles/individual/UmaAssertProviderV2.sol +++ b/src/v2/oracles/individual/UmaAssertProviderV2.sol @@ -26,13 +26,13 @@ contract UmaAssertProviderV2 is Ownable { address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; - uint256 public immutable requiredBond; // Bond required to assert on a market // Market info uint256 public immutable timeOut; uint256 public immutable decimals; string public description; bytes public assertionDescription; + uint256 public requiredBond; // Bond required to assert on a market uint256 public coverageStart; mapping(uint256 => uint256) public marketIdToConditionType; @@ -43,6 +43,7 @@ contract UmaAssertProviderV2 is Ownable { event AssertionResolved(bytes32 assertionId, bool assertion); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event CoverageStartUpdated(uint256 startTime); + event BondUpdated(uint256 newBond); /** @param _decimals is decimals for the provider maker if relevant @@ -110,6 +111,12 @@ contract UmaAssertProviderV2 is Ownable { emit CoverageStartUpdated(_coverageStart); } + function updateRequiredBond(uint256 newBond) external onlyOwner { + if (newBond == 0) revert InvalidInput(); + requiredBond = newBond; + emit BondUpdated(newBond); + } + /*////////////////////////////////////////////////////////////// EXTERNAL //////////////////////////////////////////////////////////////*/ diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index c7edb456..3de38960 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -9,6 +9,8 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract Helper is Test { event MarketAsserted(uint256 marketId, bytes32 assertionId); event AssertionResolved(bytes32 assertionId, bool assertion); + event ProtocolFeeCollected(uint256 indexed epochId, uint256 indexed fee); + event BondUpdated(uint256 newBond); uint256 public constant STRIKE = 1000000000000000000; uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; @@ -75,8 +77,6 @@ contract Helper is Test { string public ARBITRUM_GOERLI_RPC_URL = vm.envString("ARBITRUM_GOERLI_RPC_URL"); - event ProtocolFeeCollected(uint256 indexed epochId, uint256 indexed fee); - //////////////////////////////////////////////// // Vault Helpers // //////////////////////////////////////////////// diff --git a/test/V2/oracles/individual/UmaAssertProvider.t.sol b/test/V2/oracles/individual/UmaAssertProvider.t.sol index b0b04c0d..c8774967 100644 --- a/test/V2/oracles/individual/UmaAssertProvider.t.sol +++ b/test/V2/oracles/individual/UmaAssertProvider.t.sol @@ -77,6 +77,15 @@ contract UmaAssertProviderTest is Helper { assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); } + function testUpdateRequiredBond() public { + uint256 newBond = 1e6; + + vm.expectEmit(true, true, false, false); + emit BondUpdated(newBond); + umaPriceProvider.updateRequiredBond(newBond); + assertEq(umaPriceProvider.requiredBond(), newBond); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// @@ -245,7 +254,6 @@ contract UmaAssertProviderTest is Helper { //////////////////////////////////////////////// // REVERT CASES // //////////////////////////////////////////////// - function testRevertConstructorInputsUma() public { vm.expectRevert(UmaAssertProvider.InvalidInput.selector); new UmaAssertProvider( @@ -357,6 +365,11 @@ contract UmaAssertProviderTest is Helper { umaPriceProvider.setConditionType(0, 3); } + function testRevertInvalidInpudRequiredBond() public { + vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + umaPriceProvider.updateRequiredBond(0); + } + function testRevertInvalidCallerCallback() public { vm.expectRevert(UmaAssertProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); diff --git a/test/V2/oracles/individual/UmaAssertProviderV2.t.sol b/test/V2/oracles/individual/UmaAssertProviderV2.t.sol index ebf11d86..96e792cd 100644 --- a/test/V2/oracles/individual/UmaAssertProviderV2.t.sol +++ b/test/V2/oracles/individual/UmaAssertProviderV2.t.sol @@ -82,6 +82,15 @@ contract UmaAssertProviderV2Test is Helper { assertEq(umaPriceProvider.coverageStart(), block.timestamp * 2); } + function testUpdateRequiredBond() public { + uint256 newBond = 1e6; + + vm.expectEmit(true, true, false, false); + emit BondUpdated(newBond); + umaPriceProvider.updateRequiredBond(newBond); + assertEq(umaPriceProvider.requiredBond(), newBond); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// @@ -250,7 +259,6 @@ contract UmaAssertProviderV2Test is Helper { //////////////////////////////////////////////// // REVERT CASES // //////////////////////////////////////////////// - function testRevertConstructorInputsUma() public { vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); new UmaAssertProviderV2( @@ -367,6 +375,11 @@ contract UmaAssertProviderV2Test is Helper { umaPriceProvider.updateCoverageStart(0); } + function testRevertInvalidInpudRequiredBond() public { + vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + umaPriceProvider.updateRequiredBond(0); + } + function testRevertInvalidCallerCallback() public { vm.expectRevert(UmaAssertProviderV2.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); From da4fbe44ad12a7f153c0c8ba3418b991d5b04349 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Mon, 9 Oct 2023 10:02:47 -0600 Subject: [PATCH 11/25] fix: umaV3 naming and pyth tests fixed --- script/v2/V2DeployContracts.s.sol | 22 ++--- ...V2.sol => UmaV3EventAssertionProvider.sol} | 2 +- ...er.sol => UmaV3PriceAssertionProvider.sol} | 2 +- ....t.sol => UmaV3EventAssertionProvider.sol} | 86 +++++++++---------- ....t.sol => UmaV3PriceAssertionProvider.sol} | 80 ++++++++--------- 5 files changed, 95 insertions(+), 97 deletions(-) rename src/v2/oracles/individual/{UmaAssertProviderV2.sol => UmaV3EventAssertionProvider.sol} (99%) rename src/v2/oracles/individual/{UmaAssertProvider.sol => UmaV3PriceAssertionProvider.sol} (99%) rename test/V2/oracles/individual/{UmaAssertProviderV2.t.sol => UmaV3EventAssertionProvider.sol} (81%) rename test/V2/oracles/individual/{UmaAssertProvider.t.sol => UmaV3PriceAssertionProvider.sol} (82%) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index 7300e5d9..c8b55397 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -14,7 +14,7 @@ import "../../src/v2/oracles/individual/RedstonePriceProvider.sol"; import "../../src/v2/oracles/individual/DIAPriceProvider.sol"; import "../../src/v2/oracles/individual/CVIPriceProvider.sol"; import "../../src/v2/oracles/individual/GdaiPriceProvider.sol"; -import "../../src/v2/oracles/individual/UmaAssertProvider.sol"; +import "../../src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol"; import "../../src/v2/TimeLock.sol"; import "./V2Helper.sol"; import { @@ -136,16 +136,16 @@ contract V2DeployContracts is Script, HelperV2 { bytes memory assertionDescription = "The USDC/USD exchange is above 0.997"; address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS - UmaAssertProvider umaPriceProvider = new UmaAssertProvider( - umaDecimals, - umaDescription, - timeOut, - umaOOV3, - defaultIdentifier, - currency, - assertionDescription, - requiredBond - ); + UmaV3PriceAssertionProvider umaPriceProvider = new UmaV3PriceAssertionProvider( + umaDecimals, + umaDescription, + timeOut, + umaOOV3, + defaultIdentifier, + currency, + assertionDescription, + requiredBond + ); // vaultFactory.whitelistController(address(controller)); // KeeperV2 resolveKeeper = new KeeperV2( diff --git a/src/v2/oracles/individual/UmaAssertProviderV2.sol b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol similarity index 99% rename from src/v2/oracles/individual/UmaAssertProviderV2.sol rename to src/v2/oracles/individual/UmaV3EventAssertionProvider.sol index 348adf7b..72fedc9b 100644 --- a/src/v2/oracles/individual/UmaAssertProviderV2.sol +++ b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol @@ -9,7 +9,7 @@ import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @notice This provider is build to work where you need to define between x and y e.g. there was a hack between Aug31st -contract UmaAssertProviderV2 is Ownable { +contract UmaV3EventAssertionProvider is Ownable { using SafeTransferLib for ERC20; struct MarketAnswer { bool activeAssertion; diff --git a/src/v2/oracles/individual/UmaAssertProvider.sol b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol similarity index 99% rename from src/v2/oracles/individual/UmaAssertProvider.sol rename to src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol index 6b2c565f..dfe2de57 100644 --- a/src/v2/oracles/individual/UmaAssertProvider.sol +++ b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol @@ -10,7 +10,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @notice Assertion provider where the condition can be checked without a required begin time e.g. 1 PEPE > $1000 or BTC market > 2x ETH market cap /// @dev This provider would not work if you needed to check if x happened between time y and z -contract UmaAssertProvider is Ownable { +contract UmaV3PriceAssertionProvider is Ownable { using SafeTransferLib for ERC20; struct MarketAnswer { bool activeAssertion; diff --git a/test/V2/oracles/individual/UmaAssertProviderV2.t.sol b/test/V2/oracles/individual/UmaV3EventAssertionProvider.sol similarity index 81% rename from test/V2/oracles/individual/UmaAssertProviderV2.t.sol rename to test/V2/oracles/individual/UmaV3EventAssertionProvider.sol index 96e792cd..666640a8 100644 --- a/test/V2/oracles/individual/UmaAssertProviderV2.t.sol +++ b/test/V2/oracles/individual/UmaV3EventAssertionProvider.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; import { - UmaAssertProviderV2 -} from "../../../../src/v2/oracles/individual/UmaAssertProviderV2.sol"; + UmaV3EventAssertionProvider +} from "../../../../src/v2/oracles/individual/UmaV3EventAssertionProvider.sol"; import {TimeLock} from "../../../../src/v2/TimeLock.sol"; import { MockOracleAnswerZero, @@ -15,10 +15,10 @@ import { import {MockUma} from "../MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; -contract UmaAssertProviderV2Test is Helper { +contract UmaV3EventAssertionProviderTest is Helper { uint256 public arbForkId; VaultFactoryV2 public factory; - UmaAssertProviderV2 public umaPriceProvider; + UmaV3EventAssertionProvider public umaPriceProvider; uint256 public marketId = 2; ERC20 public wethAsset; @@ -38,13 +38,11 @@ contract UmaAssertProviderV2Test is Helper { wethAsset = ERC20(WETH_ADDRESS); // TODO: Should this be encoded or encode packed? - assertionDescription = abi.encode( - "USDC/USD exchange rate is above 0.997" - ); + assertionDescription = abi.encode("Curve was hacked"); address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaPriceProvider = new UmaAssertProviderV2( + umaPriceProvider = new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -97,8 +95,8 @@ contract UmaAssertProviderV2Test is Helper { function testConditionOneMetUma() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProviderV2 - umaPriceProvider = new UmaAssertProviderV2( + // Deploying new UmaV3EventAssertionProvider + umaPriceProvider = new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -165,8 +163,8 @@ contract UmaAssertProviderV2Test is Helper { function testConditionTwoMetUma() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProviderV2 - umaPriceProvider = new UmaAssertProviderV2( + // Deploying new UmaV3EventAssertionProvider + umaPriceProvider = new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -199,8 +197,8 @@ contract UmaAssertProviderV2Test is Helper { function testCheckAssertionTrue() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProviderV2 - umaPriceProvider = new UmaAssertProviderV2( + // Deploying new UmaV3EventAssertionProvider + umaPriceProvider = new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -229,8 +227,8 @@ contract UmaAssertProviderV2Test is Helper { function testCheckAssertionFalse() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProviderV2 - umaPriceProvider = new UmaAssertProviderV2( + // Deploying new UmaV3EventAssertionProvider + umaPriceProvider = new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -260,8 +258,8 @@ contract UmaAssertProviderV2Test is Helper { // REVERT CASES // //////////////////////////////////////////////// function testRevertConstructorInputsUma() public { - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); + new UmaV3EventAssertionProvider( 0, UMA_DESCRIPTION, TIME_OUT, @@ -272,8 +270,8 @@ contract UmaAssertProviderV2Test is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); + new UmaV3EventAssertionProvider( UMA_DECIMALS, string(""), TIME_OUT, @@ -284,8 +282,8 @@ contract UmaAssertProviderV2Test is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); + new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, 0, @@ -296,8 +294,8 @@ contract UmaAssertProviderV2Test is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.ZeroAddress.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.ZeroAddress.selector); + new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -308,8 +306,8 @@ contract UmaAssertProviderV2Test is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); + new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -320,8 +318,8 @@ contract UmaAssertProviderV2Test is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.ZeroAddress.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.ZeroAddress.selector); + new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -332,8 +330,8 @@ contract UmaAssertProviderV2Test is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); + new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -344,8 +342,8 @@ contract UmaAssertProviderV2Test is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); - new UmaAssertProviderV2( + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); + new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -358,37 +356,37 @@ contract UmaAssertProviderV2Test is Helper { } function testRevertConditionTypeSetUma() public { - vm.expectRevert(UmaAssertProviderV2.ConditionTypeSet.selector); + vm.expectRevert(UmaV3EventAssertionProvider.ConditionTypeSet.selector); umaPriceProvider.setConditionType(2, 0); } function testRevertInvalidInputConditionUma() public { - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); umaPriceProvider.setConditionType(0, 0); - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); umaPriceProvider.setConditionType(0, 3); } function testRevertInvalidInputUpdateCoverageStart() public { - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); umaPriceProvider.updateCoverageStart(0); } function testRevertInvalidInpudRequiredBond() public { - vm.expectRevert(UmaAssertProviderV2.InvalidInput.selector); + vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); umaPriceProvider.updateRequiredBond(0); } function testRevertInvalidCallerCallback() public { - vm.expectRevert(UmaAssertProviderV2.InvalidCaller.selector); + vm.expectRevert(UmaV3EventAssertionProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); } function testRevertAssertionInactive() public { vm.prank(UMA_OO_V3); - vm.expectRevert(UmaAssertProviderV2.AssertionInactive.selector); + vm.expectRevert(UmaV3EventAssertionProvider.AssertionInactive.selector); umaPriceProvider.assertionResolvedCallback( bytes32(abi.encode(0x12)), true @@ -398,8 +396,8 @@ contract UmaAssertProviderV2Test is Helper { function testRevertAssertionActive() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProviderV2 - umaPriceProvider = new UmaAssertProviderV2( + // Deploying new UmaV3EventAssertionProvider + umaPriceProvider = new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -416,12 +414,12 @@ contract UmaAssertProviderV2Test is Helper { wethAsset.approve(address(umaPriceProvider), 1e18); umaPriceProvider.fetchAssertion(marketId); - vm.expectRevert(UmaAssertProviderV2.AssertionActive.selector); + vm.expectRevert(UmaV3EventAssertionProvider.AssertionActive.selector); umaPriceProvider.fetchAssertion(marketId); } function testRevertTimeOutUma() public { - umaPriceProvider = new UmaAssertProviderV2( + umaPriceProvider = new UmaV3EventAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -431,7 +429,7 @@ contract UmaAssertProviderV2Test is Helper { assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaAssertProviderV2.PriceTimedOut.selector); + vm.expectRevert(UmaV3EventAssertionProvider.PriceTimedOut.selector); umaPriceProvider.checkAssertion(123); } } diff --git a/test/V2/oracles/individual/UmaAssertProvider.t.sol b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.sol similarity index 82% rename from test/V2/oracles/individual/UmaAssertProvider.t.sol rename to test/V2/oracles/individual/UmaV3PriceAssertionProvider.sol index c8774967..174002b2 100644 --- a/test/V2/oracles/individual/UmaAssertProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; import { - UmaAssertProvider -} from "../../../../src/v2/oracles/individual/UmaAssertProvider.sol"; + UmaV3PriceAssertionProvider +} from "../../../../src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol"; import {TimeLock} from "../../../../src/v2/TimeLock.sol"; import { MockOracleAnswerZero, @@ -15,10 +15,10 @@ import { import {MockUma} from "../MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; -contract UmaAssertProviderTest is Helper { +contract UmaV3EventAssertionProviderTest is Helper { uint256 public arbForkId; VaultFactoryV2 public factory; - UmaAssertProvider public umaPriceProvider; + UmaV3PriceAssertionProvider public umaPriceProvider; uint256 public marketId = 2; ERC20 public wethAsset; @@ -44,7 +44,7 @@ contract UmaAssertProviderTest is Helper { address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaPriceProvider = new UmaAssertProvider( + umaPriceProvider = new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -92,8 +92,8 @@ contract UmaAssertProviderTest is Helper { function testConditionOneMetUma() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProvider - umaPriceProvider = new UmaAssertProvider( + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -160,8 +160,8 @@ contract UmaAssertProviderTest is Helper { function testConditionTwoMetUma() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProvider - umaPriceProvider = new UmaAssertProvider( + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -194,8 +194,8 @@ contract UmaAssertProviderTest is Helper { function testCheckAssertionTrue() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProvider - umaPriceProvider = new UmaAssertProvider( + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -224,8 +224,8 @@ contract UmaAssertProviderTest is Helper { function testCheckAssertionFalse() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProvider - umaPriceProvider = new UmaAssertProvider( + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -255,8 +255,8 @@ contract UmaAssertProviderTest is Helper { // REVERT CASES // //////////////////////////////////////////////// function testRevertConstructorInputsUma() public { - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); + new UmaV3PriceAssertionProvider( 0, UMA_DESCRIPTION, TIME_OUT, @@ -267,8 +267,8 @@ contract UmaAssertProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); + new UmaV3PriceAssertionProvider( UMA_DECIMALS, string(""), TIME_OUT, @@ -279,8 +279,8 @@ contract UmaAssertProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); + new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, 0, @@ -291,8 +291,8 @@ contract UmaAssertProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.ZeroAddress.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.ZeroAddress.selector); + new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -303,8 +303,8 @@ contract UmaAssertProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); + new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -315,8 +315,8 @@ contract UmaAssertProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.ZeroAddress.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.ZeroAddress.selector); + new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -327,8 +327,8 @@ contract UmaAssertProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); + new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -339,8 +339,8 @@ contract UmaAssertProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); - new UmaAssertProvider( + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); + new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -353,32 +353,32 @@ contract UmaAssertProviderTest is Helper { } function testRevertConditionTypeSetUma() public { - vm.expectRevert(UmaAssertProvider.ConditionTypeSet.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.ConditionTypeSet.selector); umaPriceProvider.setConditionType(2, 0); } function testRevertInvalidInputConditionUma() public { - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); umaPriceProvider.setConditionType(0, 0); - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); umaPriceProvider.setConditionType(0, 3); } function testRevertInvalidInpudRequiredBond() public { - vm.expectRevert(UmaAssertProvider.InvalidInput.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); umaPriceProvider.updateRequiredBond(0); } function testRevertInvalidCallerCallback() public { - vm.expectRevert(UmaAssertProvider.InvalidCaller.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); } function testRevertAssertionInactive() public { vm.prank(UMA_OO_V3); - vm.expectRevert(UmaAssertProvider.AssertionInactive.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.AssertionInactive.selector); umaPriceProvider.assertionResolvedCallback( bytes32(abi.encode(0x12)), true @@ -388,8 +388,8 @@ contract UmaAssertProviderTest is Helper { function testRevertAssertionActive() public { MockUma mockUma = new MockUma(); - // Deploying new UmaAssertProvider - umaPriceProvider = new UmaAssertProvider( + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -406,12 +406,12 @@ contract UmaAssertProviderTest is Helper { wethAsset.approve(address(umaPriceProvider), 1e18); umaPriceProvider.fetchAssertion(marketId); - vm.expectRevert(UmaAssertProvider.AssertionActive.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.AssertionActive.selector); umaPriceProvider.fetchAssertion(marketId); } function testRevertTimeOutUma() public { - umaPriceProvider = new UmaAssertProvider( + umaPriceProvider = new UmaV3PriceAssertionProvider( UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, @@ -421,7 +421,7 @@ contract UmaAssertProviderTest is Helper { assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaAssertProvider.PriceTimedOut.selector); + vm.expectRevert(UmaV3PriceAssertionProvider.PriceTimedOut.selector); umaPriceProvider.checkAssertion(123); } } From f910091a72f0b0aae7ca3f7e49b44a692058a87c Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Mon, 9 Oct 2023 16:28:43 -0600 Subject: [PATCH 12/25] feat: umaV2 price provider --- src/v2/interfaces/IFinder.sol | 13 + src/v2/interfaces/IUmaV2.sol | 75 ++++ .../individual/UmaV2AssertionProvider.sol | 4 + .../oracles/individual/UmaV2PriceProvider.sol | 225 ++++++++++ test/V2/Helper.sol | 1 + test/V2/oracles/MockOracles.sol | 46 ++ .../individual/ChainlinkPriceProvider.t.sol | 5 +- .../individual/UmaV2PriceProvider.t.sol | 402 ++++++++++++++++++ 8 files changed, 770 insertions(+), 1 deletion(-) create mode 100644 src/v2/interfaces/IFinder.sol create mode 100644 src/v2/interfaces/IUmaV2.sol create mode 100644 src/v2/oracles/individual/UmaV2AssertionProvider.sol create mode 100644 src/v2/oracles/individual/UmaV2PriceProvider.sol create mode 100644 test/V2/oracles/individual/UmaV2PriceProvider.t.sol diff --git a/src/v2/interfaces/IFinder.sol b/src/v2/interfaces/IFinder.sol new file mode 100644 index 00000000..080e7ca6 --- /dev/null +++ b/src/v2/interfaces/IFinder.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +interface IFinder { + /** + * @notice Gets the address of the contract that implements the given `interfaceName`. + * @param interfaceName queried interface. + * @return implementationAddress address of the deployed contract that implements the interface. + */ + function getImplementationAddress( + bytes32 interfaceName + ) external view returns (address); +} diff --git a/src/v2/interfaces/IUmaV2.sol b/src/v2/interfaces/IUmaV2.sol new file mode 100644 index 00000000..d8056c8c --- /dev/null +++ b/src/v2/interfaces/IUmaV2.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IUmaV2 { + /** + * @notice Requests a new price. + * @param identifier price identifier being requested. + * @param timestamp timestamp of the price being requested. + * @param ancillaryData ancillary data representing additional args being passed with the price request. + * @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM. + * @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0, + * which could make sense if the contract requests and proposes the value in the same call or + * provides its own reward system. + * @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay. + * This can be changed with a subsequent call to setBond(). + */ + function requestPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward + ) external returns (uint256 totalBond); + + /** + * @notice Set the proposal bond associated with a price request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param bond custom bond amount to set. + * @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be + * changed again with a subsequent call to setBond(). + */ + function setBond( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 bond + ) external returns (uint256 totalBond); + + /** + * @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before + * being auto-resolved. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param customLiveness new custom liveness. + */ + function setCustomLiveness( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 customLiveness + ) external; + + /** + * @notice Sets which callbacks should be enabled for the request. + * @param identifier price identifier to identify the existing request. + * @param timestamp timestamp to identify the existing request. + * @param ancillaryData ancillary data of the price being requested. + * @param callbackOnPriceProposed whether to enable the callback onPriceProposed. + * @param callbackOnPriceDisputed whether to enable the callback onPriceDisputed. + * @param callbackOnPriceSettled whether to enable the callback onPriceSettled. + */ + function setCallbacks( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + bool callbackOnPriceProposed, + bool callbackOnPriceDisputed, + bool callbackOnPriceSettled + ) external; +} diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol new file mode 100644 index 00000000..7c4f6fed --- /dev/null +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +contract UmaV2AssertionProvider {} diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol new file mode 100644 index 00000000..1676fdac --- /dev/null +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IVaultFactoryV2} from "../../interfaces/IVaultFactoryV2.sol"; +import {IUmaV2} from "../../interfaces/IUmaV2.sol"; +import {IFinder} from "../../interfaces/IFinder.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "forge-std/console.sol"; + +contract UmaV2PriceProvider is Ownable { + struct MarketAnswer { + uint80 roundId; + int256 price; + uint256 startedAt; + uint256 updatedAt; + uint80 answeredInRound; + } + + uint256 public constant ORACLE_LIVENESS_TIME = 3600 * 2; + bytes32 public constant PRICE_IDENTIFIER = "TOKEN_PRICE"; + + uint256 public immutable timeOut; + IVaultFactoryV2 public immutable vaultFactory; + IUmaV2 public immutable oo; + IFinder public immutable finder; + uint256 public immutable decimals; + IERC20 public immutable currency; + + string public description; + string public ancillaryData; + MarketAnswer public answer; + MarketAnswer public pendingAnswer; + uint256 public requiredBond; + + mapping(uint256 => uint256) public marketIdToConditionType; + + event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + event PriceRequested(); + event PriceSettled(int256 price); + event BondUpdated(uint256 newBond); + + constructor( + address _factory, + uint256 _timeOut, + uint256 _decimals, + string memory _description, + address _finder, + address _currency, + string memory _ancillaryData, + uint256 _requiredBond + ) { + if (_factory == address(0)) revert ZeroAddress(); + if (_timeOut == 0) revert InvalidInput(); + if (keccak256(bytes(_description)) == keccak256("")) + revert InvalidInput(); + if (_finder == address(0)) revert ZeroAddress(); + if (_currency == address(0)) revert ZeroAddress(); + if (keccak256(bytes(_ancillaryData)) == keccak256("")) + revert InvalidInput(); + if (_requiredBond == 0) revert InvalidInput(); + + vaultFactory = IVaultFactoryV2(_factory); + timeOut = _timeOut; + decimals = _decimals; + description = _description; + + finder = IFinder(_finder); + oo = IUmaV2(finder.getImplementationAddress("OptimisticOracleV2")); + currency = IERC20(_currency); + ancillaryData = _ancillaryData; + requiredBond = _requiredBond; + } + + /*////////////////////////////////////////////////////////////// + ADMIN + //////////////////////////////////////////////////////////////*/ + function setConditionType( + uint256 _marketId, + uint256 _condition + ) external onlyOwner { + if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); + if (_condition != 1 && _condition != 2) revert InvalidInput(); + marketIdToConditionType[_marketId] = _condition; + emit MarketConditionSet(_marketId, _condition); + } + + function updateRequiredBond(uint256 newBond) external onlyOwner { + if (newBond == 0) revert InvalidInput(); + requiredBond = newBond; + emit BondUpdated(newBond); + } + + /*////////////////////////////////////////////////////////////// + CALLBACK + //////////////////////////////////////////////////////////////*/ + function priceSettled( + bytes32, + uint256 _timestamp, + bytes memory, + int256 _price + ) external { + if (msg.sender != address(oo)) revert InvalidCaller(); + + MarketAnswer memory _pendingAnswer = pendingAnswer; + MarketAnswer memory _answer = answer; + + _answer.startedAt = _pendingAnswer.startedAt; + _answer.updatedAt = _timestamp; + _answer.price = _price; + _answer.roundId = 1; + _answer.answeredInRound = 1; + answer = _answer; + + emit PriceSettled(_price); + } + + /*////////////////////////////////////////////////////////////// + PUBLIC + //////////////////////////////////////////////////////////////*/ + function requestLatestPrice() external { + if (pendingAnswer.startedAt != 0) revert RequestInProgress(); + + bytes memory _bytesAncillary = abi.encodePacked(ancillaryData); + oo.requestPrice( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + currency, + 0 + ); + oo.setBond( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + requiredBond + ); + oo.setCustomLiveness( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + ORACLE_LIVENESS_TIME + ); + oo.setCallbacks( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + false, + false, + true + ); + + MarketAnswer memory _pendingAnswer; + _pendingAnswer.startedAt = block.timestamp; + pendingAnswer = _pendingAnswer; + + emit PriceRequested(); + } + + function latestRoundData() + public + view + returns ( + uint80 roundId, + int256 price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + MarketAnswer memory _answer = answer; + price = _answer.price; + updatedAt = _answer.updatedAt; + roundId = _answer.roundId; + startedAt = _answer.startedAt; + answeredInRound = _answer.answeredInRound; + } + + /** @notice Fetch token price from priceFeed (Chainlink oracle address) + * @return int256 Current token price + */ + function getLatestPrice() public view returns (int256) { + (, int256 price, , uint256 updatedAt, ) = latestRoundData(); + if (price <= 0) revert OraclePriceZero(); + if ((block.timestamp - updatedAt) > timeOut) revert PriceTimedOut(); + if (decimals < 18) { + uint256 calcDecimals = 10 ** (18 - (decimals)); + price = price * int256(calcDecimals); + } else if (decimals > 18) { + uint256 calcDecimals = 10 ** ((decimals - 18)); + price = price / int256(calcDecimals); + } + return price; + } + + /** @notice Fetch price and return condition + * @param _strike Strike price + * @param _marketId Market id + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256 _strike, + uint256 _marketId + ) public view virtual returns (bool, int256 price) { + uint256 conditionType = marketIdToConditionType[_marketId]; + price = getLatestPrice(); + if (conditionType == 1) return (int256(_strike) < price, price); + else if (conditionType == 2) return (int256(_strike) > price, price); + else revert ConditionTypeNotSet(); + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + error OraclePriceZero(); + error ZeroAddress(); + error PriceTimedOut(); + error InvalidInput(); + error ConditionTypeNotSet(); + error ConditionTypeSet(); + error InvalidCaller(); + error RequestInProgress(); +} diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index 3de38960..591db467 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -11,6 +11,7 @@ contract Helper is Test { event AssertionResolved(bytes32 assertionId, bool assertion); event ProtocolFeeCollected(uint256 indexed epochId, uint256 indexed fee); event BondUpdated(uint256 newBond); + event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); uint256 public constant STRIKE = 1000000000000000000; uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; diff --git a/test/V2/oracles/MockOracles.sol b/test/V2/oracles/MockOracles.sol index e749f25f..9f1764b5 100644 --- a/test/V2/oracles/MockOracles.sol +++ b/test/V2/oracles/MockOracles.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract MockOracleAnswerZero { uint256 public decimals = 0; @@ -368,3 +369,48 @@ contract MockOracleExponentTooSmallPyth { price = PythStructs.Price(899898, 0, -19, block.timestamp); } } + +contract MockUmaV2 { + function requestPrice( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + IERC20 currency, + uint256 reward + ) external returns (uint256 totalBond) {} + + function setBond( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 bond + ) external returns (uint256 totalBond) {} + + function setCustomLiveness( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + uint256 customLiveness + ) external {} + + function setCallbacks( + bytes32 identifier, + uint256 timestamp, + bytes memory ancillaryData, + bool callbackOnPriceProposed, + bool callbackOnPriceDisputed, + bool callbackOnPriceSettled + ) external {} +} + +contract MockUmaFinder { + address public mockUmaV2; + + constructor(address _mockUmaV2) { + mockUmaV2 = _mockUmaV2; + } + + function getImplementationAddress(bytes32) external view returns (address) { + return mockUmaV2; + } +} diff --git a/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol b/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol index 76aa68c5..3c4a4079 100644 --- a/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol +++ b/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol @@ -293,7 +293,7 @@ contract ChainlinkPriceProviderTest is Helper { chainlinkPriceProvider.getLatestPrice(); } - function testRevertOracleTimeOutChainlink() public { + function testRevertPriceTimedOutChainlink() public { address mockAddress = address( new MockOracleTimeOut(block.timestamp, TIME_OUT) ); @@ -303,5 +303,8 @@ contract ChainlinkPriceProviderTest is Helper { mockAddress, TIME_OUT ); + + vm.expectRevert(ChainlinkPriceProvider.PriceTimedOut.selector); + chainlinkPriceProvider.getLatestPrice(); } } diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol new file mode 100644 index 00000000..81002e8e --- /dev/null +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Helper} from "../../Helper.sol"; +import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; +import { + UmaV2PriceProvider +} from "../../../../src/v2/oracles/individual/UmaV2PriceProvider.sol"; +import {TimeLock} from "../../../../src/v2/TimeLock.sol"; +import { + MockOracleAnswerOne, + MockOracleGracePeriod, + MockOracleAnswerZero, + MockOracleRoundOutdated, + MockOracleTimeOut, + MockUmaV2, + MockUmaFinder +} from "../MockOracles.sol"; + +contract UmaV2PriceProviderTest is Helper { + uint256 public arbForkId; + uint256 public arbGoerliForkId; + VaultFactoryV2 public factory; + UmaV2PriceProvider public umaV2PriceProvider; + uint256 public marketId = 2; + + //////////////////////////////////////////////// + // HELPERS // + //////////////////////////////////////////////// + address public constant UMAV2_FINDER = + 0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1; + address public umaV2 = 0x88Ad27C41AD06f01153E7Cd9b10cBEdF4616f4d5; + uint256 public umaDecimals; + string public umaDescription; + address public umaCurrency; + string public ancillaryData; + uint256 public requiredBond; + + function setUp() public { + arbForkId = vm.createFork(ARBITRUM_RPC_URL); + vm.selectFork(arbForkId); + + address timelock = address(new TimeLock(ADMIN)); + factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); + + umaDecimals = 8; + umaDescription = "FUSD/ETH"; + umaCurrency = USDC_TOKEN; + // TODO: Need to review how the configuration section should be composed + ancillaryData = "base: FUSD, quote: USDC, baseChain: ArbitrumOne, rounding: 6, configurations: {}"; + requiredBond = 1e6; + + umaV2PriceProvider = new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + uint256 condition = 2; + umaV2PriceProvider.setConditionType(marketId, condition); + } + + //////////////////////////////////////////////// + // STATE // + //////////////////////////////////////////////// + + function testUmaV2Provider() public { + assertEq(umaV2PriceProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); + assertEq(umaV2PriceProvider.PRICE_IDENTIFIER(), "TOKEN_PRICE"); + assertEq(umaV2PriceProvider.timeOut(), TIME_OUT); + assertEq(address(umaV2PriceProvider.vaultFactory()), address(factory)); + assertEq(address(umaV2PriceProvider.oo()), umaV2); + assertEq(address(umaV2PriceProvider.finder()), UMAV2_FINDER); + assertEq(umaV2PriceProvider.decimals(), umaDecimals); + assertEq(address(umaV2PriceProvider.currency()), umaCurrency); + assertEq(umaV2PriceProvider.description(), umaDescription); + assertEq(umaV2PriceProvider.ancillaryData(), ancillaryData); + assertEq(umaV2PriceProvider.requiredBond(), requiredBond); + } + + //////////////////////////////////////////////// + // ADMIN // + //////////////////////////////////////////////// + function testSetConditionTypeUmaV2() public { + uint256 _marketId = 911; + uint256 _condition = 1; + + vm.expectEmit(true, true, true, true); + emit MarketConditionSet(_marketId, _condition); + umaV2PriceProvider.setConditionType(_marketId, _condition); + + assertEq(umaV2PriceProvider.marketIdToConditionType(_marketId), 1); + } + + function testUpdateRequiredBondUmaV2() public { + uint256 newBond = 1000; + vm.expectEmit(true, true, true, true); + emit BondUpdated(newBond); + umaV2PriceProvider.updateRequiredBond(newBond); + + assertEq(umaV2PriceProvider.requiredBond(), newBond); + } + + //////////////////////////////////////////////// + // PUBLIC CALLBACK // + //////////////////////////////////////////////// + function testPriceSettledUmaV2() public { + bytes32 emptyBytes32; + bytes memory emptyBytes; + int256 price = 2e6; + + // Deploying new umaV2PriceProvider with mock contract + MockUmaV2 mockUmaV2 = new MockUmaV2(); + MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); + umaV2PriceProvider = new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + umaDescription, + address(umaMockFinder), + umaCurrency, + ancillaryData, + requiredBond + ); + + // Configuring the pending answer + uint256 previousTimestamp = block.timestamp; + umaV2PriceProvider.requestLatestPrice(); + vm.warp(block.timestamp + 1 days); + + // Configuring the answer via the callback + vm.prank(address(mockUmaV2)); + umaV2PriceProvider.priceSettled( + emptyBytes32, + block.timestamp, + emptyBytes, + price + ); + ( + uint80 roundId, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = umaV2PriceProvider.answer(); + + // Checking the data + assertEq(roundId, 1); + assertEq(_price, price); + assertEq(startedAt, previousTimestamp); + assertEq(updatedAt, block.timestamp); + assertEq(answeredInRound, 1); + } + + //////////////////////////////////////////////// + // PUBLIC FUNCTIONS // + //////////////////////////////////////////////// + function testrequestLatestPriceUmaV2PriceProvider() public { + umaV2PriceProvider.requestLatestPrice(); + (, , uint256 startedAt, , ) = umaV2PriceProvider.pendingAnswer(); + assertEq(startedAt, block.timestamp); + } + + function testLatestRoundDataUmaV2PriceProvider() public { + // Config the data using the mock oracle + _configureSettledPrice(); + + ( + uint80 roundId, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = umaV2PriceProvider.latestRoundData(); + assertTrue(_price != 0); + assertTrue(roundId != 0); + assertTrue(startedAt != 0); + assertTrue(updatedAt != 0); + assertTrue(answeredInRound != 0); + } + + function testLatestPriceUmaV2PriceProvider() public { + // Config the data using the mock oracle + _configureSettledPrice(); + + int256 price = umaV2PriceProvider.getLatestPrice(); + assertTrue(price != 0); + uint256 calcDecimals = 10 ** (18 - (umaDecimals)); + int256 expectedPrice = 2e6 * int256(calcDecimals); + assertEq(price, expectedPrice); + } + + function testConditionOneMetUmaV2PriceProvider() public { + // Config the data with mock oracle + _configureSettledPrice(); + + uint256 conditionType = 1; + uint256 marketIdOne = 1; + umaV2PriceProvider.setConditionType(marketIdOne, conditionType); + (bool condition, int256 price) = umaV2PriceProvider.conditionMet( + 0.001 ether, + marketIdOne + ); + assertTrue(price != 0); + assertEq(condition, true); + } + + function testConditionTwoMetUmaV2PriceProvider() public { + // Config the data with mock oracle + _configureSettledPrice(); + + uint256 conditionType = 2; + umaV2PriceProvider.setConditionType(marketId, conditionType); + (bool condition, int256 price) = umaV2PriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price != 0); + assertEq(condition, true); + } + + //////////////////////////////////////////////// + // REVERT CASES // + //////////////////////////////////////////////// + function testRevertConstructorInputsUmaV2PriceProvider() public { + vm.expectRevert(UmaV2PriceProvider.ZeroAddress.selector); + new UmaV2PriceProvider( + address(0), + TIME_OUT, + umaDecimals, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); + new UmaV2PriceProvider( + address(factory), + 0, + umaDecimals, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); + new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + "", + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2PriceProvider.ZeroAddress.selector); + new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + umaDescription, + address(0), + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2PriceProvider.ZeroAddress.selector); + new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + umaDescription, + UMAV2_FINDER, + address(0), + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); + new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + umaDescription, + UMAV2_FINDER, + umaCurrency, + "", + requiredBond + ); + + vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); + new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + 0 + ); + } + + function testRevertConditionTypeSetUmaV2PriceProvider() public { + vm.expectRevert(UmaV2PriceProvider.ConditionTypeSet.selector); + umaV2PriceProvider.setConditionType(2, 0); + } + + function testRevertInvalidInputConditionUmaV2PriceProvider() public { + vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); + umaV2PriceProvider.setConditionType(0, 0); + } + + function testRevertInvalidInputUpdateRequiredBond() public { + vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); + umaV2PriceProvider.updateRequiredBond(0); + } + + function testRevertInvalidCallerPriceSettled() public { + vm.expectRevert(UmaV2PriceProvider.InvalidCaller.selector); + umaV2PriceProvider.priceSettled( + bytes32(0), + block.timestamp, + bytes(""), + 0 + ); + } + + function testRevertRequestInProgRequestLatestPrice() public { + umaV2PriceProvider.requestLatestPrice(); + vm.expectRevert(UmaV2PriceProvider.RequestInProgress.selector); + umaV2PriceProvider.requestLatestPrice(); + } + + function testRevertOraclePriceZeroUmaV2PriceProvider() public { + vm.expectRevert(UmaV2PriceProvider.OraclePriceZero.selector); + umaV2PriceProvider.getLatestPrice(); + } + + function testRevertPricedTimedOutUmaV2PriceProvider() public { + _configureSettledPrice(); + vm.warp(block.timestamp + 2 days); + + vm.expectRevert(UmaV2PriceProvider.PriceTimedOut.selector); + umaV2PriceProvider.getLatestPrice(); + } + + function testRevertConditionTypeNotSetUmaV2PriceProvider() public { + _configureSettledPrice(); + + vm.expectRevert(UmaV2PriceProvider.ConditionTypeNotSet.selector); + umaV2PriceProvider.conditionMet(0.001 ether, 1); + } + + //////////////////////////////////////////////// + // HELPER // + //////////////////////////////////////////////// + function _configureSettledPrice() internal { + // Config the data using the mock oracle + bytes32 emptyBytes32; + bytes memory emptyBytes; + int256 price = 2e6; + + // Deploying new umaV2PriceProvider with mock contract + MockUmaV2 mockUmaV2 = new MockUmaV2(); + MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); + umaV2PriceProvider = new UmaV2PriceProvider( + address(factory), + TIME_OUT, + umaDecimals, + umaDescription, + address(umaMockFinder), + umaCurrency, + ancillaryData, + requiredBond + ); + + // Configuring the pending answer + umaV2PriceProvider.requestLatestPrice(); + vm.warp(block.timestamp + 1 days); + + // Configuring the answer via the callback + vm.prank(address(mockUmaV2)); + umaV2PriceProvider.priceSettled( + emptyBytes32, + block.timestamp, + emptyBytes, + price + ); + } +} From 683b208fd59f7d6b2286f46322d5171d2d9c1114 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Mon, 9 Oct 2023 17:42:46 -0600 Subject: [PATCH 13/25] feat: UmaV2 assertion provider --- .../individual/UmaV2AssertionProvider.sol | 210 +++++++++- .../oracles/individual/UmaV2PriceProvider.sol | 20 +- .../UmaV3EventAssertionProvider.sol | 4 +- .../UmaV3PriceAssertionProvider.sol | 4 +- test/V2/Helper.sol | 1 + test/V2/oracles/MockOracles.sol | 4 +- .../individual/UmaV2AssertionProvider.t.sol | 390 ++++++++++++++++++ .../individual/UmaV2PriceProvider.t.sol | 40 +- 8 files changed, 636 insertions(+), 37 deletions(-) create mode 100644 test/V2/oracles/individual/UmaV2AssertionProvider.t.sol diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol index 7c4f6fed..da9d839c 100644 --- a/src/v2/oracles/individual/UmaV2AssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -1,4 +1,212 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -contract UmaV2AssertionProvider {} +import {IVaultFactoryV2} from "../../interfaces/IVaultFactoryV2.sol"; +import {IUmaV2} from "../../interfaces/IUmaV2.sol"; +import {IFinder} from "../../interfaces/IFinder.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract UmaV2AssertionProvider is Ownable { + struct AssertionAnswer { + uint80 roundId; + int256 assertion; + uint256 startedAt; + uint256 updatedAt; + uint80 answeredInRound; + } + + uint256 public constant ORACLE_LIVENESS_TIME = 3600 * 2; + bytes32 public constant PRICE_IDENTIFIER = "YES_OR_NO_QUERY"; + string public constant ANCILLARY_TAIL = "A:1 for YES, B:2 for NO"; + + uint256 public immutable timeOut; + IVaultFactoryV2 public immutable vaultFactory; + IUmaV2 public immutable oo; + IFinder public immutable finder; + IERC20 public immutable currency; + + string public description; + string public ancillaryData; + AssertionAnswer public answer; + AssertionAnswer public pendingAnswer; + uint256 public requiredBond; + uint256 public coverageStart; + + mapping(uint256 => uint256) public marketIdToConditionType; + + event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + event CoverageStartUpdated(uint256 startTime); + event BondUpdated(uint256 newBond); + event PriceSettled(int256 price); + event PriceRequested(); + + constructor( + address _factory, + uint256 _timeOut, + string memory _description, + address _finder, + address _currency, + string memory _ancillaryData, + uint256 _requiredBond + ) { + if (_factory == address(0)) revert ZeroAddress(); + if (_timeOut == 0) revert InvalidInput(); + if (keccak256(bytes(_description)) == keccak256("")) + revert InvalidInput(); + if (_finder == address(0)) revert ZeroAddress(); + if (_currency == address(0)) revert ZeroAddress(); + if (keccak256(bytes(_ancillaryData)) == keccak256("")) + revert InvalidInput(); + if (_requiredBond == 0) revert InvalidInput(); + + vaultFactory = IVaultFactoryV2(_factory); + timeOut = _timeOut; + description = _description; + + finder = IFinder(_finder); + oo = IUmaV2(finder.getImplementationAddress("OptimisticOracleV2")); + currency = IERC20(_currency); + ancillaryData = _ancillaryData; + requiredBond = _requiredBond; + coverageStart = block.timestamp; + } + + /*////////////////////////////////////////////////////////////// + ADMIN + //////////////////////////////////////////////////////////////*/ + function setConditionType( + uint256 _marketId, + uint256 _condition + ) external onlyOwner { + if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); + if (_condition != 1 && _condition != 2) revert InvalidInput(); + marketIdToConditionType[_marketId] = _condition; + emit MarketConditionSet(_marketId, _condition); + } + + function updateCoverageStart(uint256 _coverageStart) external onlyOwner { + if (_coverageStart < coverageStart) revert InvalidInput(); + coverageStart = _coverageStart; + emit CoverageStartUpdated(_coverageStart); + } + + function updateRequiredBond(uint256 newBond) external onlyOwner { + if (newBond == 0) revert InvalidInput(); + requiredBond = newBond; + emit BondUpdated(newBond); + } + + /*////////////////////////////////////////////////////////////// + CALLBACK + //////////////////////////////////////////////////////////////*/ + function priceSettled( + bytes32, + uint256 _timestamp, + bytes memory, + int256 _price + ) external { + if (msg.sender != address(oo)) revert InvalidCaller(); + + AssertionAnswer memory _pendingAnswer = pendingAnswer; + AssertionAnswer memory _answer = answer; + + _answer.startedAt = _pendingAnswer.startedAt; + _answer.updatedAt = _timestamp; + _answer.assertion = _price; + _answer.roundId = 1; + _answer.answeredInRound = 1; + answer = _answer; + + emit PriceSettled(_price); + } + + /*////////////////////////////////////////////////////////////// + PUBLIC + //////////////////////////////////////////////////////////////*/ + function requestLatestAssertion() external { + if (pendingAnswer.startedAt != 0) revert RequestInProgress(); + + bytes memory _bytesAncillary = abi.encodePacked( + ancillaryData, + coverageStart, + ANCILLARY_TAIL + ); + oo.requestPrice( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + currency, + 0 + ); + oo.setBond( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + requiredBond + ); + oo.setCustomLiveness( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + ORACLE_LIVENESS_TIME + ); + oo.setCallbacks( + PRICE_IDENTIFIER, + block.timestamp, + _bytesAncillary, + false, + false, + true + ); + + AssertionAnswer memory _pendingAnswer; + _pendingAnswer.startedAt = block.timestamp; + pendingAnswer = _pendingAnswer; + + emit PriceRequested(); + } + + /** @notice Fetch the assertion state of the market + * @return bool If assertion is true or false for the market condition + */ + function checkAssertion() public view virtual returns (bool) { + AssertionAnswer memory assertionAnswer = answer; + + if (assertionAnswer.updatedAt == 0) revert OraclePriceZero(); + if ((block.timestamp - assertionAnswer.updatedAt) > timeOut) + revert PriceTimedOut(); + + if (assertionAnswer.assertion == 1) return true; + else return false; + } + + /** @notice Fetch price and return condition + * @param _marketId Market id + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256, + uint256 _marketId + ) public view virtual returns (bool, int256 price) { + uint256 conditionType = marketIdToConditionType[_marketId]; + bool condition = checkAssertion(); + + if (conditionType == 1) return (condition, price); + else if (conditionType == 2) return (condition, price); + else revert ConditionTypeNotSet(); + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + error OraclePriceZero(); + error ZeroAddress(); + error PriceTimedOut(); + error InvalidInput(); + error ConditionTypeNotSet(); + error ConditionTypeSet(); + error InvalidCaller(); + error RequestInProgress(); +} diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol index 1676fdac..24531844 100644 --- a/src/v2/oracles/individual/UmaV2PriceProvider.sol +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -7,10 +7,8 @@ import {IFinder} from "../../interfaces/IFinder.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "forge-std/console.sol"; - contract UmaV2PriceProvider is Ownable { - struct MarketAnswer { + struct PriceAnswer { uint80 roundId; int256 price; uint256 startedAt; @@ -30,16 +28,16 @@ contract UmaV2PriceProvider is Ownable { string public description; string public ancillaryData; - MarketAnswer public answer; - MarketAnswer public pendingAnswer; + PriceAnswer public answer; + PriceAnswer public pendingAnswer; uint256 public requiredBond; mapping(uint256 => uint256) public marketIdToConditionType; event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); - event PriceRequested(); - event PriceSettled(int256 price); event BondUpdated(uint256 newBond); + event PriceSettled(int256 price); + event PriceRequested(); constructor( address _factory, @@ -103,8 +101,8 @@ contract UmaV2PriceProvider is Ownable { ) external { if (msg.sender != address(oo)) revert InvalidCaller(); - MarketAnswer memory _pendingAnswer = pendingAnswer; - MarketAnswer memory _answer = answer; + PriceAnswer memory _pendingAnswer = pendingAnswer; + PriceAnswer memory _answer = answer; _answer.startedAt = _pendingAnswer.startedAt; _answer.updatedAt = _timestamp; @@ -151,7 +149,7 @@ contract UmaV2PriceProvider is Ownable { true ); - MarketAnswer memory _pendingAnswer; + PriceAnswer memory _pendingAnswer; _pendingAnswer.startedAt = block.timestamp; pendingAnswer = _pendingAnswer; @@ -169,7 +167,7 @@ contract UmaV2PriceProvider is Ownable { uint80 answeredInRound ) { - MarketAnswer memory _answer = answer; + PriceAnswer memory _answer = answer; price = _answer.price; updatedAt = _answer.updatedAt; roundId = _answer.roundId; diff --git a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol index 72fedc9b..84b526d9 100644 --- a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol @@ -192,12 +192,12 @@ contract UmaV3EventAssertionProvider is Ownable { // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future /** @notice Fetch price and return condition - * @param _strike Strike price + * @param _marketId the marketId for the market * @return boolean If condition is met i.e. strike > price * @return price Current price for token */ function conditionMet( - uint256 _strike, + uint256, uint256 _marketId ) public view virtual returns (bool, int256 price) { uint256 conditionType = marketIdToConditionType[_marketId]; diff --git a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol index dfe2de57..e62158cc 100644 --- a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol @@ -184,12 +184,12 @@ contract UmaV3PriceAssertionProvider is Ownable { // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future /** @notice Fetch price and return condition - * @param _strike Strike price + * @param _marketId the marketId for the market * @return boolean If condition is met i.e. strike > price * @return price Current price for token */ function conditionMet( - uint256 _strike, + uint256, uint256 _marketId ) public view virtual returns (bool, int256 price) { uint256 conditionType = marketIdToConditionType[_marketId]; diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index 591db467..5f8626e8 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -12,6 +12,7 @@ contract Helper is Test { event ProtocolFeeCollected(uint256 indexed epochId, uint256 indexed fee); event BondUpdated(uint256 newBond); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + event CoverageStartUpdated(uint256 startTime); uint256 public constant STRIKE = 1000000000000000000; uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; diff --git a/test/V2/oracles/MockOracles.sol b/test/V2/oracles/MockOracles.sol index 9f1764b5..d3e0c522 100644 --- a/test/V2/oracles/MockOracles.sol +++ b/test/V2/oracles/MockOracles.sol @@ -125,7 +125,7 @@ contract MockOracleConditionNotMet { function conditionMet( uint256 _strike, - uint256 _marketId + uint256 ) external view returns (bool, int256 price) { (, price, , , ) = latestRoundData(); return (int256(_strike) > price, price); @@ -202,7 +202,7 @@ contract MockOracleConditionMet { function conditionMet( uint256 _strike, - uint256 _marketId + uint256 ) external view returns (bool, int256 price) { (, price, , , ) = latestRoundData(); return (int256(_strike) > price, price); diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol new file mode 100644 index 00000000..6bf7156c --- /dev/null +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Helper} from "../../Helper.sol"; +import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; +import { + UmaV2AssertionProvider +} from "../../../../src/v2/oracles/individual/UmaV2AssertionProvider.sol"; +import {TimeLock} from "../../../../src/v2/TimeLock.sol"; +import { + MockOracleAnswerOne, + MockOracleGracePeriod, + MockOracleAnswerZero, + MockOracleRoundOutdated, + MockOracleTimeOut, + MockUmaV2, + MockUmaFinder +} from "../MockOracles.sol"; + +// Uma address all networks: https://docs.uma.xyz/resources/network-addresses +// Uma addresses on Arbitrum: https://github.com/UMAprotocol/protocol/blob/master/packages/core/networks/42161.json + +contract UmaV2AssertionProviderTest is Helper { + uint256 public arbForkId; + VaultFactoryV2 public factory; + UmaV2AssertionProvider public umaV2AssertionProvider; + uint256 public marketId = 2; + + //////////////////////////////////////////////// + // HELPERS // + //////////////////////////////////////////////// + address public constant UMAV2_FINDER = + 0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1; + address public umaV2 = 0x88Ad27C41AD06f01153E7Cd9b10cBEdF4616f4d5; + uint256 public umaDecimals; + string public umaDescription; + address public umaCurrency; + string public ancillaryData; + uint256 public requiredBond; + + function setUp() public { + arbForkId = vm.createFork(ARBITRUM_RPC_URL); + vm.selectFork(arbForkId); + + address timelock = address(new TimeLock(ADMIN)); + factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); + + umaDecimals = 8; + umaDescription = "FUSD/ETH"; + umaCurrency = USDC_TOKEN; + // TODO: Need to review how the configuration section should be composed + ancillaryData = "base: FUSD, quote: USDC, baseChain: ArbitrumOne, rounding: 6, configurations: {}"; + requiredBond = 1e6; + + umaV2AssertionProvider = new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + uint256 condition = 2; + umaV2AssertionProvider.setConditionType(marketId, condition); + } + + //////////////////////////////////////////////// + // STATE // + //////////////////////////////////////////////// + function testUmaV2AssertionProvider() public { + assertEq(umaV2AssertionProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); + assertEq(umaV2AssertionProvider.PRICE_IDENTIFIER(), "YES_OR_NO_QUERY"); + assertEq(umaV2AssertionProvider.timeOut(), TIME_OUT); + assertEq( + address(umaV2AssertionProvider.vaultFactory()), + address(factory) + ); + assertEq(address(umaV2AssertionProvider.oo()), umaV2); + assertEq(address(umaV2AssertionProvider.finder()), UMAV2_FINDER); + assertEq(address(umaV2AssertionProvider.currency()), umaCurrency); + assertEq(umaV2AssertionProvider.description(), umaDescription); + assertEq(umaV2AssertionProvider.ancillaryData(), ancillaryData); + assertEq(umaV2AssertionProvider.requiredBond(), requiredBond); + assertEq(umaV2AssertionProvider.coverageStart(), block.timestamp); + } + + //////////////////////////////////////////////// + // ADMIN // + //////////////////////////////////////////////// + function testSetConditionTypeUmaV2Assert() public { + uint256 _marketId = 911; + uint256 _condition = 1; + + vm.expectEmit(true, true, true, true); + emit MarketConditionSet(_marketId, _condition); + umaV2AssertionProvider.setConditionType(_marketId, _condition); + + assertEq(umaV2AssertionProvider.marketIdToConditionType(_marketId), 1); + } + + function testUpdateCoverageStartUmaV2Assert() public { + uint256 newCoverageStart = block.timestamp + 1 days; + vm.expectEmit(true, true, true, true); + emit CoverageStartUpdated(newCoverageStart); + umaV2AssertionProvider.updateCoverageStart(newCoverageStart); + + assertEq(umaV2AssertionProvider.coverageStart(), newCoverageStart); + } + + function testUpdateRequiredBondUmaV2Assert() public { + uint256 newBond = 1000; + vm.expectEmit(true, true, true, true); + emit BondUpdated(newBond); + umaV2AssertionProvider.updateRequiredBond(newBond); + + assertEq(umaV2AssertionProvider.requiredBond(), newBond); + } + + //////////////////////////////////////////////// + // PUBLIC CALLBACK // + //////////////////////////////////////////////// + function testPriceSettledUmaV2Assert() public { + bytes32 emptyBytes32; + bytes memory emptyBytes; + int256 price = 1; + + // Deploying new UmaV2AssertionProvider with mock contract + MockUmaV2 mockUmaV2 = new MockUmaV2(); + MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); + umaV2AssertionProvider = new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + umaDescription, + address(umaMockFinder), + umaCurrency, + ancillaryData, + requiredBond + ); + + // Configuring the pending answer + uint256 previousTimestamp = block.timestamp; + umaV2AssertionProvider.requestLatestAssertion(); + vm.warp(block.timestamp + 1 days); + + // Configuring the answer via the callback + vm.prank(address(mockUmaV2)); + umaV2AssertionProvider.priceSettled( + emptyBytes32, + block.timestamp, + emptyBytes, + price + ); + ( + uint80 roundId, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = umaV2AssertionProvider.answer(); + + // Checking the data + assertEq(roundId, 1); + assertEq(_price, price); + assertEq(startedAt, previousTimestamp); + assertEq(updatedAt, block.timestamp); + assertEq(answeredInRound, 1); + } + + //////////////////////////////////////////////// + // PUBLIC FUNCTIONS // + //////////////////////////////////////////////// + function testrequestLatestAssertionUmaV2Assert() public { + umaV2AssertionProvider.requestLatestAssertion(); + (, , uint256 startedAt, , ) = umaV2AssertionProvider.pendingAnswer(); + assertEq(startedAt, block.timestamp); + } + + function testCheckAssertionUmaV2Assert() public { + // Config the data with mock oracle + _configureSettledPrice(true); + + bool condition = umaV2AssertionProvider.checkAssertion(); + assertEq(condition, true); + } + + function testConditionOneMetUmaV2AssertProvider() public { + // Config the data with mock oracle + _configureSettledPrice(true); + + uint256 conditionType = 1; + uint256 marketIdOne = 1; + umaV2AssertionProvider.setConditionType(marketIdOne, conditionType); + (bool condition, ) = umaV2AssertionProvider.conditionMet( + 0.001 ether, + marketIdOne + ); + + assertEq(condition, true); + } + + function testConditionTwoMetUmaV2Assert() public { + // Config the data with mock oracle + _configureSettledPrice(false); + + uint256 conditionType = 2; + umaV2AssertionProvider.setConditionType(marketId, conditionType); + (bool condition, ) = umaV2AssertionProvider.conditionMet( + 2 ether, + marketId + ); + + assertEq(condition, false); + } + + //////////////////////////////////////////////// + // REVERT CASES // + //////////////////////////////////////////////// + function testRevertConstructorInputsUmaV2Assert() public { + vm.expectRevert(UmaV2AssertionProvider.ZeroAddress.selector); + new UmaV2AssertionProvider( + address(0), + TIME_OUT, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); + new UmaV2AssertionProvider( + address(factory), + 0, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); + new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + "", + UMAV2_FINDER, + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2AssertionProvider.ZeroAddress.selector); + new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + umaDescription, + address(0), + umaCurrency, + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2AssertionProvider.ZeroAddress.selector); + new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + umaDescription, + UMAV2_FINDER, + address(0), + ancillaryData, + requiredBond + ); + + vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); + new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + umaDescription, + UMAV2_FINDER, + umaCurrency, + "", + requiredBond + ); + + vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); + new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + umaDescription, + UMAV2_FINDER, + umaCurrency, + ancillaryData, + 0 + ); + } + + function testRevertConditionTypeSetUmaV2Assert() public { + vm.expectRevert(UmaV2AssertionProvider.ConditionTypeSet.selector); + umaV2AssertionProvider.setConditionType(2, 0); + } + + function testRevertInvalidInputConditionUmaV2Assert() public { + vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); + umaV2AssertionProvider.setConditionType(0, 0); + } + + function testRevertInvalidInputCoverageStartUmaV2Assert() public { + vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); + umaV2AssertionProvider.updateCoverageStart(0); + } + + function testRevertInvalidInputUpdateRequiredBondUmaV2Assert() public { + vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); + umaV2AssertionProvider.updateRequiredBond(0); + } + + function testRevertInvalidCallerPriceSettledUmaV2Assert() public { + vm.expectRevert(UmaV2AssertionProvider.InvalidCaller.selector); + umaV2AssertionProvider.priceSettled( + bytes32(0), + block.timestamp, + bytes(""), + 0 + ); + } + + function testRevertRequestInProgRequestLatestAssertionUmaV2Assert() public { + umaV2AssertionProvider.requestLatestAssertion(); + vm.expectRevert(UmaV2AssertionProvider.RequestInProgress.selector); + umaV2AssertionProvider.requestLatestAssertion(); + } + + function testRevertOraclePriceZeroCheckAssertionUmaV2Assert() public { + vm.expectRevert(UmaV2AssertionProvider.OraclePriceZero.selector); + umaV2AssertionProvider.checkAssertion(); + } + + function testRevertPriceTimedOutCheckAssertionUmaV2Assert() public { + // Config the data with mock oracle + _configureSettledPrice(true); + + vm.warp(block.timestamp + 2 days); + vm.expectRevert(UmaV2AssertionProvider.PriceTimedOut.selector); + umaV2AssertionProvider.checkAssertion(); + } + + function testRevertConditionTypeNotSetUmaV2Assert() public { + _configureSettledPrice(true); + + vm.expectRevert(UmaV2AssertionProvider.ConditionTypeNotSet.selector); + umaV2AssertionProvider.conditionMet(0.001 ether, 1); + } + + //////////////////////////////////////////////// + // HELPER // + //////////////////////////////////////////////// + function _configureSettledPrice(bool condition) internal { + // Config the data using the mock oracle + bytes32 emptyBytes32; + bytes memory emptyBytes; + int256 price = condition ? int256(1) : int256(0); + + // Deploying new UmaV2AssertionProvider with mock contract + MockUmaV2 mockUmaV2 = new MockUmaV2(); + MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); + umaV2AssertionProvider = new UmaV2AssertionProvider( + address(factory), + TIME_OUT, + umaDescription, + address(umaMockFinder), + umaCurrency, + ancillaryData, + requiredBond + ); + + // Configuring the pending answer + umaV2AssertionProvider.requestLatestAssertion(); + vm.warp(block.timestamp + 1 days); + + // Configuring the answer via the callback + vm.prank(address(mockUmaV2)); + umaV2AssertionProvider.priceSettled( + emptyBytes32, + block.timestamp, + emptyBytes, + price + ); + } +} diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index 81002e8e..56bb8cf5 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -17,9 +17,11 @@ import { MockUmaFinder } from "../MockOracles.sol"; +// Uma address all networks: https://docs.uma.xyz/resources/network-addresses +// Uma addresses on Arbitrum: https://github.com/UMAprotocol/protocol/blob/master/packages/core/networks/42161.json + contract UmaV2PriceProviderTest is Helper { uint256 public arbForkId; - uint256 public arbGoerliForkId; VaultFactoryV2 public factory; UmaV2PriceProvider public umaV2PriceProvider; uint256 public marketId = 2; @@ -68,7 +70,7 @@ contract UmaV2PriceProviderTest is Helper { // STATE // //////////////////////////////////////////////// - function testUmaV2Provider() public { + function testUmaV2Price() public { assertEq(umaV2PriceProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); assertEq(umaV2PriceProvider.PRICE_IDENTIFIER(), "TOKEN_PRICE"); assertEq(umaV2PriceProvider.timeOut(), TIME_OUT); @@ -85,7 +87,7 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// // ADMIN // //////////////////////////////////////////////// - function testSetConditionTypeUmaV2() public { + function testSetConditionTypeUmaV2Price() public { uint256 _marketId = 911; uint256 _condition = 1; @@ -96,7 +98,7 @@ contract UmaV2PriceProviderTest is Helper { assertEq(umaV2PriceProvider.marketIdToConditionType(_marketId), 1); } - function testUpdateRequiredBondUmaV2() public { + function testUpdateRequiredBondUmaV2Price() public { uint256 newBond = 1000; vm.expectEmit(true, true, true, true); emit BondUpdated(newBond); @@ -108,7 +110,7 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// // PUBLIC CALLBACK // //////////////////////////////////////////////// - function testPriceSettledUmaV2() public { + function testPriceSettledUmaV2Price() public { bytes32 emptyBytes32; bytes memory emptyBytes; int256 price = 2e6; @@ -159,13 +161,13 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// // PUBLIC FUNCTIONS // //////////////////////////////////////////////// - function testrequestLatestPriceUmaV2PriceProvider() public { + function testrequestLatestPriceUmaV2Price() public { umaV2PriceProvider.requestLatestPrice(); (, , uint256 startedAt, , ) = umaV2PriceProvider.pendingAnswer(); assertEq(startedAt, block.timestamp); } - function testLatestRoundDataUmaV2PriceProvider() public { + function testLatestRoundDataUmaV2Price() public { // Config the data using the mock oracle _configureSettledPrice(); @@ -183,7 +185,7 @@ contract UmaV2PriceProviderTest is Helper { assertTrue(answeredInRound != 0); } - function testLatestPriceUmaV2PriceProvider() public { + function testLatestPriceUmaV2Price() public { // Config the data using the mock oracle _configureSettledPrice(); @@ -194,7 +196,7 @@ contract UmaV2PriceProviderTest is Helper { assertEq(price, expectedPrice); } - function testConditionOneMetUmaV2PriceProvider() public { + function testConditionOneMetUmaV2Price() public { // Config the data with mock oracle _configureSettledPrice(); @@ -209,7 +211,7 @@ contract UmaV2PriceProviderTest is Helper { assertEq(condition, true); } - function testConditionTwoMetUmaV2PriceProvider() public { + function testConditionTwoMetUmaV2Price() public { // Config the data with mock oracle _configureSettledPrice(); @@ -226,7 +228,7 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// // REVERT CASES // //////////////////////////////////////////////// - function testRevertConstructorInputsUmaV2PriceProvider() public { + function testRevertConstructorInputsUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.ZeroAddress.selector); new UmaV2PriceProvider( address(0), @@ -312,22 +314,22 @@ contract UmaV2PriceProviderTest is Helper { ); } - function testRevertConditionTypeSetUmaV2PriceProvider() public { + function testRevertConditionTypeSetUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.ConditionTypeSet.selector); umaV2PriceProvider.setConditionType(2, 0); } - function testRevertInvalidInputConditionUmaV2PriceProvider() public { + function testRevertInvalidInputConditionUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); umaV2PriceProvider.setConditionType(0, 0); } - function testRevertInvalidInputUpdateRequiredBond() public { + function testRevertInvalidInputUpdateRequiredBondUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); umaV2PriceProvider.updateRequiredBond(0); } - function testRevertInvalidCallerPriceSettled() public { + function testRevertInvalidCallerPriceSettledUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.InvalidCaller.selector); umaV2PriceProvider.priceSettled( bytes32(0), @@ -337,18 +339,18 @@ contract UmaV2PriceProviderTest is Helper { ); } - function testRevertRequestInProgRequestLatestPrice() public { + function testRevertRequestInProgRequestLatestPriceUmaV2Price() public { umaV2PriceProvider.requestLatestPrice(); vm.expectRevert(UmaV2PriceProvider.RequestInProgress.selector); umaV2PriceProvider.requestLatestPrice(); } - function testRevertOraclePriceZeroUmaV2PriceProvider() public { + function testRevertOraclePriceZeroUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.OraclePriceZero.selector); umaV2PriceProvider.getLatestPrice(); } - function testRevertPricedTimedOutUmaV2PriceProvider() public { + function testRevertPricedTimedOutUmaV2Price() public { _configureSettledPrice(); vm.warp(block.timestamp + 2 days); @@ -356,7 +358,7 @@ contract UmaV2PriceProviderTest is Helper { umaV2PriceProvider.getLatestPrice(); } - function testRevertConditionTypeNotSetUmaV2PriceProvider() public { + function testRevertConditionTypeNotSetUmaV2Price() public { _configureSettledPrice(); vm.expectRevert(UmaV2PriceProvider.ConditionTypeNotSet.selector); From 2f3ea19e81e635e46bfa5526acf73fe3fe15be12 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Tue, 10 Oct 2023 09:05:50 -0600 Subject: [PATCH 14/25] feat: info for config added --- src/v2/oracles/individual/UmaV2AssertionProvider.sol | 12 +++++++++++- src/v2/oracles/individual/UmaV2PriceProvider.sol | 11 +++++++++++ .../oracles/individual/UmaV2AssertionProvider.t.sol | 3 ++- test/V2/oracles/individual/UmaV2PriceProvider.t.sol | 7 ++++--- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol index da9d839c..88e6e157 100644 --- a/src/v2/oracles/individual/UmaV2AssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -7,6 +7,15 @@ import {IFinder} from "../../interfaces/IFinder.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +/** + @notice The defintion information the Uma YER_OR_NO_QUERY is as follows. + Points to remember: (1) No possible defintion for data sources, (2) Sources entirely up to voters, (3) No price feeds providable + Custom return values can be defined for four return types: + - P1 --> for no (default return 0 if not set) + - P2 --> for yes (default return 1 if not set) + - P3 --> for undetermined (default return 2 if not set) + - P4 --> undetermined and there's an early expiration of a specific last possible timestamp listed (default return of mint int256 if not set) + */ contract UmaV2AssertionProvider is Ownable { struct AssertionAnswer { uint80 roundId; @@ -18,7 +27,8 @@ contract UmaV2AssertionProvider is Ownable { uint256 public constant ORACLE_LIVENESS_TIME = 3600 * 2; bytes32 public constant PRICE_IDENTIFIER = "YES_OR_NO_QUERY"; - string public constant ANCILLARY_TAIL = "A:1 for YES, B:2 for NO"; + string public constant ANCILLARY_TAIL = + ". P1: 0 for NO, P2: 1 for YES, P3: 2 for UNDETERMINED"; uint256 public immutable timeOut; IVaultFactoryV2 public immutable vaultFactory; diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol index 24531844..8c70033c 100644 --- a/src/v2/oracles/individual/UmaV2PriceProvider.sol +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -7,6 +7,17 @@ import {IFinder} from "../../interfaces/IFinder.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +/** + @notice The definition information the Uma TOKEN_PRICE is as follows. + Price returned and the params required are: + base: collateral token symbol to be priced + baseAddress: base token deployment address on Ethereum or other chain if provided + baseChain (optional): chainId + quote: quote token symbol to be priced + rounding: defines how many digits should remain to the right of decimals + fallback: data endpoint to user as fallback either for the whole base/quote or part of it + configuration: price feed config formatted as JSON that can be used to construct price feed + */ contract UmaV2PriceProvider is Ownable { struct PriceAnswer { uint80 roundId; diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index 6bf7156c..c53793a7 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -17,6 +17,7 @@ import { MockUmaFinder } from "../MockOracles.sol"; +// Specification for YER_NO_QUERY on Uma: https://github.com/UMAprotocol/UMIPs/blob/master/UMIPs/umip-107.md // Uma address all networks: https://docs.uma.xyz/resources/network-addresses // Uma addresses on Arbitrum: https://github.com/UMAprotocol/protocol/blob/master/packages/core/networks/42161.json @@ -49,7 +50,7 @@ contract UmaV2AssertionProviderTest is Helper { umaDescription = "FUSD/ETH"; umaCurrency = USDC_TOKEN; // TODO: Need to review how the configuration section should be composed - ancillaryData = "base: FUSD, quote: USDC, baseChain: ArbitrumOne, rounding: 6, configurations: {}"; + ancillaryData = "q: Curve USDC pool on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; requiredBond = 1e6; umaV2AssertionProvider = new UmaV2AssertionProvider( diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index 56bb8cf5..8c7915f2 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -17,6 +17,8 @@ import { MockUmaFinder } from "../MockOracles.sol"; +// The configuration information for TOKEN_PRICE query: https://github.com/UMAprotocol/UMIPs/blob/master/UMIPs/umip-121.md +// Price feeds to use in config: https://github.com/UMAprotocol/protocol/tree/master/packages/financial-templates-lib/src/price-feed // Uma address all networks: https://docs.uma.xyz/resources/network-addresses // Uma addresses on Arbitrum: https://github.com/UMAprotocol/protocol/blob/master/packages/core/networks/42161.json @@ -46,10 +48,9 @@ contract UmaV2PriceProviderTest is Helper { factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); umaDecimals = 8; - umaDescription = "FUSD/ETH"; + umaDescription = "FUSD/USD"; umaCurrency = USDC_TOKEN; - // TODO: Need to review how the configuration section should be composed - ancillaryData = "base: FUSD, quote: USDC, baseChain: ArbitrumOne, rounding: 6, configurations: {}"; + ancillaryData = "base:FUSD,baseAddress:0x630410530785377d49992824a70b43bd5c482c9a,baseChain: 42161,quote:USD,quoteDetails:United States Dollar,rounding:6,fallback:"https://www.coingecko.com/en/coins/uma",configuration:{"type": "medianizer","minTimeBetweenUpdates": 60,"twapLength": 600,"medianizedFeeds":[{"type": "cryptowatch", "exchange": "coinbase-pro", "pair": "umausd" }, { "type": "cryptowatch", "exchange": "binance", "pair": "umausdt" }, { "type": "cryptowatch", "exchange": "okex", "pair": "umausdt" }]}"; requiredBond = 1e6; umaV2PriceProvider = new UmaV2PriceProvider( From df71cebe10221618ac9b28e263f409cf360506d4 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Tue, 10 Oct 2023 12:32:37 -0600 Subject: [PATCH 15/25] fix: removed custom bond config --- src/v2/oracles/individual/UmaV2AssertionProvider.sol | 7 +------ src/v2/oracles/individual/UmaV2PriceProvider.sol | 7 +------ test/V2/oracles/individual/UmaV2AssertionProvider.t.sol | 2 +- test/V2/oracles/individual/UmaV2PriceProvider.t.sol | 3 ++- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol index 88e6e157..552eb3d6 100644 --- a/src/v2/oracles/individual/UmaV2AssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -142,17 +142,12 @@ contract UmaV2AssertionProvider is Ownable { coverageStart, ANCILLARY_TAIL ); + currency.approve(address(oo), requiredBond); oo.requestPrice( PRICE_IDENTIFIER, block.timestamp, _bytesAncillary, currency, - 0 - ); - oo.setBond( - PRICE_IDENTIFIER, - block.timestamp, - _bytesAncillary, requiredBond ); oo.setCustomLiveness( diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol index 8c70033c..3a773f64 100644 --- a/src/v2/oracles/individual/UmaV2PriceProvider.sol +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -132,17 +132,12 @@ contract UmaV2PriceProvider is Ownable { if (pendingAnswer.startedAt != 0) revert RequestInProgress(); bytes memory _bytesAncillary = abi.encodePacked(ancillaryData); + currency.approve(address(oo), requiredBond); oo.requestPrice( PRICE_IDENTIFIER, block.timestamp, _bytesAncillary, currency, - 0 - ); - oo.setBond( - PRICE_IDENTIFIER, - block.timestamp, - _bytesAncillary, requiredBond ); oo.setCustomLiveness( diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index c53793a7..092e4c1b 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -49,7 +49,6 @@ contract UmaV2AssertionProviderTest is Helper { umaDecimals = 8; umaDescription = "FUSD/ETH"; umaCurrency = USDC_TOKEN; - // TODO: Need to review how the configuration section should be composed ancillaryData = "q: Curve USDC pool on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; requiredBond = 1e6; @@ -64,6 +63,7 @@ contract UmaV2AssertionProviderTest is Helper { ); uint256 condition = 2; umaV2AssertionProvider.setConditionType(marketId, condition); + deal(USDC_TOKEN, address(umaV2AssertionProvider), 1000e6); } //////////////////////////////////////////////// diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index 8c7915f2..db4027d0 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -50,7 +50,7 @@ contract UmaV2PriceProviderTest is Helper { umaDecimals = 8; umaDescription = "FUSD/USD"; umaCurrency = USDC_TOKEN; - ancillaryData = "base:FUSD,baseAddress:0x630410530785377d49992824a70b43bd5c482c9a,baseChain: 42161,quote:USD,quoteDetails:United States Dollar,rounding:6,fallback:"https://www.coingecko.com/en/coins/uma",configuration:{"type": "medianizer","minTimeBetweenUpdates": 60,"twapLength": 600,"medianizedFeeds":[{"type": "cryptowatch", "exchange": "coinbase-pro", "pair": "umausd" }, { "type": "cryptowatch", "exchange": "binance", "pair": "umausdt" }, { "type": "cryptowatch", "exchange": "okex", "pair": "umausdt" }]}"; + ancillaryData = 'base:FUSD,baseAddress:0x630410530785377d49992824a70b43bd5c482c9a,baseChain: 42161,quote:USD,quoteDetails:United States Dollar,rounding:6,fallback:"https://www.coingecko.com/en/coins/uma",configuration:{"type": "medianizer","minTimeBetweenUpdates": 60,"twapLength": 600,"medianizedFeeds":[{"type": "cryptowatch", "exchange": "coinbase-pro", "pair": "umausd" }, { "type": "cryptowatch", "exchange": "binance", "pair": "umausdt" }, { "type": "cryptowatch", "exchange": "okex", "pair": "umausdt" }]}'; requiredBond = 1e6; umaV2PriceProvider = new UmaV2PriceProvider( @@ -65,6 +65,7 @@ contract UmaV2PriceProviderTest is Helper { ); uint256 condition = 2; umaV2PriceProvider.setConditionType(marketId, condition); + deal(USDC_TOKEN, address(umaV2PriceProvider), 1000e6); } //////////////////////////////////////////////// From 57bdb5c97a88a83108a0eeb2f03ec6f93adaf9a2 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 12 Oct 2023 14:58:00 -0600 Subject: [PATCH 16/25] feat: dynamic data assertion v3 --- .../UmaV3DynamicAssertionProvider.sol | 300 ++++++++++ .../UmaV3EventAssertionProvider.sol | 6 +- .../UmaV3PriceAssertionProvider.sol | 5 +- test/V2/Helper.sol | 1 + .../UmaV3DynamicAssertionProvider.sol | 531 ++++++++++++++++++ 5 files changed, 841 insertions(+), 2 deletions(-) create mode 100644 src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol create mode 100644 test/V2/oracles/individual/UmaV3DynamicAssertionProvider.sol diff --git a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol new file mode 100644 index 00000000..5699e766 --- /dev/null +++ b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IConditionProvider} from "../../interfaces/IConditionProvider.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IOptimisticOracleV3} from "../../interfaces/IOptimisticOracleV3.sol"; +import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol"; +import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "forge-std/console.sol"; + +/// @notice Assertion provider where the condition can be checked without a required begin time e.g. 1 PEPE > $1000 or BTC market > 2x ETH market cap +/// @dev This provider would not work if you needed to check if x happened between time y and z +contract UmaV3DynamicAssertionProvider is Ownable { + using SafeTransferLib for ERC20; + struct MarketAnswer { + bool activeAssertion; + uint128 updatedAt; + uint8 answer; + bytes32 assertionId; + } + + struct AssertionData { + uint128 assertionData; + uint128 updatedAt; + } + + string public constant OUTCOME_1 = "true. "; + string public constant OUTCOME_2 = "false. "; + + // Uma V3 + uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. + address public immutable currency; // Currency used for all prediction markets + bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. + IOptimisticOracleV3 public immutable umaV3; + + // Market info + uint256 public immutable timeOut; + uint256 public immutable decimals; + string public description; + bytes public assertionDescription; + AssertionData public assertionData; // The uint data value for the market + uint256 public requiredBond; // Bond required to assert on a market + + mapping(uint256 => uint256) public marketIdToConditionType; + mapping(uint256 => MarketAnswer) public marketIdToAnswer; + mapping(bytes32 => uint256) public assertionIdToMarket; + + event MarketAsserted(uint256 marketId, bytes32 assertionId); + event AssertionResolved(bytes32 assertionId, bool assertion); + event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + event BondUpdated(uint256 newBond); + event AssertionDataUpdated(uint256 newData); + + /** + @param _decimals is decimals for the provider maker if relevant + @param _description is for the price provider market + @param _timeOut is the max time between receiving callback and resolving market condition + @param _umaV3 is the V3 Uma Optimistic Oracle + @param _defaultIdentifier is UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. + @param _currency is currency used to post the bond + @param _assertionDescription is description used for the market + @param _requiredBond is bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This must be >= getMinimumBond(address(currency)). + */ + constructor( + uint256 _decimals, + string memory _description, + uint256 _timeOut, + address _umaV3, + bytes32 _defaultIdentifier, + address _currency, + bytes memory _assertionDescription, + uint256 _requiredBond + ) { + if (_decimals == 0) revert InvalidInput(); + if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) + revert InvalidInput(); + if (_timeOut == 0) revert InvalidInput(); + if (_umaV3 == address(0)) revert ZeroAddress(); + if ( + keccak256(abi.encodePacked(_defaultIdentifier)) == + keccak256(abi.encodePacked(bytes32(""))) + ) revert InvalidInput(); + if (_currency == address(0)) revert ZeroAddress(); + if ( + keccak256(abi.encodePacked(_assertionDescription)) == + keccak256(bytes("")) + ) revert InvalidInput(); + if (_requiredBond == 0) revert InvalidInput(); + + decimals = _decimals; + description = _description; + timeOut = _timeOut; + umaV3 = IOptimisticOracleV3(_umaV3); + defaultIdentifier = _defaultIdentifier; + currency = _currency; + assertionDescription = _assertionDescription; + requiredBond = _requiredBond; + assertionData.updatedAt = uint128(block.timestamp); + } + + /*////////////////////////////////////////////////////////////// + ADMIN + //////////////////////////////////////////////////////////////*/ + function setConditionType( + uint256 _marketId, + uint256 _condition + ) external onlyOwner { + if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); + if (_condition != 1 && _condition != 2) revert InvalidInput(); + marketIdToConditionType[_marketId] = _condition; + emit MarketConditionSet(_marketId, _condition); + } + + function updateRequiredBond(uint256 newBond) external onlyOwner { + if (newBond == 0) revert InvalidInput(); + requiredBond = newBond; + emit BondUpdated(newBond); + } + + function updateAssertionData(uint256 _newData) public onlyOwner { + if (_newData == 0) revert InvalidInput(); + assertionData = AssertionData({ + assertionData: uint128(_newData), + updatedAt: uint128(block.timestamp) + }); + + emit AssertionDataUpdated(_newData); + } + + function updateAssertionDataAndFetch( + uint256 _newData, + uint256 _marketId + ) external onlyOwner returns (bytes32) { + updateAssertionData(_newData); + return fetchAssertion(_marketId); + } + + /*////////////////////////////////////////////////////////////// + CALLBACK + //////////////////////////////////////////////////////////////*/ + // Callback from settled assertion. + // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. + // Otherwise, assertedOutcomeId is reset and the market can be asserted again. + function assertionResolvedCallback( + bytes32 _assertionId, + bool _assertedTruthfully + ) external { + if (msg.sender != address(umaV3)) revert InvalidCaller(); + + uint256 marketId = assertionIdToMarket[_assertionId]; + MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; + if (marketAnswer.activeAssertion == false) revert AssertionInactive(); + + marketAnswer.updatedAt = uint128(block.timestamp); + marketAnswer.answer = _assertedTruthfully ? 1 : 0; + marketAnswer.activeAssertion = false; + marketIdToAnswer[marketId] = marketAnswer; + + emit AssertionResolved(_assertionId, _assertedTruthfully); + } + + /*////////////////////////////////////////////////////////////// + EXTERNAL + //////////////////////////////////////////////////////////////*/ + + function fetchAssertion( + uint256 _marketId + ) public returns (bytes32 assertionId) { + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; + if (marketAnswer.activeAssertion == true) revert AssertionActive(); + if ((block.timestamp - assertionData.updatedAt) > timeOut) + revert AssertionDataEmpty(); + + // Configure bond and claim information + uint256 minimumBond = umaV3.getMinimumBond(address(currency)); + uint256 reqBond = requiredBond; + uint256 bond = reqBond > minimumBond ? reqBond : minimumBond; + bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); + + // Transfer bond from sender and request assertion + ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); + ERC20(currency).safeApprove(address(umaV3), bond); + assertionId = umaV3.assertTruth( + claim, + msg.sender, // Asserter + address(this), // Receive callback to this contract + address(0), // No sovereign security + ASSERTION_LIVENESS, + IERC20(currency), + bond, + defaultIdentifier, + bytes32(0) // No domain + ); + + assertionIdToMarket[assertionId] = _marketId; + marketIdToAnswer[_marketId].activeAssertion = true; + marketIdToAnswer[_marketId].assertionId = assertionId; + + emit MarketAsserted(_marketId, assertionId); + } + + /** @notice Fetch the assertion state of the market + * @return bool If assertion is true or false for the market condition + */ + function checkAssertion( + uint256 _marketId + ) public view virtual returns (bool) { + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; + + if ((block.timestamp - marketAnswer.updatedAt) > timeOut) + revert PriceTimedOut(); + + if (marketAnswer.answer == 1) return true; + else return false; + } + + // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future + /** @notice Fetch price and return condition + * @param _marketId the marketId for the market + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256, + uint256 _marketId + ) public view virtual returns (bool, int256 price) { + uint256 conditionType = marketIdToConditionType[_marketId]; + bool condition = checkAssertion(_marketId); + + if (conditionType == 1) return (condition, price); + else if (conditionType == 2) return (condition, price); + else revert ConditionTypeNotSet(); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////*/ + /** + @param _conditionType is the condition type for the market + @dev encode claim would look like: "As of assertion timestamp , " + Where inputs could be: "As of assertion timestamp 1625097600, <0.997>" + @return bytes for the claim + */ + function _composeClaim( + uint256 _conditionType + ) internal view returns (bytes memory) { + return + abi.encodePacked( + "As of assertion timestamp ", + _toUtf8BytesUint(block.timestamp), + ", the following statement is", + _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, + assertionDescription, + assertionData.assertionData + ); + } + + /** + * @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type. + * @dev This method is based off of this code: https://stackoverflow.com/a/65707309. + * @dev Pulled from UMA protocol packages: https://github.com/UMAprotocol/protocol/blob/9bfbbe98bed0ac7d9c924115018bb0e26987e2b5/packages/core/contracts/common/implementation/AncillaryData.sol + */ + function _toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { + if (x == 0) { + return "0"; + } + uint256 j = x; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len; + while (x != 0) { + k = k - 1; + uint8 temp = (48 + uint8(x - (x / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + x /= 10; + } + return bstr; + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + error ZeroAddress(); + error InvalidInput(); + error PriceTimedOut(); + error ConditionTypeNotSet(); + error ConditionTypeSet(); + error InvalidCaller(); + error AssertionActive(); + error AssertionInactive(); + error InvalidCallback(); + error AssertionDataEmpty(); +} diff --git a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol index 84b526d9..1caa7286 100644 --- a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol @@ -118,7 +118,7 @@ contract UmaV3EventAssertionProvider is Ownable { } /*////////////////////////////////////////////////////////////// - EXTERNAL + CALLBACK //////////////////////////////////////////////////////////////*/ // Callback from settled assertion. // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. @@ -141,6 +141,10 @@ contract UmaV3EventAssertionProvider is Ownable { emit AssertionResolved(_assertionId, _assertedTruthfully); } + /*////////////////////////////////////////////////////////////// + EXTERNAL + //////////////////////////////////////////////////////////////*/ + function fetchAssertion( uint256 _marketId ) external returns (bytes32 assertionId) { diff --git a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol index e62158cc..a7531a3a 100644 --- a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol @@ -110,7 +110,7 @@ contract UmaV3PriceAssertionProvider is Ownable { } /*////////////////////////////////////////////////////////////// - EXTERNAL + CALLBACK //////////////////////////////////////////////////////////////*/ // Callback from settled assertion. // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. @@ -133,6 +133,9 @@ contract UmaV3PriceAssertionProvider is Ownable { emit AssertionResolved(_assertionId, _assertedTruthfully); } + /*////////////////////////////////////////////////////////////// + EXTERNAL + //////////////////////////////////////////////////////////////*/ function fetchAssertion( uint256 _marketId ) external returns (bytes32 assertionId) { diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index 5f8626e8..59db406f 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -13,6 +13,7 @@ contract Helper is Test { event BondUpdated(uint256 newBond); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event CoverageStartUpdated(uint256 startTime); + event AssertionDataUpdated(uint256 newData); uint256 public constant STRIKE = 1000000000000000000; uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; diff --git a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.sol b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.sol new file mode 100644 index 00000000..254ac50d --- /dev/null +++ b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.sol @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Helper} from "../../Helper.sol"; +import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; +import { + UmaV3DynamicAssertionProvider +} from "../../../../src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol"; +import {TimeLock} from "../../../../src/v2/TimeLock.sol"; +import { + MockOracleAnswerZero, + MockOracleRoundOutdated, + MockOracleTimeOut +} from "../MockOracles.sol"; +import {MockUma} from "../MockUma.sol"; +import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; + +contract UmaV3DynamicAssertionProviderTest is Helper { + uint256 public arbForkId; + VaultFactoryV2 public factory; + UmaV3DynamicAssertionProvider public umaPriceProvider; + uint256 public marketId = 2; + ERC20 public wethAsset; + + //////////////////////////////////////////////// + // HELPERS // + //////////////////////////////////////////////// + uint256 public UMA_DECIMALS = 18; + address public UMA_OO_V3 = address(0x123); + string public UMA_DESCRIPTION = "USDC"; + uint256 public REQUIRED_BOND = 1e6; + bytes32 public defaultIdentifier = bytes32("abc"); + bytes public assertionDescription; + + function setUp() public { + arbForkId = vm.createFork(ARBITRUM_RPC_URL); + vm.selectFork(arbForkId); + wethAsset = ERC20(WETH_ADDRESS); + + // TODO: Should this be encoded or encode packed? + assertionDescription = abi.encode( + "USDC/USD exchange rate is above 0.997" + ); + + address timelock = address(new TimeLock(ADMIN)); + factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + uint256 condition = 2; + umaPriceProvider.setConditionType(marketId, condition); + } + + //////////////////////////////////////////////// + // STATE // + //////////////////////////////////////////////// + function testUmaCreation() public { + assertEq(umaPriceProvider.ASSERTION_LIVENESS(), 7200); + assertEq(umaPriceProvider.currency(), WETH_ADDRESS); + assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); + assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); + assertEq(umaPriceProvider.requiredBond(), REQUIRED_BOND); + + assertEq(umaPriceProvider.timeOut(), TIME_OUT); + assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); + assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); + assertEq(umaPriceProvider.assertionDescription(), assertionDescription); + (uint256 assertionData, uint256 updatedAt) = umaPriceProvider + .assertionData(); + assertEq(assertionData, 0); + assertEq(updatedAt, block.timestamp); + assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); + } + + function testUpdateRequiredBond() public { + uint256 newBond = 1e6; + + vm.expectEmit(true, true, false, false); + emit BondUpdated(newBond); + umaPriceProvider.updateRequiredBond(newBond); + assertEq(umaPriceProvider.requiredBond(), newBond); + } + + function testUpdateAssertionData() public { + uint256 newData = 1e6; + + vm.expectEmit(true, true, true, true); + emit AssertionDataUpdated(newData); + umaPriceProvider.updateAssertionData(newData); + (uint256 assertionData, uint256 updatedAt) = umaPriceProvider + .assertionData(); + assertEq(assertionData, newData); + assertEq(updatedAt, block.timestamp); + } + + function testUpdateAssertionDataAndFetch() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 1); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + // Moving forward so the constructor data is invalid + vm.warp(block.timestamp + 2 days); + uint256 _newData = 4e6; + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + _newData, + marketId + ); + + // Checking assertion links to marketId + uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); + assertEq(_marketId, marketId); + assertEq(wethAsset.balanceOf(address(mockUma)), 1e6); + + // Checking marketId info is correct + ( + bool activeAssertion, + uint128 updatedAt, + uint8 answer, + bytes32 assertionIdReturned + ) = umaPriceProvider.marketIdToAnswer(_marketId); + assertEq(activeAssertion, true); + assertEq(updatedAt, uint128(0)); + assertEq(answer, 0); + assertEq(assertionIdReturned, _assertionId); + } + + //////////////////////////////////////////////// + // FUNCTIONS // + //////////////////////////////////////////////// + function testConditionOneMetUma() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 1); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + // Moving forward so the constructor data is invalid + vm.warp(block.timestamp + 2 days); + uint256 _newData = 4e6; + umaPriceProvider.updateAssertionData(_newData); + + vm.expectEmit(true, false, false, true); + emit MarketAsserted(marketId, bytes32(abi.encode(0x12))); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + + // Checking assertion links to marketId + uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); + assertEq(_marketId, marketId); + assertEq(wethAsset.balanceOf(address(mockUma)), 1e6); + + // Checking marketId info is correct + ( + bool activeAssertion, + uint128 updatedAt, + uint8 answer, + bytes32 assertionIdReturned + ) = umaPriceProvider.marketIdToAnswer(_marketId); + assertEq(activeAssertion, true); + assertEq(updatedAt, uint128(0)); + assertEq(answer, 0); + assertEq(assertionIdReturned, _assertionId); + + vm.expectEmit(true, false, false, true); + emit AssertionResolved(_assertionId, true); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + // Checking resolved callback info + ( + activeAssertion, + updatedAt, + answer, + assertionIdReturned + ) = umaPriceProvider.marketIdToAnswer(_marketId); + assertEq(activeAssertion, false); + assertEq(updatedAt, uint128(block.timestamp)); + assertEq(answer, 1); + + (bool condition, int256 price) = umaPriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price == 0); + assertEq(condition, true); + } + + function testConditionTwoMetUma() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + // Moving forward so the constructor data is invalid + vm.warp(block.timestamp + 2 days); + uint256 _newData = 4e6; + umaPriceProvider.updateAssertionData(_newData); + + // Querying for assertion + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + (bool condition, int256 price) = umaPriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price == 0); + assertEq(condition, true); + } + + function testCheckAssertionTrue() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + // Moving forward so the constructor data is invalid + vm.warp(block.timestamp + 2 days); + uint256 _newData = 4e6; + umaPriceProvider.updateAssertionData(_newData); + + // Querying for assertion + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + bool condition = umaPriceProvider.checkAssertion(marketId); + assertEq(condition, true); + } + + function testCheckAssertionFalse() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + // Moving forward so the constructor data is invalid + vm.warp(block.timestamp + 2 days); + uint256 _newData = 4e6; + umaPriceProvider.updateAssertionData(_newData); + + // Querying for assertion + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + false + ); + + bool condition = umaPriceProvider.checkAssertion(marketId); + assertEq(condition, false); + } + + //////////////////////////////////////////////// + // REVERT CASES // + //////////////////////////////////////////////// + function testRevertConstructorInputsUma() public { + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + new UmaV3DynamicAssertionProvider( + 0, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + string(""), + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + 0, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); + new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(0), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + bytes32(""), + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); + new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + address(0), + assertionDescription, + REQUIRED_BOND + ); + + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + bytes(""), + REQUIRED_BOND + ); + + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + 0 + ); + } + + function testRevertConditionTypeSetUma() public { + vm.expectRevert( + UmaV3DynamicAssertionProvider.ConditionTypeSet.selector + ); + umaPriceProvider.setConditionType(2, 0); + } + + function testRevertInvalidInputConditionUma() public { + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + umaPriceProvider.setConditionType(0, 0); + + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + umaPriceProvider.setConditionType(0, 3); + } + + function testRevertInvalidInputRequiredBond() public { + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + umaPriceProvider.updateRequiredBond(0); + } + + function testRevertInvalidInputAssertionData() public { + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + umaPriceProvider.updateAssertionData(0); + } + + function testRevertInvalidCallerCallback() public { + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidCaller.selector); + umaPriceProvider.assertionResolvedCallback(bytes32(""), true); + } + + function testRevertAssertionInactive() public { + vm.prank(UMA_OO_V3); + + vm.expectRevert( + UmaV3DynamicAssertionProvider.AssertionInactive.selector + ); + umaPriceProvider.assertionResolvedCallback( + bytes32(abi.encode(0x12)), + true + ); + } + + function testRevertAssertionActive() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3DynamicAssertionProvider + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 1); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + umaPriceProvider.fetchAssertion(marketId); + + vm.expectRevert(UmaV3DynamicAssertionProvider.AssertionActive.selector); + umaPriceProvider.fetchAssertion(marketId); + } + + function testRevertAssertionDataEmpty() public { + vm.warp(block.timestamp + 2 days); + vm.expectRevert( + UmaV3DynamicAssertionProvider.AssertionDataEmpty.selector + ); + umaPriceProvider.fetchAssertion(marketId); + } + + function testRevertTimeOutUma() public { + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + UMA_OO_V3, + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + vm.expectRevert(UmaV3DynamicAssertionProvider.PriceTimedOut.selector); + umaPriceProvider.checkAssertion(123); + } +} From 2d4a93c126e2e0defeae36c2176d2439e08bca30 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Tue, 17 Oct 2023 17:47:29 -0600 Subject: [PATCH 17/25] feat: deploy scripts UmaV2 --- script/v2/V2DeployContracts.s.sol | 80 ++++++++++++++----- .../individual/UmaV2AssertionProvider.sol | 27 +++---- .../oracles/individual/UmaV2PriceProvider.sol | 27 +++---- test/V2/Helper.sol | 1 + .../individual/UmaV2AssertionProvider.t.sol | 65 +++++---------- .../individual/UmaV2PriceProvider.t.sol | 63 +++++---------- 6 files changed, 118 insertions(+), 145 deletions(-) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index c8b55397..a6ffa21c 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -14,6 +14,8 @@ import "../../src/v2/oracles/individual/RedstonePriceProvider.sol"; import "../../src/v2/oracles/individual/DIAPriceProvider.sol"; import "../../src/v2/oracles/individual/CVIPriceProvider.sol"; import "../../src/v2/oracles/individual/GdaiPriceProvider.sol"; +import "../../src/v2/oracles/individual/UmaV2PriceProvider.sol"; +import "../../src/v2/oracles/individual/UmaV2AssertionProvider.sol"; import "../../src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol"; import "../../src/v2/TimeLock.sol"; import "./V2Helper.sol"; @@ -113,8 +115,8 @@ contract V2DeployContracts is Script, HelperV2 { // timeOut // ); - address gdaiVault = 0xd85E038593d7A098614721EaE955EC2022B9B91B; - GdaiPriceProvider gdaiPriceProvider = new GdaiPriceProvider(gdaiVault); + // address gdaiVault = 0xd85E038593d7A098614721EaE955EC2022B9B91B; + // GdaiPriceProvider gdaiPriceProvider = new GdaiPriceProvider(gdaiVault); // address cviOracle = 0x649813B6dc6111D67484BaDeDd377D32e4505F85; // uint256 cviDecimals = 0; @@ -128,24 +130,55 @@ contract V2DeployContracts is Script, HelperV2 { // DIAPriceProvider diaPriceProvider = new DIAPriceProvider(diaOracleV2); uint256 timeOut = 2 hours; + address umaCurrency = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + address umaV2Finder = 0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1; + uint256 reward = 1e6; + uint256 umaDecimals = 18; - address umaOOV3 = address(0x123); - string memory umaDescription = "USDC"; - uint256 requiredBond = 1e6; - bytes32 defaultIdentifier = bytes32("abc"); - bytes - memory assertionDescription = "The USDC/USD exchange is above 0.997"; - address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS - UmaV3PriceAssertionProvider umaPriceProvider = new UmaV3PriceAssertionProvider( - umaDecimals, - umaDescription, - timeOut, - umaOOV3, - defaultIdentifier, - currency, - assertionDescription, - requiredBond - ); + string memory umaDescription = "FUSD/USD"; + string + memory ancillaryData = 'base:FDUSD,baseAddress:0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409,baseChain: 1,quote:USD,quoteDetails:United States Dollar,rounding:18,fallback:"https://www.coingecko.com/en/coins/first-digital-usd",configuration:{"type": "medianizer","minTimeBetweenUpdates": 60,"twapLength": 600,"medianizedFeeds":[{ "type": "cryptowatch", "exchange": "binance", "pair": "fdusdusdt" }]}'; + + UmaV2PriceProvider umaV2PriceProvider = new UmaV2PriceProvider( + timeOut, + umaDecimals, + umaDescription, + umaV2Finder, + umaCurrency, + ancillaryData, + reward + ); + + // string memory umaDescription = "AAVE aUSDC Hack Market"; + // string + // memory ancillaryData = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; + // UmaV2AssertionProvider umaV2AssertionProvider = new UmaV2AssertionProvider( + // timeOut, + // umaDescription, + // umaV2Finder, + // umaCurrency, + // ancillaryData, + // reward + // ); + + // uint256 umaDecimals = 18; + // address umaOOV3 = address(0x123); + // string memory umaDescription = "USDC"; + // uint256 requiredBond = 1e6; + // bytes32 defaultIdentifier = bytes32("abc"); + // bytes + // memory assertionDescription = "The USDC/USD exchange is above 0.997"; + // address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS + // UmaV3PriceAssertionProvider umaPriceProvider = new UmaV3PriceAssertionProvider( + // umaDecimals, + // umaDescription, + // timeOut, + // umaOOV3, + // defaultIdentifier, + // currency, + // assertionDescription, + // requiredBond + // ); // vaultFactory.whitelistController(address(controller)); // KeeperV2 resolveKeeper = new KeeperV2( @@ -185,10 +218,15 @@ contract V2DeployContracts is Script, HelperV2 { // address(chainlinkPriceProviderMAI) // ); // console2.log("Redstone Price Provider", address(redstoneProvider)); - console2.log("Gdai Price Provider", address(gdaiPriceProvider)); + // console2.log("Gdai Price Provider", address(gdaiPriceProvider)); // console2.log("CVI Price Provider", address(cviPriceProvider)); // console2.log("Dia Price Provider", address(diaPriceProvider)); - console2.log("Uma Price Provider", address(umaPriceProvider)); + console2.log("Uma V2 Price Provider", address(umaV2PriceProvider)); + // console2.log( + // "Uma V2 Assertion Provider", + // address(umaV2AssertionProvider) + // ); + // console2.log("Uma Price Provider", address(umaPriceProvider)); // console2.log("resolveKeeper address", address(resolveKeeper)); // console2.log("resolveKeeperGenericController address", address(resolveKeeperGenericController)); diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol index 552eb3d6..8c7a9f6d 100644 --- a/src/v2/oracles/individual/UmaV2AssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {IVaultFactoryV2} from "../../interfaces/IVaultFactoryV2.sol"; import {IUmaV2} from "../../interfaces/IUmaV2.sol"; import {IFinder} from "../../interfaces/IFinder.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -31,7 +30,6 @@ contract UmaV2AssertionProvider is Ownable { ". P1: 0 for NO, P2: 1 for YES, P3: 2 for UNDETERMINED"; uint256 public immutable timeOut; - IVaultFactoryV2 public immutable vaultFactory; IUmaV2 public immutable oo; IFinder public immutable finder; IERC20 public immutable currency; @@ -40,27 +38,25 @@ contract UmaV2AssertionProvider is Ownable { string public ancillaryData; AssertionAnswer public answer; AssertionAnswer public pendingAnswer; - uint256 public requiredBond; + uint256 public reward; uint256 public coverageStart; mapping(uint256 => uint256) public marketIdToConditionType; event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event CoverageStartUpdated(uint256 startTime); - event BondUpdated(uint256 newBond); + event RewardUpdated(uint256 newReward); event PriceSettled(int256 price); event PriceRequested(); constructor( - address _factory, uint256 _timeOut, string memory _description, address _finder, address _currency, string memory _ancillaryData, - uint256 _requiredBond + uint256 _reward ) { - if (_factory == address(0)) revert ZeroAddress(); if (_timeOut == 0) revert InvalidInput(); if (keccak256(bytes(_description)) == keccak256("")) revert InvalidInput(); @@ -68,9 +64,8 @@ contract UmaV2AssertionProvider is Ownable { if (_currency == address(0)) revert ZeroAddress(); if (keccak256(bytes(_ancillaryData)) == keccak256("")) revert InvalidInput(); - if (_requiredBond == 0) revert InvalidInput(); + if (_reward == 0) revert InvalidInput(); - vaultFactory = IVaultFactoryV2(_factory); timeOut = _timeOut; description = _description; @@ -78,7 +73,7 @@ contract UmaV2AssertionProvider is Ownable { oo = IUmaV2(finder.getImplementationAddress("OptimisticOracleV2")); currency = IERC20(_currency); ancillaryData = _ancillaryData; - requiredBond = _requiredBond; + reward = _reward; coverageStart = block.timestamp; } @@ -101,10 +96,10 @@ contract UmaV2AssertionProvider is Ownable { emit CoverageStartUpdated(_coverageStart); } - function updateRequiredBond(uint256 newBond) external onlyOwner { - if (newBond == 0) revert InvalidInput(); - requiredBond = newBond; - emit BondUpdated(newBond); + function updateReward(uint256 newReward) external onlyOwner { + if (newReward == 0) revert InvalidInput(); + reward = newReward; + emit RewardUpdated(newReward); } /*////////////////////////////////////////////////////////////// @@ -142,13 +137,13 @@ contract UmaV2AssertionProvider is Ownable { coverageStart, ANCILLARY_TAIL ); - currency.approve(address(oo), requiredBond); + currency.approve(address(oo), reward); oo.requestPrice( PRICE_IDENTIFIER, block.timestamp, _bytesAncillary, currency, - requiredBond + reward ); oo.setCustomLiveness( PRICE_IDENTIFIER, diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol index 3a773f64..c2213a2d 100644 --- a/src/v2/oracles/individual/UmaV2PriceProvider.sol +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {IVaultFactoryV2} from "../../interfaces/IVaultFactoryV2.sol"; import {IUmaV2} from "../../interfaces/IUmaV2.sol"; import {IFinder} from "../../interfaces/IFinder.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -31,7 +30,6 @@ contract UmaV2PriceProvider is Ownable { bytes32 public constant PRICE_IDENTIFIER = "TOKEN_PRICE"; uint256 public immutable timeOut; - IVaultFactoryV2 public immutable vaultFactory; IUmaV2 public immutable oo; IFinder public immutable finder; uint256 public immutable decimals; @@ -41,26 +39,24 @@ contract UmaV2PriceProvider is Ownable { string public ancillaryData; PriceAnswer public answer; PriceAnswer public pendingAnswer; - uint256 public requiredBond; + uint256 public reward; mapping(uint256 => uint256) public marketIdToConditionType; event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); - event BondUpdated(uint256 newBond); + event RewardUpdated(uint256 newReward); event PriceSettled(int256 price); event PriceRequested(); constructor( - address _factory, uint256 _timeOut, uint256 _decimals, string memory _description, address _finder, address _currency, string memory _ancillaryData, - uint256 _requiredBond + uint256 _reward ) { - if (_factory == address(0)) revert ZeroAddress(); if (_timeOut == 0) revert InvalidInput(); if (keccak256(bytes(_description)) == keccak256("")) revert InvalidInput(); @@ -68,9 +64,8 @@ contract UmaV2PriceProvider is Ownable { if (_currency == address(0)) revert ZeroAddress(); if (keccak256(bytes(_ancillaryData)) == keccak256("")) revert InvalidInput(); - if (_requiredBond == 0) revert InvalidInput(); + if (_reward == 0) revert InvalidInput(); - vaultFactory = IVaultFactoryV2(_factory); timeOut = _timeOut; decimals = _decimals; description = _description; @@ -79,7 +74,7 @@ contract UmaV2PriceProvider is Ownable { oo = IUmaV2(finder.getImplementationAddress("OptimisticOracleV2")); currency = IERC20(_currency); ancillaryData = _ancillaryData; - requiredBond = _requiredBond; + reward = _reward; } /*////////////////////////////////////////////////////////////// @@ -95,10 +90,10 @@ contract UmaV2PriceProvider is Ownable { emit MarketConditionSet(_marketId, _condition); } - function updateRequiredBond(uint256 newBond) external onlyOwner { - if (newBond == 0) revert InvalidInput(); - requiredBond = newBond; - emit BondUpdated(newBond); + function updateReward(uint256 newReward) external onlyOwner { + if (newReward == 0) revert InvalidInput(); + reward = newReward; + emit RewardUpdated(newReward); } /*////////////////////////////////////////////////////////////// @@ -132,13 +127,13 @@ contract UmaV2PriceProvider is Ownable { if (pendingAnswer.startedAt != 0) revert RequestInProgress(); bytes memory _bytesAncillary = abi.encodePacked(ancillaryData); - currency.approve(address(oo), requiredBond); + currency.approve(address(oo), reward); oo.requestPrice( PRICE_IDENTIFIER, block.timestamp, _bytesAncillary, currency, - requiredBond + reward ); oo.setCustomLiveness( PRICE_IDENTIFIER, diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index 59db406f..345891aa 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -11,6 +11,7 @@ contract Helper is Test { event AssertionResolved(bytes32 assertionId, bool assertion); event ProtocolFeeCollected(uint256 indexed epochId, uint256 indexed fee); event BondUpdated(uint256 newBond); + event RewardUpdated(uint256 newReward); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event CoverageStartUpdated(uint256 startTime); event AssertionDataUpdated(uint256 newData); diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index 092e4c1b..73e54e11 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; -import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; import { UmaV2AssertionProvider } from "../../../../src/v2/oracles/individual/UmaV2AssertionProvider.sol"; @@ -23,7 +22,6 @@ import { contract UmaV2AssertionProviderTest is Helper { uint256 public arbForkId; - VaultFactoryV2 public factory; UmaV2AssertionProvider public umaV2AssertionProvider; uint256 public marketId = 2; @@ -37,29 +35,25 @@ contract UmaV2AssertionProviderTest is Helper { string public umaDescription; address public umaCurrency; string public ancillaryData; - uint256 public requiredBond; + uint256 public reward; function setUp() public { arbForkId = vm.createFork(ARBITRUM_RPC_URL); vm.selectFork(arbForkId); - address timelock = address(new TimeLock(ADMIN)); - factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaDecimals = 8; umaDescription = "FUSD/ETH"; umaCurrency = USDC_TOKEN; ancillaryData = "q: Curve USDC pool on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; - requiredBond = 1e6; + reward = 1e6; umaV2AssertionProvider = new UmaV2AssertionProvider( - address(factory), TIME_OUT, umaDescription, UMAV2_FINDER, umaCurrency, ancillaryData, - requiredBond + reward ); uint256 condition = 2; umaV2AssertionProvider.setConditionType(marketId, condition); @@ -73,16 +67,12 @@ contract UmaV2AssertionProviderTest is Helper { assertEq(umaV2AssertionProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); assertEq(umaV2AssertionProvider.PRICE_IDENTIFIER(), "YES_OR_NO_QUERY"); assertEq(umaV2AssertionProvider.timeOut(), TIME_OUT); - assertEq( - address(umaV2AssertionProvider.vaultFactory()), - address(factory) - ); assertEq(address(umaV2AssertionProvider.oo()), umaV2); assertEq(address(umaV2AssertionProvider.finder()), UMAV2_FINDER); assertEq(address(umaV2AssertionProvider.currency()), umaCurrency); assertEq(umaV2AssertionProvider.description(), umaDescription); assertEq(umaV2AssertionProvider.ancillaryData(), ancillaryData); - assertEq(umaV2AssertionProvider.requiredBond(), requiredBond); + assertEq(umaV2AssertionProvider.reward(), reward); assertEq(umaV2AssertionProvider.coverageStart(), block.timestamp); } @@ -109,13 +99,13 @@ contract UmaV2AssertionProviderTest is Helper { assertEq(umaV2AssertionProvider.coverageStart(), newCoverageStart); } - function testUpdateRequiredBondUmaV2Assert() public { - uint256 newBond = 1000; + function testUpdateRewardUmaV2Assert() public { + uint256 newReward = 1000; vm.expectEmit(true, true, true, true); - emit BondUpdated(newBond); - umaV2AssertionProvider.updateRequiredBond(newBond); + emit RewardUpdated(newReward); + umaV2AssertionProvider.updateReward(newReward); - assertEq(umaV2AssertionProvider.requiredBond(), newBond); + assertEq(umaV2AssertionProvider.reward(), newReward); } //////////////////////////////////////////////// @@ -130,13 +120,12 @@ contract UmaV2AssertionProviderTest is Helper { MockUmaV2 mockUmaV2 = new MockUmaV2(); MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); umaV2AssertionProvider = new UmaV2AssertionProvider( - address(factory), TIME_OUT, umaDescription, address(umaMockFinder), umaCurrency, ancillaryData, - requiredBond + reward ); // Configuring the pending answer @@ -218,75 +207,58 @@ contract UmaV2AssertionProviderTest is Helper { // REVERT CASES // //////////////////////////////////////////////// function testRevertConstructorInputsUmaV2Assert() public { - vm.expectRevert(UmaV2AssertionProvider.ZeroAddress.selector); - new UmaV2AssertionProvider( - address(0), - TIME_OUT, - umaDescription, - UMAV2_FINDER, - umaCurrency, - ancillaryData, - requiredBond - ); - vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); new UmaV2AssertionProvider( - address(factory), 0, umaDescription, UMAV2_FINDER, umaCurrency, ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); new UmaV2AssertionProvider( - address(factory), TIME_OUT, "", UMAV2_FINDER, umaCurrency, ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2AssertionProvider.ZeroAddress.selector); new UmaV2AssertionProvider( - address(factory), TIME_OUT, umaDescription, address(0), umaCurrency, ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2AssertionProvider.ZeroAddress.selector); new UmaV2AssertionProvider( - address(factory), TIME_OUT, umaDescription, UMAV2_FINDER, address(0), ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); new UmaV2AssertionProvider( - address(factory), TIME_OUT, umaDescription, UMAV2_FINDER, umaCurrency, "", - requiredBond + reward ); vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); new UmaV2AssertionProvider( - address(factory), TIME_OUT, umaDescription, UMAV2_FINDER, @@ -311,9 +283,9 @@ contract UmaV2AssertionProviderTest is Helper { umaV2AssertionProvider.updateCoverageStart(0); } - function testRevertInvalidInputUpdateRequiredBondUmaV2Assert() public { + function testRevertInvalidInputUpdateRewardUmaV2Assert() public { vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); - umaV2AssertionProvider.updateRequiredBond(0); + umaV2AssertionProvider.updateReward(0); } function testRevertInvalidCallerPriceSettledUmaV2Assert() public { @@ -366,13 +338,12 @@ contract UmaV2AssertionProviderTest is Helper { MockUmaV2 mockUmaV2 = new MockUmaV2(); MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); umaV2AssertionProvider = new UmaV2AssertionProvider( - address(factory), TIME_OUT, umaDescription, address(umaMockFinder), umaCurrency, ancillaryData, - requiredBond + reward ); // Configuring the pending answer diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index db4027d0..e35b00f5 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; -import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; import { UmaV2PriceProvider } from "../../../../src/v2/oracles/individual/UmaV2PriceProvider.sol"; @@ -24,7 +23,6 @@ import { contract UmaV2PriceProviderTest is Helper { uint256 public arbForkId; - VaultFactoryV2 public factory; UmaV2PriceProvider public umaV2PriceProvider; uint256 public marketId = 2; @@ -38,30 +36,26 @@ contract UmaV2PriceProviderTest is Helper { string public umaDescription; address public umaCurrency; string public ancillaryData; - uint256 public requiredBond; + uint256 public reward; function setUp() public { arbForkId = vm.createFork(ARBITRUM_RPC_URL); vm.selectFork(arbForkId); - address timelock = address(new TimeLock(ADMIN)); - factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaDecimals = 8; umaDescription = "FUSD/USD"; umaCurrency = USDC_TOKEN; ancillaryData = 'base:FUSD,baseAddress:0x630410530785377d49992824a70b43bd5c482c9a,baseChain: 42161,quote:USD,quoteDetails:United States Dollar,rounding:6,fallback:"https://www.coingecko.com/en/coins/uma",configuration:{"type": "medianizer","minTimeBetweenUpdates": 60,"twapLength": 600,"medianizedFeeds":[{"type": "cryptowatch", "exchange": "coinbase-pro", "pair": "umausd" }, { "type": "cryptowatch", "exchange": "binance", "pair": "umausdt" }, { "type": "cryptowatch", "exchange": "okex", "pair": "umausdt" }]}'; - requiredBond = 1e6; + reward = 1e6; umaV2PriceProvider = new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, umaDescription, UMAV2_FINDER, umaCurrency, ancillaryData, - requiredBond + reward ); uint256 condition = 2; umaV2PriceProvider.setConditionType(marketId, condition); @@ -76,14 +70,13 @@ contract UmaV2PriceProviderTest is Helper { assertEq(umaV2PriceProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); assertEq(umaV2PriceProvider.PRICE_IDENTIFIER(), "TOKEN_PRICE"); assertEq(umaV2PriceProvider.timeOut(), TIME_OUT); - assertEq(address(umaV2PriceProvider.vaultFactory()), address(factory)); assertEq(address(umaV2PriceProvider.oo()), umaV2); assertEq(address(umaV2PriceProvider.finder()), UMAV2_FINDER); assertEq(umaV2PriceProvider.decimals(), umaDecimals); assertEq(address(umaV2PriceProvider.currency()), umaCurrency); assertEq(umaV2PriceProvider.description(), umaDescription); assertEq(umaV2PriceProvider.ancillaryData(), ancillaryData); - assertEq(umaV2PriceProvider.requiredBond(), requiredBond); + assertEq(umaV2PriceProvider.reward(), reward); } //////////////////////////////////////////////// @@ -100,13 +93,13 @@ contract UmaV2PriceProviderTest is Helper { assertEq(umaV2PriceProvider.marketIdToConditionType(_marketId), 1); } - function testUpdateRequiredBondUmaV2Price() public { - uint256 newBond = 1000; + function testUpdateRewardUmaV2Price() public { + uint256 newReward = 1000; vm.expectEmit(true, true, true, true); - emit BondUpdated(newBond); - umaV2PriceProvider.updateRequiredBond(newBond); + emit RewardUpdated(newReward); + umaV2PriceProvider.updateReward(newReward); - assertEq(umaV2PriceProvider.requiredBond(), newBond); + assertEq(umaV2PriceProvider.reward(), newReward); } //////////////////////////////////////////////// @@ -121,14 +114,13 @@ contract UmaV2PriceProviderTest is Helper { MockUmaV2 mockUmaV2 = new MockUmaV2(); MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); umaV2PriceProvider = new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, umaDescription, address(umaMockFinder), umaCurrency, ancillaryData, - requiredBond + reward ); // Configuring the pending answer @@ -231,81 +223,63 @@ contract UmaV2PriceProviderTest is Helper { // REVERT CASES // //////////////////////////////////////////////// function testRevertConstructorInputsUmaV2Price() public { - vm.expectRevert(UmaV2PriceProvider.ZeroAddress.selector); - new UmaV2PriceProvider( - address(0), - TIME_OUT, - umaDecimals, - umaDescription, - UMAV2_FINDER, - umaCurrency, - ancillaryData, - requiredBond - ); - vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); new UmaV2PriceProvider( - address(factory), 0, umaDecimals, umaDescription, UMAV2_FINDER, umaCurrency, ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, "", UMAV2_FINDER, umaCurrency, ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2PriceProvider.ZeroAddress.selector); new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, umaDescription, address(0), umaCurrency, ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2PriceProvider.ZeroAddress.selector); new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, umaDescription, UMAV2_FINDER, address(0), ancillaryData, - requiredBond + reward ); vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, umaDescription, UMAV2_FINDER, umaCurrency, "", - requiredBond + reward ); vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, umaDescription, @@ -326,9 +300,9 @@ contract UmaV2PriceProviderTest is Helper { umaV2PriceProvider.setConditionType(0, 0); } - function testRevertInvalidInputUpdateRequiredBondUmaV2Price() public { + function testRevertInvalidInputupdateRewardUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); - umaV2PriceProvider.updateRequiredBond(0); + umaV2PriceProvider.updateReward(0); } function testRevertInvalidCallerPriceSettledUmaV2Price() public { @@ -380,14 +354,13 @@ contract UmaV2PriceProviderTest is Helper { MockUmaV2 mockUmaV2 = new MockUmaV2(); MockUmaFinder umaMockFinder = new MockUmaFinder(address(mockUmaV2)); umaV2PriceProvider = new UmaV2PriceProvider( - address(factory), TIME_OUT, umaDecimals, umaDescription, address(umaMockFinder), umaCurrency, ancillaryData, - requiredBond + reward ); // Configuring the pending answer From 15535b8c1b9273abdb0ee2b7dca957364aa0b992 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Fri, 20 Oct 2023 08:04:12 -0600 Subject: [PATCH 18/25] feat: whitelist added for assertionFetch --- .../UmaV3DynamicAssertionProvider.sol | 11 +++++++ .../UmaV3EventAssertionProvider.sol | 11 +++++++ .../UmaV3PriceAssertionProvider.sol | 11 +++++++ test/V2/Helper.sol | 1 + .../individual/UmaV2AssertionProvider.t.sol | 13 ++++++++ ...ol => UmaV3DynamicAssertionProvider.t.sol} | 32 ++++++++++++++++++ ....sol => UmaV3EventAssertionProvider.t.sol} | 33 ++++++++++++++++++- ....sol => UmaV3PriceAssertionProvider.t.sol} | 31 +++++++++++++++++ 8 files changed, 142 insertions(+), 1 deletion(-) rename test/V2/oracles/individual/{UmaV3DynamicAssertionProvider.sol => UmaV3DynamicAssertionProvider.t.sol} (92%) rename test/V2/oracles/individual/{UmaV3EventAssertionProvider.sol => UmaV3EventAssertionProvider.t.sol} (91%) rename test/V2/oracles/individual/{UmaV3PriceAssertionProvider.sol => UmaV3PriceAssertionProvider.t.sol} (91%) diff --git a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol index 5699e766..11cdb0d9 100644 --- a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol @@ -46,12 +46,14 @@ contract UmaV3DynamicAssertionProvider is Ownable { mapping(uint256 => uint256) public marketIdToConditionType; mapping(uint256 => MarketAnswer) public marketIdToAnswer; mapping(bytes32 => uint256) public assertionIdToMarket; + mapping(address => bool) public whitelistRelayer; event MarketAsserted(uint256 marketId, bytes32 assertionId); event AssertionResolved(bytes32 assertionId, bool assertion); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event BondUpdated(uint256 newBond); event AssertionDataUpdated(uint256 newData); + event RelayerUpdated(address relayer, bool state); /** @param _decimals is decimals for the provider maker if relevant @@ -137,6 +139,13 @@ contract UmaV3DynamicAssertionProvider is Ownable { return fetchAssertion(_marketId); } + function updateRelayer(address _relayer) external onlyOwner { + if (_relayer == address(0)) revert ZeroAddress(); + bool relayerState = whitelistRelayer[_relayer]; + whitelistRelayer[_relayer] = !relayerState; + emit RelayerUpdated(_relayer, relayerState); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -168,6 +177,8 @@ contract UmaV3DynamicAssertionProvider is Ownable { function fetchAssertion( uint256 _marketId ) public returns (bytes32 assertionId) { + if (whitelistRelayer[msg.sender] == false) revert InvalidCaller(); + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; if (marketAnswer.activeAssertion == true) revert AssertionActive(); if ((block.timestamp - assertionData.updatedAt) > timeOut) diff --git a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol index 1caa7286..aa9867fd 100644 --- a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol @@ -38,12 +38,14 @@ contract UmaV3EventAssertionProvider is Ownable { mapping(uint256 => uint256) public marketIdToConditionType; mapping(uint256 => MarketAnswer) public marketIdToAnswer; mapping(bytes32 => uint256) public assertionIdToMarket; + mapping(address => bool) public whitelistRelayer; event MarketAsserted(uint256 marketId, bytes32 assertionId); event AssertionResolved(bytes32 assertionId, bool assertion); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event CoverageStartUpdated(uint256 startTime); event BondUpdated(uint256 newBond); + event RelayerUpdated(address relayer, bool state); /** @param _decimals is decimals for the provider maker if relevant @@ -117,6 +119,13 @@ contract UmaV3EventAssertionProvider is Ownable { emit BondUpdated(newBond); } + function updateRelayer(address _relayer) external onlyOwner { + if (_relayer == address(0)) revert ZeroAddress(); + bool relayerState = whitelistRelayer[_relayer]; + whitelistRelayer[_relayer] = !relayerState; + emit RelayerUpdated(_relayer, relayerState); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -148,6 +157,8 @@ contract UmaV3EventAssertionProvider is Ownable { function fetchAssertion( uint256 _marketId ) external returns (bytes32 assertionId) { + if (whitelistRelayer[msg.sender] == false) revert InvalidCaller(); + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; if (marketAnswer.activeAssertion == true) revert AssertionActive(); diff --git a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol index a7531a3a..c4a5e461 100644 --- a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol @@ -38,11 +38,13 @@ contract UmaV3PriceAssertionProvider is Ownable { mapping(uint256 => uint256) public marketIdToConditionType; mapping(uint256 => MarketAnswer) public marketIdToAnswer; mapping(bytes32 => uint256) public assertionIdToMarket; + mapping(address => bool) public whitelistRelayer; event MarketAsserted(uint256 marketId, bytes32 assertionId); event AssertionResolved(bytes32 assertionId, bool assertion); event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event BondUpdated(uint256 newBond); + event RelayerUpdated(address relayer, bool state); /** @param _decimals is decimals for the provider maker if relevant @@ -109,6 +111,13 @@ contract UmaV3PriceAssertionProvider is Ownable { emit BondUpdated(newBond); } + function updateRelayer(address _relayer) external onlyOwner { + if (_relayer == address(0)) revert ZeroAddress(); + bool relayerState = whitelistRelayer[_relayer]; + whitelistRelayer[_relayer] = !relayerState; + emit RelayerUpdated(_relayer, relayerState); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -139,6 +148,8 @@ contract UmaV3PriceAssertionProvider is Ownable { function fetchAssertion( uint256 _marketId ) external returns (bytes32 assertionId) { + if (whitelistRelayer[msg.sender] == false) revert InvalidCaller(); + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; if (marketAnswer.activeAssertion == true) revert AssertionActive(); diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index 345891aa..abb5cf95 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -15,6 +15,7 @@ contract Helper is Test { event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event CoverageStartUpdated(uint256 startTime); event AssertionDataUpdated(uint256 newData); + event RelayerUpdated(address relayer, bool state); uint256 public constant STRIKE = 1000000000000000000; uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index 73e54e11..86bed9a9 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -15,6 +15,7 @@ import { MockUmaV2, MockUmaFinder } from "../MockOracles.sol"; +import "forge-std/console.sol"; // Specification for YER_NO_QUERY on Uma: https://github.com/UMAprotocol/UMIPs/blob/master/UMIPs/umip-107.md // Uma address all networks: https://docs.uma.xyz/resources/network-addresses @@ -74,6 +75,18 @@ contract UmaV2AssertionProviderTest is Helper { assertEq(umaV2AssertionProvider.ancillaryData(), ancillaryData); assertEq(umaV2AssertionProvider.reward(), reward); assertEq(umaV2AssertionProvider.coverageStart(), block.timestamp); + + string + memory ancillaryDataHead = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: string"; + uint256 coverageStart = 1697498162; + string + memory ancillaryDataTail = ". P1: 0 for NO, P2: 1 for YES, P3: 2 for UNDETERMINED"; + bytes memory output = abi.encodePacked( + ancillaryDataHead, + coverageStart, + ancillaryDataTail + ); + console.logBytes(output); } //////////////////////////////////////////////// diff --git a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.sol b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol similarity index 92% rename from test/V2/oracles/individual/UmaV3DynamicAssertionProvider.sol rename to test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol index 254ac50d..89a56dd1 100644 --- a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.sol +++ b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol @@ -57,6 +57,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { uint256 condition = 2; umaPriceProvider.setConditionType(marketId, condition); + umaPriceProvider.updateRelayer(address(this)); } //////////////////////////////////////////////// @@ -78,6 +79,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { assertEq(assertionData, 0); assertEq(updatedAt, block.timestamp); assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); + assertEq(umaPriceProvider.whitelistRelayer(address(this)), true); } function testUpdateRequiredBond() public { @@ -116,6 +118,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 1); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -147,6 +150,18 @@ contract UmaV3DynamicAssertionProviderTest is Helper { assertEq(assertionIdReturned, _assertionId); } + function testUpdateRelayer() public { + address newRelayer = address(0x123); + + vm.expectEmit(true, true, false, false); + emit RelayerUpdated(newRelayer, true); + umaPriceProvider.updateRelayer(newRelayer); + assertEq(umaPriceProvider.whitelistRelayer(newRelayer), true); + + umaPriceProvider.updateRelayer(newRelayer); + assertEq(umaPriceProvider.whitelistRelayer(newRelayer), false); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// @@ -165,6 +180,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 1); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -238,6 +254,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -279,6 +296,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -316,6 +334,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -459,6 +478,11 @@ contract UmaV3DynamicAssertionProviderTest is Helper { umaPriceProvider.updateRequiredBond(0); } + function testRevertZeroAddressUpdateRelayer() public { + vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); + umaPriceProvider.updateRelayer(address(0)); + } + function testRevertInvalidInputAssertionData() public { vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); umaPriceProvider.updateAssertionData(0); @@ -496,6 +520,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 1); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -506,6 +531,13 @@ contract UmaV3DynamicAssertionProviderTest is Helper { umaPriceProvider.fetchAssertion(marketId); } + function testRevertFetchAssertionInvalidCaller() public { + vm.startPrank(address(0x123)); + vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidCaller.selector); + umaPriceProvider.fetchAssertion(marketId); + vm.stopPrank(); + } + function testRevertAssertionDataEmpty() public { vm.warp(block.timestamp + 2 days); vm.expectRevert( diff --git a/test/V2/oracles/individual/UmaV3EventAssertionProvider.sol b/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol similarity index 91% rename from test/V2/oracles/individual/UmaV3EventAssertionProvider.sol rename to test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol index 666640a8..b8c7832f 100644 --- a/test/V2/oracles/individual/UmaV3EventAssertionProvider.sol +++ b/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol @@ -55,6 +55,7 @@ contract UmaV3EventAssertionProviderTest is Helper { uint256 condition = 2; umaPriceProvider.setConditionType(marketId, condition); + umaPriceProvider.updateRelayer(address(this)); } //////////////////////////////////////////////// @@ -73,6 +74,7 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(umaPriceProvider.assertionDescription(), assertionDescription); assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); + assertEq(umaPriceProvider.whitelistRelayer(address(this)), true); } function testUpdateCoverageTime() public { @@ -89,6 +91,18 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(umaPriceProvider.requiredBond(), newBond); } + function testUpdateRelayer() public { + address newRelayer = address(0x123); + + vm.expectEmit(true, true, false, false); + emit RelayerUpdated(newRelayer, true); + umaPriceProvider.updateRelayer(newRelayer); + assertEq(umaPriceProvider.whitelistRelayer(newRelayer), true); + + umaPriceProvider.updateRelayer(newRelayer); + assertEq(umaPriceProvider.whitelistRelayer(newRelayer), false); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// @@ -107,6 +121,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 1); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -175,6 +190,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -209,6 +225,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -239,6 +256,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -373,11 +391,16 @@ contract UmaV3EventAssertionProviderTest is Helper { umaPriceProvider.updateCoverageStart(0); } - function testRevertInvalidInpudRequiredBond() public { + function testRevertInvalidInputRequiredBond() public { vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); umaPriceProvider.updateRequiredBond(0); } + function testRevertZeroAddressUpdateRelayer() public { + vm.expectRevert(UmaV3EventAssertionProvider.ZeroAddress.selector); + umaPriceProvider.updateRelayer(address(0)); + } + function testRevertInvalidCallerCallback() public { vm.expectRevert(UmaV3EventAssertionProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); @@ -393,6 +416,13 @@ contract UmaV3EventAssertionProviderTest is Helper { ); } + function testRevertFetchAssertionZeroInvalidCaller() public { + vm.startPrank(address(0x123)); + vm.expectRevert(UmaV3EventAssertionProvider.InvalidCaller.selector); + umaPriceProvider.fetchAssertion(marketId); + vm.stopPrank(); + } + function testRevertAssertionActive() public { MockUma mockUma = new MockUma(); @@ -408,6 +438,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 1); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); diff --git a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.sol b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol similarity index 91% rename from test/V2/oracles/individual/UmaV3PriceAssertionProvider.sol rename to test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol index 174002b2..553d85a0 100644 --- a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.sol +++ b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol @@ -57,6 +57,7 @@ contract UmaV3EventAssertionProviderTest is Helper { uint256 condition = 2; umaPriceProvider.setConditionType(marketId, condition); + umaPriceProvider.updateRelayer(address(this)); } //////////////////////////////////////////////// @@ -75,6 +76,7 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(umaPriceProvider.assertionDescription(), assertionDescription); assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); + assertEq(umaPriceProvider.whitelistRelayer(address(this)), true); } function testUpdateRequiredBond() public { @@ -86,6 +88,18 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(umaPriceProvider.requiredBond(), newBond); } + function testUpdateRelayer() public { + address newRelayer = address(0x123); + + vm.expectEmit(true, true, false, false); + emit RelayerUpdated(newRelayer, true); + umaPriceProvider.updateRelayer(newRelayer); + assertEq(umaPriceProvider.whitelistRelayer(newRelayer), true); + + umaPriceProvider.updateRelayer(newRelayer); + assertEq(umaPriceProvider.whitelistRelayer(newRelayer), false); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// @@ -104,6 +118,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 1); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -172,6 +187,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -206,6 +222,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -236,6 +253,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); @@ -370,6 +388,11 @@ contract UmaV3EventAssertionProviderTest is Helper { umaPriceProvider.updateRequiredBond(0); } + function testRevertInvalidInputUpdateRelayer() public { + vm.expectRevert(UmaV3PriceAssertionProvider.ZeroAddress.selector); + umaPriceProvider.updateRelayer(address(0)); + } + function testRevertInvalidCallerCallback() public { vm.expectRevert(UmaV3PriceAssertionProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); @@ -385,6 +408,13 @@ contract UmaV3EventAssertionProviderTest is Helper { ); } + function testRevertAssertionInvalidCaller() public { + vm.startPrank(address(0x123)); + vm.expectRevert(UmaV3PriceAssertionProvider.InvalidCaller.selector); + umaPriceProvider.fetchAssertion(marketId); + vm.stopPrank(); + } + function testRevertAssertionActive() public { MockUma mockUma = new MockUma(); @@ -400,6 +430,7 @@ contract UmaV3EventAssertionProviderTest is Helper { REQUIRED_BOND ); umaPriceProvider.setConditionType(marketId, 1); + umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); From 605b53526f396fb2670b5cb3e131e200fe1d0fdd Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Tue, 24 Oct 2023 18:58:10 -0600 Subject: [PATCH 19/25] fix: RedstoneUniversal tests --- getRedstonePayload.js | 48 -------- src/v2/interfaces/IConditionProvider.sol | 6 + src/v2/oracles/CVIPriceProvider.sol | 86 -------------- src/v2/oracles/DIAPriceProvider.sol | 85 -------------- .../oracles/individual/PythPriceProvider.sol | 2 +- .../RedstoneCoreUniversalProvider.sol | 17 ++- .../Controllers/ControllerGenericTest.t.sol | 2 +- test/V2/e2e/EndToEndGenericTest.t.sol | 35 ------ test/V2/oracles/CVIPriceProvider.t.sol | 106 ------------------ test/V2/oracles/getRedstonePayload.js | 57 ++++++++++ .../oracles/individual/CVIPriceProvider.t.sol | 2 +- .../individual/ChainlinkPriceProvider.t.sol | 2 +- .../oracles/individual/DIAPriceProvider.t.sol | 2 +- .../individual/GdaiPriceProvider.t.sol | 2 +- .../individual/PythPriceProvider.t.sol | 36 ++---- .../individual/RedstonePriceProvider.t.sol | 2 +- .../individual/UmaV2AssertionProvider.t.sol | 2 +- .../individual/UmaV2PriceProvider.t.sol | 2 +- .../UmaV3DynamicAssertionProvider.t.sol | 4 +- .../UmaV3EventAssertionProvider.t.sol | 4 +- .../UmaV3PriceAssertionProvider.t.sol | 4 +- test/V2/oracles/{ => mocks}/MockOracles.sol | 0 .../MockRedstoneCoreUniversalProvider.sol | 33 ++++++ test/V2/oracles/{ => mocks}/MockUma.sol | 0 .../ChainlinkUniversalProvider.t.sol | 2 +- .../universal/DIAUniversalProvider.t.sol | 2 +- .../RedstoneCoreUniversalProvider.t.sol | 74 +++++------- .../universal/RedstoneUniversalProvider.t.sol | 2 +- 28 files changed, 170 insertions(+), 449 deletions(-) delete mode 100644 getRedstonePayload.js delete mode 100644 src/v2/oracles/CVIPriceProvider.sol delete mode 100644 src/v2/oracles/DIAPriceProvider.sol delete mode 100644 test/V2/oracles/CVIPriceProvider.t.sol create mode 100644 test/V2/oracles/getRedstonePayload.js rename test/V2/oracles/{ => mocks}/MockOracles.sol (100%) create mode 100644 test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol rename test/V2/oracles/{ => mocks}/MockUma.sol (100%) diff --git a/getRedstonePayload.js b/getRedstonePayload.js deleted file mode 100644 index a538cf9d..00000000 --- a/getRedstonePayload.js +++ /dev/null @@ -1,48 +0,0 @@ -const { appendFileSync } = require('fs'); -const { DataPackage, NumericDataPoint, RedstonePayload } = require('./lib/redstone-oracles-monorepo/packages/protocol/dist/src/index'); - -const args = process.argv.slice(2); - -const exit = (code, message) => { - process.stderr.write(message); - appendFileSync("./getRedstonePayload.log.txt", message); - process.exit(code); -} - -if (args.length === 0) { - exit(1, "You have to provide at least on dataFeed"); -} - -const dataFeeds = args[0].split(','); - -if (dataFeeds.length === 0) { - exit(2, "You have to provide at least on dataFeed"); -} - -const timestampMilliseconds = Date.now(); - -const PRIVATE_KEY_1 = '0x548e7c2fae09cc353ffe54ed40609d88a99fab24acfc81bfbf5cd9c11741643d'; - -const dataPoints = dataFeeds.map(arg => { - const [dataFeedId, value, decimals] = arg.split(':'); - - if (!dataFeedId || !value || !decimals) { - exit(3, "Input should have format: dataFeedId:value:decimals (example: BTC:120:8)"); - } - - return new NumericDataPoint({ dataFeedId, value: parseInt(value), decimals: parseInt(decimals) }) -}); - - -// Prepare unsigned data package -const dataPackage = new DataPackage(dataPoints, timestampMilliseconds); - -// Prepare signed data packages -const signedDataPackages = [ - dataPackage.sign(PRIVATE_KEY_1), -]; - -const payload = RedstonePayload.prepare(signedDataPackages, ""); - -process.stdout.write("0x" + payload) -process.exit(0); \ No newline at end of file diff --git a/src/v2/interfaces/IConditionProvider.sol b/src/v2/interfaces/IConditionProvider.sol index 491b6987..13b31155 100644 --- a/src/v2/interfaces/IConditionProvider.sol +++ b/src/v2/interfaces/IConditionProvider.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.17; interface IConditionProvider { + function setConditionType(uint256 _marketId, uint256 _condition) external; + function getLatestPrice() external view returns (int256); function conditionMet( @@ -13,4 +15,8 @@ interface IConditionProvider { external view returns (uint80, int256, uint256, uint256, uint80); + + function marketIdToConditionType( + uint256 _marketId + ) external view returns (uint256); } diff --git a/src/v2/oracles/CVIPriceProvider.sol b/src/v2/oracles/CVIPriceProvider.sol deleted file mode 100644 index 72f50930..00000000 --- a/src/v2/oracles/CVIPriceProvider.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {IVaultFactoryV2} from "../interfaces/IVaultFactoryV2.sol"; -import {IConditionProvider} from "../interfaces/IConditionProvider.sol"; -import {ICVIPriceFeed} from "../interfaces/ICVIPriceFeed.sol"; - -contract CVIPriceProvider is IConditionProvider { - uint256 public immutable timeOut; - ICVIPriceFeed public priceFeedAdapter; - uint256 public immutable decimals; - string public description; - - constructor(address _priceFeed, uint256 _timeOut, uint256 _decimals) { - if (_priceFeed == address(0)) revert ZeroAddress(); - if (_timeOut == 0) revert InvalidInput(); - priceFeedAdapter = ICVIPriceFeed(_priceFeed); - timeOut = _timeOut; - decimals = _decimals; - description = "CVI"; - } - - function latestRoundData() - public - view - returns ( - uint80 roundId, - int256 price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) - { - uint32 cviValue; - (cviValue, roundId, updatedAt) = priceFeedAdapter - .getCVILatestRoundData(); - price = int32(cviValue); - startedAt = 1; - answeredInRound = roundId; - } - - /** @notice Fetch token price from priceFeedAdapter (Redston oracle address) - * @return price Current token price - */ - function getLatestPrice() public view virtual returns (int256 price) { - (uint256 uintPrice, , uint256 updatedAt) = priceFeedAdapter - .getCVILatestRoundData(); - price = int256(uintPrice); - if (price == 0) revert OraclePriceZero(); - - // TODO: What is a suitable timeframe to set timeout as based on this info? Update at always timestamp? - if ((block.timestamp - updatedAt) > timeOut) revert PriceTimedOut(); - - if (decimals < 18) { - uint256 calcDecimals = 10 ** (18 - (decimals)); - price = price * int256(calcDecimals); - } else if (decimals > 18) { - uint256 calcDecimals = 10 ** ((decimals - 18)); - price = price / int256(calcDecimals); - } - - return price; - } - - // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future - /** @notice Fetch price and return condition - * @param _strike Strike price - * @return boolean If condition is met i.e. strike > price - * @return price Current price for token - */ - function conditionMet( - uint256 _strike - ) public view virtual returns (bool, int256 price) { - price = getLatestPrice(); - return (int256(_strike) < price, price); - } - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - error ZeroAddress(); - error InvalidInput(); - error OraclePriceZero(); - error RoundIdOutdated(); - error PriceTimedOut(); -} diff --git a/src/v2/oracles/DIAPriceProvider.sol b/src/v2/oracles/DIAPriceProvider.sol deleted file mode 100644 index 27ca916e..00000000 --- a/src/v2/oracles/DIAPriceProvider.sol +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************* -NOTE: Development in progress by JG. Reached functional milestone; Live VST data is accessible. -***/ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {IConditionProvider} from "../interfaces/IConditionProvider.sol"; -import {IDIAPriceFeed} from "../interfaces/IDIAPriceFeed.sol"; - -contract DIAPriceProvider is IConditionProvider { - IDIAPriceFeed public diaPriceFeed; - uint256 public immutable decimals; - string public constant description = "BTC/USD"; - - constructor(address _priceFeed, uint256 _decimals) { - if (_priceFeed == address(0)) revert ZeroAddress(); - diaPriceFeed = IDIAPriceFeed(_priceFeed); - decimals = _decimals; - } - - function latestRoundData() - public - view - returns ( - uint80 roundId, - int256 price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) - { - (price, updatedAt) = _getLatestPrice(); - startedAt = 1; - roundId = 1; - answeredInRound = 1; - } - - /** @notice Fetch token price from priceFeedAdapter (Using string name) - * @return price Current token price - */ - function getLatestPrice() public view override returns (int256 price) { - (price, ) = _getLatestPrice(); - } - - /** @notice Fetch price and return condition - * @dev The strike is hashed as an int256 to enable comparison vs. price for earthquake - and conditional check vs. strike to ensure vaidity - * @param _strike Strike price - * @return condition boolean If condition is met i.e. strike > price - * @return price current price for token - */ - function conditionMet( - uint256 _strike - ) public view virtual returns (bool condition, int256 price) { - (price, ) = _getLatestPrice(); - return (_strike > uint256(price), price); - } - - /*////////////////////////////////////////////////////////////// - INTERNAL - //////////////////////////////////////////////////////////////*/ - - function _getLatestPrice() - private - view - returns (int256 price, uint256 timestamp) - { - uint256 uintPrice; - (uintPrice, timestamp) = diaPriceFeed.getValue(description); - price = int256(uintPrice); - - if (decimals < 18) { - uint256 calcDecimals = 10 ** (18 - (decimals)); - price = price * int256(calcDecimals); - } else if (decimals > 18) { - uint256 calcDecimals = 10 ** ((decimals - 18)); - price = price / int256(calcDecimals); - } - } - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - error ZeroAddress(); -} diff --git a/src/v2/oracles/individual/PythPriceProvider.sol b/src/v2/oracles/individual/PythPriceProvider.sol index 1c335502..ed7fd553 100644 --- a/src/v2/oracles/individual/PythPriceProvider.sol +++ b/src/v2/oracles/individual/PythPriceProvider.sol @@ -55,7 +55,7 @@ contract PythPriceProvider is Ownable, IConditionProvider { function getLatestPrice() public view virtual returns (int256) { PythStructs.Price memory answer = pyth.getPriceNoOlderThan( priceFeedId, - timeOut + block.timestamp - timeOut ); if (answer.price <= 0) revert OraclePriceNegative(); diff --git a/src/v2/oracles/universal/RedstoneCoreUniversalProvider.sol b/src/v2/oracles/universal/RedstoneCoreUniversalProvider.sol index 7a8c267a..6d53c6b9 100644 --- a/src/v2/oracles/universal/RedstoneCoreUniversalProvider.sol +++ b/src/v2/oracles/universal/RedstoneCoreUniversalProvider.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.17; import {IUniversalProvider} from "../../interfaces/IUniversalProvider.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import "@redstone-finance/evm-connector/contracts/data-services/PrimaryProdDataServiceConsumerBase.sol"; +import {console} from "forge-std/console.sol"; contract RedstoneCoreUniversalProvider is Ownable, @@ -58,13 +59,18 @@ contract RedstoneCoreUniversalProvider is } function updatePrices(uint256[] memory _marketIds) external { + console.log("market id length", _marketIds.length); uint256[] memory prices = extractPrice(_marketIds); + console.log("prices length", prices.length); uint256 length = _marketIds.length; - for (uint256 i = 0; i < length; i += 1) { + for (uint256 i; i < length; ) { marketIdToPrice[_marketIds[i]] = prices[i]; marketIdToUpdatedAt[_marketIds[i]] = extractTimestampsAndAssertAllAreEqual() / 1000; + unchecked { + i++; + } } } @@ -104,8 +110,11 @@ contract RedstoneCoreUniversalProvider is ) public view returns (bytes32[] memory dataFeeds) { uint256 length = _marketIds.length; dataFeeds = new bytes32[](length); - for (uint256 i = 0; i < length; i += 1) { + for (uint256 i; i < length; ) { dataFeeds[i] = marketIdToDataFeed[_marketIds[i]]; + unchecked { + i++; + } } } @@ -113,6 +122,10 @@ contract RedstoneCoreUniversalProvider is uint256[] memory _marketIds ) public view returns (uint256[] memory price) { bytes32[] memory dataFeeds = getDataFeeds(_marketIds); + // TODO: Remove + for (uint i; i < dataFeeds.length; i += 1) { + console.logBytes32(dataFeeds[i]); + } return getOracleNumericValuesFromTxMsg(dataFeeds); } diff --git a/test/V2/Controllers/ControllerGenericTest.t.sol b/test/V2/Controllers/ControllerGenericTest.t.sol index ae5f5835..73f26a40 100644 --- a/test/V2/Controllers/ControllerGenericTest.t.sol +++ b/test/V2/Controllers/ControllerGenericTest.t.sol @@ -14,7 +14,7 @@ import {TimeLock} from "../../../src/v2/TimeLock.sol"; import { MockOracleConditionMet, MockOracleConditionNotMet -} from "../oracles/MockOracles.sol"; +} from "../oracles/mocks/MockOracles.sol"; contract ControllerGenericTest is Helper { VaultFactoryV2 public factory; diff --git a/test/V2/e2e/EndToEndGenericTest.t.sol b/test/V2/e2e/EndToEndGenericTest.t.sol index fc45db03..4523ddc7 100644 --- a/test/V2/e2e/EndToEndGenericTest.t.sol +++ b/test/V2/e2e/EndToEndGenericTest.t.sol @@ -302,41 +302,6 @@ contract EndToEndV2GenericTest is Helper { MintableToken(UNDERLYING).mint(USER); } - function _setupCVI() internal { - vm.selectFork(arbForkId); - UNDERLYING = address(new MintableToken("CVI Volatility", "CVI")); - address timelock = address(new TimeLock(ADMIN)); - factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - controller = new ControllerGeneric(address(factory), TREASURY); - factory.whitelistController(address(controller)); - - cviPriceProvider = new CVIPriceProvider( - CVI_ORACLE, - TIME_OUT, - CVI_DECIMALS - ); - int256 cviStrike = cviPriceProvider.getLatestPrice() - 1; - - depegStrike = uint256(cviStrike); - string memory name = string("CVI Volatility"); - string memory symbol = string("CVI"); - (depegPremium, depegCollateral, depegMarketId) = factory - .createNewMarket( - VaultFactoryV2.MarketConfigurationCalldata( - UNDERLYING, - depegStrike, - address(cviPriceProvider), - UNDERLYING, - name, - symbol, - address(controller) - ) - ); - - (depegEpochId, ) = factory.createEpoch(depegMarketId, begin, end, fee); - MintableToken(UNDERLYING).mint(USER); - } - function helperCalculateFeeAdjustedValue( uint256 _amount, uint16 _fee diff --git a/test/V2/oracles/CVIPriceProvider.t.sol b/test/V2/oracles/CVIPriceProvider.t.sol deleted file mode 100644 index 4527193b..00000000 --- a/test/V2/oracles/CVIPriceProvider.t.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {Helper} from "../Helper.sol"; -import {VaultFactoryV2} from "../../../src/v2/VaultFactoryV2.sol"; -import {CVIPriceProvider} from "../../../src/v2/oracles/CVIPriceProvider.sol"; -import {TimeLock} from "../../../src/v2/TimeLock.sol"; -import {MockOracleAnswerZeroCVI, MockOracleTimeOutCVI} from "./MockOracles.sol"; - -contract CVIPriceProviderTest is Helper { - uint256 public arbForkId; - VaultFactoryV2 public factory; - CVIPriceProvider public cviPriceProvider; - - //////////////////////////////////////////////// - // HELPERS // - //////////////////////////////////////////////// - - function setUp() public { - arbForkId = vm.createFork(ARBITRUM_RPC_URL); - vm.selectFork(arbForkId); - - address timelock = address(new TimeLock(ADMIN)); - factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - cviPriceProvider = new CVIPriceProvider( - CVI_ORACLE, - TIME_OUT, - CVI_DECIMALS - ); - } - - //////////////////////////////////////////////// - // STATE // - //////////////////////////////////////////////// - function testCVICreation() public { - assertEq(cviPriceProvider.timeOut(), TIME_OUT); - assertEq(address(cviPriceProvider.priceFeedAdapter()), CVI_ORACLE); - assertEq(cviPriceProvider.decimals(), CVI_DECIMALS); - assertEq(cviPriceProvider.description(), "CVI"); - } - - //////////////////////////////////////////////// - // FUNCTIONS // - //////////////////////////////////////////////// - function testLatestRoundDataCVI() public { - ( - uint80 roundId, - int256 price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) = cviPriceProvider.latestRoundData(); - assertTrue(price != 0); - assertTrue(roundId != 0); - assertEq(startedAt, 1); - assertTrue(updatedAt != 0); - assertTrue(answeredInRound != 0); - } - - function testLatestPriceCVI() public { - int256 price = cviPriceProvider.getLatestPrice(); - assertTrue(price != 0); - } - - function testConditionMetCVI() public { - (bool condition, int256 price) = cviPriceProvider.conditionMet(100); - assertTrue(price != 0); - assertEq(condition, true); - } - - //////////////////////////////////////////////// - // REVERT CASES // - //////////////////////////////////////////////// - - function testRevertConstructorInputs() public { - vm.expectRevert(CVIPriceProvider.ZeroAddress.selector); - new CVIPriceProvider(address(0), TIME_OUT, CVI_DECIMALS); - - vm.expectRevert(CVIPriceProvider.InvalidInput.selector); - new CVIPriceProvider(CVI_ORACLE, 0, CVI_DECIMALS); - } - - function testRevertOraclePriceZeroCVI() public { - address mockOracle = address(new MockOracleAnswerZeroCVI()); - cviPriceProvider = new CVIPriceProvider( - mockOracle, - TIME_OUT, - CVI_DECIMALS - ); - vm.expectRevert(CVIPriceProvider.OraclePriceZero.selector); - cviPriceProvider.getLatestPrice(); - } - - function testRevertTimeOut() public { - address mockOracle = address( - new MockOracleTimeOutCVI(block.timestamp, TIME_OUT) - ); - cviPriceProvider = new CVIPriceProvider( - mockOracle, - TIME_OUT, - CVI_DECIMALS - ); - vm.expectRevert(CVIPriceProvider.PriceTimedOut.selector); - cviPriceProvider.getLatestPrice(); - } -} diff --git a/test/V2/oracles/getRedstonePayload.js b/test/V2/oracles/getRedstonePayload.js new file mode 100644 index 00000000..b1dd4e0b --- /dev/null +++ b/test/V2/oracles/getRedstonePayload.js @@ -0,0 +1,57 @@ +const { appendFileSync } = require("fs"); +const { + DataPackage, + NumericDataPoint, + RedstonePayload, +} = require("../../../lib/redstone-oracles-monorepo/packages/protocol/dist/src/index"); + +const args = process.argv.slice(2); + +const exit = (code, message) => { + process.stderr.write(message); + appendFileSync("./getRedstonePayload.log.txt", message); + process.exit(code); +}; + +if (args.length === 0) { + exit(1, "You have to provide at least on dataFeed"); +} + +const dataFeeds = args[0].split(","); + +if (dataFeeds.length === 0) { + exit(2, "You have to provide at least on dataFeed"); +} + +const timestampMilliseconds = Date.now(); + +const PRIVATE_KEY_1 = + "0x548e7c2fae09cc353ffe54ed40609d88a99fab24acfc81bfbf5cd9c11741643d"; + +const dataPoints = dataFeeds.map((arg) => { + const [dataFeedId, value, decimals] = arg.split(":"); + + if (!dataFeedId || !value || !decimals) { + exit( + 3, + "Input should have format: dataFeedId:value:decimals (example: BTC:120:8)" + ); + } + + return new NumericDataPoint({ + dataFeedId, + value: parseInt(value), + decimals: parseInt(decimals), + }); +}); + +// Prepare unsigned data package +const dataPackage = new DataPackage(dataPoints, timestampMilliseconds); + +// Prepare signed data packages +const signedDataPackages = [dataPackage.sign(PRIVATE_KEY_1)]; + +const payload = RedstonePayload.prepare(signedDataPackages, ""); + +process.stdout.write("0x" + payload); +process.exit(0); diff --git a/test/V2/oracles/individual/CVIPriceProvider.t.sol b/test/V2/oracles/individual/CVIPriceProvider.t.sol index 25f62033..bb1aa2b1 100644 --- a/test/V2/oracles/individual/CVIPriceProvider.t.sol +++ b/test/V2/oracles/individual/CVIPriceProvider.t.sol @@ -10,7 +10,7 @@ import {TimeLock} from "../../../../src/v2/TimeLock.sol"; import { MockOracleAnswerZeroCVI, MockOracleTimeOutCVI -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; contract CVIPriceProviderTest is Helper { uint256 public arbForkId; diff --git a/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol b/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol index 3c4a4079..093457d7 100644 --- a/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol +++ b/test/V2/oracles/individual/ChainlinkPriceProvider.t.sol @@ -13,7 +13,7 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; import {IChainlink} from "../PriceInterfaces.sol"; contract ChainlinkPriceProviderTest is Helper { diff --git a/test/V2/oracles/individual/DIAPriceProvider.t.sol b/test/V2/oracles/individual/DIAPriceProvider.t.sol index 4bda830f..fa7af7f0 100644 --- a/test/V2/oracles/individual/DIAPriceProvider.t.sol +++ b/test/V2/oracles/individual/DIAPriceProvider.t.sol @@ -13,7 +13,7 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; contract DIAPriceProviderTest is Helper { DIAPriceProvider public diaPriceProvider; diff --git a/test/V2/oracles/individual/GdaiPriceProvider.t.sol b/test/V2/oracles/individual/GdaiPriceProvider.t.sol index dd61e7ec..d46b8f47 100644 --- a/test/V2/oracles/individual/GdaiPriceProvider.t.sol +++ b/test/V2/oracles/individual/GdaiPriceProvider.t.sol @@ -13,7 +13,7 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; import {IPriceFeedAdapter} from "../PriceInterfaces.sol"; contract GdaiPriceProviderTest is Helper { diff --git a/test/V2/oracles/individual/PythPriceProvider.t.sol b/test/V2/oracles/individual/PythPriceProvider.t.sol index 3c19fc7f..9b1d62dd 100644 --- a/test/V2/oracles/individual/PythPriceProvider.t.sol +++ b/test/V2/oracles/individual/PythPriceProvider.t.sol @@ -9,10 +9,8 @@ import {TimeLock} from "../../../../src/v2/TimeLock.sol"; import { MockOracleAnswerNegativePyth, MockOracleExponentTooSmallPyth -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; import {IPriceFeedAdapter} from "../PriceInterfaces.sol"; -import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; -import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; contract PythPriceProviderTest is Helper { uint256 public arbForkId; @@ -26,6 +24,7 @@ contract PythPriceProviderTest is Helper { function setUp() public { arbForkId = vm.createFork(ARBITRUM_RPC_URL); vm.selectFork(arbForkId); + vm.warp(1698034887); pythProvider = new PythPriceProvider( PYTH_CONTRACT, @@ -38,23 +37,24 @@ contract PythPriceProviderTest is Helper { // STATE // //////////////////////////////////////////////// - function testCreation() public { + function testPythCreation() public { assertEq(pythProvider.timeOut(), TIME_OUT); assertEq(pythProvider.priceFeedId(), PYTH_FDUSD_FEED_ID); assertEq(address(pythProvider.pyth()), PYTH_CONTRACT); - - PythStructs.Price memory answer = IPyth(PYTH_CONTRACT).getPriceUnsafe( - PYTH_FDUSD_FEED_ID - ); - assertEq(pythProvider.decimals(), uint256(int256(-answer.expo))); + assertEq(pythProvider.decimals(), 8); } //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// function testLatestRoundDataPyth() public { - (, int256 price, , uint256 updatedAt, ) = pythProvider - .latestRoundData(); + ( + uint80 roundId, + int256 price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = pythProvider.latestRoundData(); assertTrue(price != 0); assertTrue(updatedAt != 0); } @@ -64,15 +64,6 @@ contract PythPriceProviderTest is Helper { assertTrue(price != 0); } - function testConditionMet() public { - (bool condition, int256 price) = pythProvider.conditionMet( - 2 ether, - marketId - ); - assertTrue(price != 0); - assertEq(condition, true); - } - function testConditionOneMetPyth() public { uint256 marketIdOne = 1; uint256 strikePrice = 10000000000000001; @@ -128,13 +119,10 @@ contract PythPriceProviderTest is Helper { PYTH_FDUSD_FEED_ID, TIME_OUT ); - PythStructs.Price memory answer = IPyth(mockPyth).getPriceUnsafe( - PYTH_FDUSD_FEED_ID - ); vm.expectRevert( abi.encodeWithSelector( PythPriceProvider.ExponentTooSmall.selector, - answer.expo + int256(-19) ) ); pythProvider.getLatestPrice(); diff --git a/test/V2/oracles/individual/RedstonePriceProvider.t.sol b/test/V2/oracles/individual/RedstonePriceProvider.t.sol index b742e72e..dd70de08 100644 --- a/test/V2/oracles/individual/RedstonePriceProvider.t.sol +++ b/test/V2/oracles/individual/RedstonePriceProvider.t.sol @@ -11,7 +11,7 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; import {IPriceFeedAdapter} from "../PriceInterfaces.sol"; contract RedstonePriceProviderTest is Helper { diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index 86bed9a9..9a6ae6cc 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -14,7 +14,7 @@ import { MockOracleTimeOut, MockUmaV2, MockUmaFinder -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; import "forge-std/console.sol"; // Specification for YER_NO_QUERY on Uma: https://github.com/UMAprotocol/UMIPs/blob/master/UMIPs/umip-107.md diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index e35b00f5..998df221 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -14,7 +14,7 @@ import { MockOracleTimeOut, MockUmaV2, MockUmaFinder -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; // The configuration information for TOKEN_PRICE query: https://github.com/UMAprotocol/UMIPs/blob/master/UMIPs/umip-121.md // Price feeds to use in config: https://github.com/UMAprotocol/protocol/tree/master/packages/financial-templates-lib/src/price-feed diff --git a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol index 89a56dd1..aea89ee1 100644 --- a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol @@ -11,8 +11,8 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; -import {MockUma} from "../MockUma.sol"; +} from "../mocks/MockOracles.sol"; +import {MockUma} from "../mocks/MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; contract UmaV3DynamicAssertionProviderTest is Helper { diff --git a/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol index b8c7832f..b8662510 100644 --- a/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol @@ -11,8 +11,8 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; -import {MockUma} from "../MockUma.sol"; +} from "../mocks/MockOracles.sol"; +import {MockUma} from "../mocks/MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; contract UmaV3EventAssertionProviderTest is Helper { diff --git a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol index 553d85a0..b60ad623 100644 --- a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol @@ -11,8 +11,8 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; -import {MockUma} from "../MockUma.sol"; +} from "../mocks/MockOracles.sol"; +import {MockUma} from "../mocks/MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; contract UmaV3EventAssertionProviderTest is Helper { diff --git a/test/V2/oracles/MockOracles.sol b/test/V2/oracles/mocks/MockOracles.sol similarity index 100% rename from test/V2/oracles/MockOracles.sol rename to test/V2/oracles/mocks/MockOracles.sol diff --git a/test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol b/test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol new file mode 100644 index 00000000..4fcae9be --- /dev/null +++ b/test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { + RedstoneCoreUniversalProvider +} from "../../../../src/v2/oracles/universal/RedstoneCoreUniversalProvider.sol"; + +contract MockRedstoneCoreUniversalProvider is RedstoneCoreUniversalProvider { + constructor(uint256 _timeOut) RedstoneCoreUniversalProvider(_timeOut) {} + + function getUniqueSignersThreshold() + public + view + virtual + override + returns (uint8) + { + return 1; + } + + function getAuthorisedSignerIndex( + address signerAddress + ) public view virtual override returns (uint8) { + // authorize everyone + return 0; + } + + function validateTimestamp( + uint256 receivedTimestampMilliseconds + ) public view override { + // allow any timestamp + } +} diff --git a/test/V2/oracles/MockUma.sol b/test/V2/oracles/mocks/MockUma.sol similarity index 100% rename from test/V2/oracles/MockUma.sol rename to test/V2/oracles/mocks/MockUma.sol diff --git a/test/V2/oracles/universal/ChainlinkUniversalProvider.t.sol b/test/V2/oracles/universal/ChainlinkUniversalProvider.t.sol index 03da8333..9041a2aa 100644 --- a/test/V2/oracles/universal/ChainlinkUniversalProvider.t.sol +++ b/test/V2/oracles/universal/ChainlinkUniversalProvider.t.sol @@ -13,7 +13,7 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; import {IChainlinkUniversal} from "../PriceInterfaces.sol"; contract ChainlinkUniversalProviderTest is Helper { diff --git a/test/V2/oracles/universal/DIAUniversalProvider.t.sol b/test/V2/oracles/universal/DIAUniversalProvider.t.sol index 9476c20d..e49ff74b 100644 --- a/test/V2/oracles/universal/DIAUniversalProvider.t.sol +++ b/test/V2/oracles/universal/DIAUniversalProvider.t.sol @@ -13,7 +13,7 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; contract DIAUniversalProviderTest is Helper { DIAUniversalProvider public diaPriceProvider; diff --git a/test/V2/oracles/universal/RedstoneCoreUniversalProvider.t.sol b/test/V2/oracles/universal/RedstoneCoreUniversalProvider.t.sol index 1e1339c2..a5de8fd0 100644 --- a/test/V2/oracles/universal/RedstoneCoreUniversalProvider.t.sol +++ b/test/V2/oracles/universal/RedstoneCoreUniversalProvider.t.sol @@ -3,40 +3,13 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; import { + MockRedstoneCoreUniversalProvider, RedstoneCoreUniversalProvider -} from "../../../../src/v2/oracles/universal/RedstoneCoreUniversalProvider.sol"; +} from "../mocks/MockRedstoneCoreUniversalProvider.sol"; import "forge-std/Script.sol"; -contract MockRedstoneCoreUniversalProvider is RedstoneCoreUniversalProvider { - constructor(uint256 _timeOut) RedstoneCoreUniversalProvider(_timeOut) {} - - function getUniqueSignersThreshold() - public - view - virtual - override - returns (uint8) - { - return 1; - } - - function getAuthorisedSignerIndex( - address signerAddress - ) public view virtual override returns (uint8) { - // authorize everyone - return 0; - } - - function validateTimestamp( - uint256 receivedTimestampMilliseconds - ) public view override { - // allow any timestamp - } -} - -// cd ./lib/redstone-oracles-monorepo/packages/protocol && yarn && yarn build -// delete ./lib/redstone-oracles-monorepo/node_modules/@chainlink -// forge test --match-path test/V2/oracles/universal/RedstoneCoreUniversalProviderTest.t.sol -vvvv --ffi +// NOTE: If test failing run the following command to install packages correctly +// cd ./lib/redstone-oracles-monorepo/packages/protocol && yarn && yarn build && cd ../../../../ && rm -r ./lib/redstone-oracles-monorepo/node_modules/@chainlink contract RedstoneCoreUniversalProviderTest is Helper { uint256 public arbForkId; MockRedstoneCoreUniversalProvider public redstoneCoreProvider; @@ -71,20 +44,26 @@ contract RedstoneCoreUniversalProviderTest is Helper { function getRedstonePayload( // dataFeedId:value:decimals string memory priceFeed - ) public returns (bytes memory) { + ) public returns (bytes memory payload) { string[] memory args = new string[](3); args[0] = "node"; - args[1] = "getRedstonePayload.js"; + args[1] = "./test/V2/oracles/getRedstonePayload.js"; args[2] = priceFeed; - return vm.ffi(args); + payload = vm.ffi(args); + console.log("payload length", payload.length); + console.logBytes(payload); } function updatePrice( string memory payload, uint256[] memory _marketIds ) public { + // updatePrice("BTC:270:8,ETH:16:8", marketIds); bytes memory redstonePayload = getRedstonePayload(payload); + uint256[] memory newIds = new uint256[](2); + newIds[0] = btcMarketId; + newIds[1] = ethMarketId; bytes memory encodedFunction = abi.encodeWithSignature( "updatePrices(uint256[])", @@ -95,6 +74,11 @@ contract RedstoneCoreUniversalProviderTest is Helper { redstonePayload ); + console.log("length of market inputs", newIds.length); + console.logBytes(encodedFunction); + console.logBytes(redstonePayload); + console.logBytes(encodedFunctionWithRedstonePayload); + // Securely getting oracle value (bool success, ) = address(redstoneCoreProvider).call( encodedFunctionWithRedstonePayload @@ -119,8 +103,8 @@ contract RedstoneCoreUniversalProviderTest is Helper { //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// - function testLatestRoundDataRedUni() public { - updatePrice("BTC:270:8,ETH:16:8", marketIds); + function testLatestRoundDataRedCUni() public { + updatePrice("BTC:120:8,ETH:69:8", marketIds); ( uint80 roundId, int256 price, @@ -135,7 +119,7 @@ contract RedstoneCoreUniversalProviderTest is Helper { assertTrue(answeredInRound == 0); } - function testLatestPriceRedUni() public { + function testLatestPriceRedCUni() public { updatePrice("BTC:270:8,ETH:16:8", marketIds); (, int256 btcRoundPrice, , , ) = redstoneCoreProvider.latestRoundData( @@ -155,7 +139,7 @@ contract RedstoneCoreUniversalProviderTest is Helper { assertEq(ethPrice, 16 * 10 ** 18); } - function testConditionOneMetRedUni() public { + function testConditionOneMetRedCUni() public { uint256 conditionType = 1; uint256 marketIdOne = 1; redstoneCoreProvider.setConditionType(marketIdOne, conditionType); @@ -173,7 +157,7 @@ contract RedstoneCoreUniversalProviderTest is Helper { assertEq(condition, true); } - function testConditionTwoMetRedUni() public { + function testConditionTwoMetRedCUni() public { updatePrice("BTC:270:8,ETH:16:8", marketIds); (bool condition, int256 price) = redstoneCoreProvider.conditionMet( 20 ether, @@ -187,19 +171,19 @@ contract RedstoneCoreUniversalProviderTest is Helper { // REVERT CASES // //////////////////////////////////////////////// - function testRevertConstructorInputsRedUni() public { + function testRevertConstructorInputsRedCUni() public { vm.expectRevert(RedstoneCoreUniversalProvider.InvalidInput.selector); new MockRedstoneCoreUniversalProvider(0); } - function testRevertConditionTypeSetRedUni() public { + function testRevertConditionTypeSetRedCUni() public { vm.expectRevert( RedstoneCoreUniversalProvider.ConditionTypeSet.selector ); redstoneCoreProvider.setConditionType(2, 0); } - function testRevertInvalidInputConditionRedUni() public { + function testRevertInvalidInputConditionRedCUni() public { vm.expectRevert(RedstoneCoreUniversalProvider.InvalidInput.selector); redstoneCoreProvider.setConditionType(0, 0); @@ -207,12 +191,12 @@ contract RedstoneCoreUniversalProviderTest is Helper { redstoneCoreProvider.setConditionType(0, 3); } - function testRevertInvalidInputFeedRedUni() public { + function testRevertInvalidInputFeedRedCUni() public { vm.expectRevert(RedstoneCoreUniversalProvider.InvalidInput.selector); redstoneCoreProvider.setPriceFeed(0, bytes32(0), 8); } - function testRevertOraclePriceZeroRedUni() public { + function testRevertOraclePriceZeroRedCUni() public { redstoneCoreProvider.setConditionType(marketIdMock, 1); redstoneCoreProvider.setPriceFeed(marketIdMock, ethFeed, 8); @@ -220,7 +204,7 @@ contract RedstoneCoreUniversalProviderTest is Helper { redstoneCoreProvider.getLatestPrice(marketIdMock); } - function testRevertTimeOutRedUni() public { + function testRevertTimeOutRedCUni() public { redstoneCoreProvider.setConditionType(marketIdMock, 1); redstoneCoreProvider.setPriceFeed(marketIdMock, ethFeed, 8); diff --git a/test/V2/oracles/universal/RedstoneUniversalProvider.t.sol b/test/V2/oracles/universal/RedstoneUniversalProvider.t.sol index 020b7242..a7164310 100644 --- a/test/V2/oracles/universal/RedstoneUniversalProvider.t.sol +++ b/test/V2/oracles/universal/RedstoneUniversalProvider.t.sol @@ -11,7 +11,7 @@ import { MockOracleAnswerZero, MockOracleRoundOutdated, MockOracleTimeOut -} from "../MockOracles.sol"; +} from "../mocks/MockOracles.sol"; import {IPriceFeedAdapter} from "../PriceInterfaces.sol"; contract RedstoneUniversalProviderTest is Helper { From c17b4d5bbaa8effa48d42fd2d5ced1129409a345 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Wed, 25 Oct 2023 10:53:48 -0600 Subject: [PATCH 20/25] feat: pyth deploy script --- script/v2/V2DeployContracts.s.sol | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index a6ffa21c..f4835123 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -17,6 +17,7 @@ import "../../src/v2/oracles/individual/GdaiPriceProvider.sol"; import "../../src/v2/oracles/individual/UmaV2PriceProvider.sol"; import "../../src/v2/oracles/individual/UmaV2AssertionProvider.sol"; import "../../src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol"; +import "../../src/v2/oracles/individual/PythPriceProvider.sol"; import "../../src/v2/TimeLock.sol"; import "./V2Helper.sol"; import { @@ -180,6 +181,15 @@ contract V2DeployContracts is Script, HelperV2 { // requiredBond // ); + // address pythContract = 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C; + // // bytes32 fdUsdFeedId = 0xccdc1a08923e2e4f4b1e6ea89de6acbc5fe1948e9706f5604b8cb50bc1ed3979; + // bytes32 cUsdFeedId = 0x8f218655050a1476b780185e89f19d2b1e1f49e9bd629efad6ac547a946bf6ab; + // PythPriceProvider pythProvider = new PythPriceProvider( + // pythContract, + // cUsdFeedId, + // timeOut + // ); + // vaultFactory.whitelistController(address(controller)); // KeeperV2 resolveKeeper = new KeeperV2( // payable(addresses.gelatoOpsV2), @@ -221,7 +231,8 @@ contract V2DeployContracts is Script, HelperV2 { // console2.log("Gdai Price Provider", address(gdaiPriceProvider)); // console2.log("CVI Price Provider", address(cviPriceProvider)); // console2.log("Dia Price Provider", address(diaPriceProvider)); - console2.log("Uma V2 Price Provider", address(umaV2PriceProvider)); + // console2.log("Uma V2 Price Provider", address(umaV2PriceProvider)); + // console.log("Pyth Price Provider", address(pythProvider)); // console2.log( // "Uma V2 Assertion Provider", // address(umaV2AssertionProvider) From 9e526e2c47f3cfe5b66440f365b3a6c70edc4ae0 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 26 Oct 2023 18:38:05 -0600 Subject: [PATCH 21/25] feat: umaV2 fetchAssert pulls funds --- script/v2/V2DeployContracts.s.sol | 62 +++++++++---------- .../individual/UmaV2AssertionProvider.sol | 33 +++++++++- .../oracles/individual/UmaV2PriceProvider.sol | 1 + .../UmaV3DynamicAssertionProvider.sol | 2 +- .../UmaV3EventAssertionProvider.sol | 2 +- .../individual/UmaV2AssertionProvider.t.sol | 8 ++- .../individual/UmaV2PriceProvider.t.sol | 6 +- 7 files changed, 76 insertions(+), 38 deletions(-) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index f4835123..e919d06f 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -133,34 +133,34 @@ contract V2DeployContracts is Script, HelperV2 { uint256 timeOut = 2 hours; address umaCurrency = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; address umaV2Finder = 0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1; - uint256 reward = 1e6; + uint256 reward = 5e6; - uint256 umaDecimals = 18; - string memory umaDescription = "FUSD/USD"; - string - memory ancillaryData = 'base:FDUSD,baseAddress:0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409,baseChain: 1,quote:USD,quoteDetails:United States Dollar,rounding:18,fallback:"https://www.coingecko.com/en/coins/first-digital-usd",configuration:{"type": "medianizer","minTimeBetweenUpdates": 60,"twapLength": 600,"medianizedFeeds":[{ "type": "cryptowatch", "exchange": "binance", "pair": "fdusdusdt" }]}'; - - UmaV2PriceProvider umaV2PriceProvider = new UmaV2PriceProvider( - timeOut, - umaDecimals, - umaDescription, - umaV2Finder, - umaCurrency, - ancillaryData, - reward - ); - - // string memory umaDescription = "AAVE aUSDC Hack Market"; + // uint256 umaDecimals = 18; + // string memory umaDescription = "FUSD/USD"; // string - // memory ancillaryData = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; - // UmaV2AssertionProvider umaV2AssertionProvider = new UmaV2AssertionProvider( - // timeOut, - // umaDescription, - // umaV2Finder, - // umaCurrency, - // ancillaryData, - // reward - // ); + // memory ancillaryData = 'base:FDUSD,baseAddress:0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409,baseChain: 1,quote:USD,quoteDetails:United States Dollar,rounding:18,fallback:"https://www.coingecko.com/en/coins/first-digital-usd",configuration:{"type": "medianizer","minTimeBetweenUpdates": 60,"twapLength": 600,"medianizedFeeds":[{ "type": "cryptowatch", "exchange": "binance", "pair": "fdusdusdt" }]}'; + + // UmaV2PriceProvider umaV2PriceProvider = new UmaV2PriceProvider( + // timeOut, + // umaDecimals, + // umaDescription, + // umaV2Finder, + // umaCurrency, + // ancillaryData, + // reward + // ); + + string memory umaDescription = "AAVE aUSDC Hack Market"; + string + memory ancillaryData = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; + UmaV2AssertionProvider umaV2AssertionProvider = new UmaV2AssertionProvider( + timeOut, + umaDescription, + umaV2Finder, + umaCurrency, + ancillaryData, + reward + ); // uint256 umaDecimals = 18; // address umaOOV3 = address(0x123); @@ -231,12 +231,12 @@ contract V2DeployContracts is Script, HelperV2 { // console2.log("Gdai Price Provider", address(gdaiPriceProvider)); // console2.log("CVI Price Provider", address(cviPriceProvider)); // console2.log("Dia Price Provider", address(diaPriceProvider)); - // console2.log("Uma V2 Price Provider", address(umaV2PriceProvider)); // console.log("Pyth Price Provider", address(pythProvider)); - // console2.log( - // "Uma V2 Assertion Provider", - // address(umaV2AssertionProvider) - // ); + // console2.log("Uma V2 Price Provider", address(umaV2PriceProvider)); + console2.log( + "Uma V2 Assertion Provider", + address(umaV2AssertionProvider) + ); // console2.log("Uma Price Provider", address(umaPriceProvider)); // console2.log("resolveKeeper address", address(resolveKeeper)); diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol index 8c7a9f6d..b29ad097 100644 --- a/src/v2/oracles/individual/UmaV2AssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -134,9 +134,10 @@ contract UmaV2AssertionProvider is Ownable { bytes memory _bytesAncillary = abi.encodePacked( ancillaryData, - coverageStart, + _toUtf8BytesUint(coverageStart), ANCILLARY_TAIL ); + currency.transferFrom(msg.sender, address(this), reward); currency.approve(address(oo), reward); oo.requestPrice( PRICE_IDENTIFIER, @@ -198,6 +199,36 @@ contract UmaV2AssertionProvider is Ownable { else revert ConditionTypeNotSet(); } + /*////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////*/ + /** + * @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type. + * @dev This method is based off of this code: https://stackoverflow.com/a/65707309. + * @dev Pulled from UMA protocol packages: https://github.com/UMAprotocol/protocol/blob/9bfbbe98bed0ac7d9c924115018bb0e26987e2b5/packages/core/contracts/common/implementation/AncillaryData.sol + */ + function _toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { + if (x == 0) { + return "0"; + } + uint256 j = x; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len; + while (x != 0) { + k = k - 1; + uint8 temp = (48 + uint8(x - (x / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + x /= 10; + } + return bstr; + } + /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol index c2213a2d..3c217428 100644 --- a/src/v2/oracles/individual/UmaV2PriceProvider.sol +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -127,6 +127,7 @@ contract UmaV2PriceProvider is Ownable { if (pendingAnswer.startedAt != 0) revert RequestInProgress(); bytes memory _bytesAncillary = abi.encodePacked(ancillaryData); + currency.transferFrom(msg.sender, address(this), reward); currency.approve(address(oo), reward); oo.requestPrice( PRICE_IDENTIFIER, diff --git a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol index 11cdb0d9..e0718619 100644 --- a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol @@ -264,7 +264,7 @@ contract UmaV3DynamicAssertionProvider is Ownable { ", the following statement is", _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, assertionDescription, - assertionData.assertionData + _toUtf8BytesUint(assertionData.assertionData) ); } diff --git a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol index aa9867fd..4ecaf5d1 100644 --- a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol @@ -243,7 +243,7 @@ contract UmaV3EventAssertionProvider is Ownable { _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, assertionDescription, "This occured after the timestamp of ", - coverageStart + _toUtf8BytesUint(coverageStart) ); } diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index 9a6ae6cc..924d62b6 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -15,7 +15,7 @@ import { MockUmaV2, MockUmaFinder } from "../mocks/MockOracles.sol"; -import "forge-std/console.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // Specification for YER_NO_QUERY on Uma: https://github.com/UMAprotocol/UMIPs/blob/master/UMIPs/umip-107.md // Uma address all networks: https://docs.uma.xyz/resources/network-addresses @@ -58,7 +58,8 @@ contract UmaV2AssertionProviderTest is Helper { ); uint256 condition = 2; umaV2AssertionProvider.setConditionType(marketId, condition); - deal(USDC_TOKEN, address(umaV2AssertionProvider), 1000e6); + deal(USDC_TOKEN, address(this), 1000e6); + IERC20(USDC_TOKEN).approve(address(umaV2AssertionProvider), 1000e6); } //////////////////////////////////////////////// @@ -86,7 +87,6 @@ contract UmaV2AssertionProviderTest is Helper { coverageStart, ancillaryDataTail ); - console.logBytes(output); } //////////////////////////////////////////////// @@ -140,6 +140,7 @@ contract UmaV2AssertionProviderTest is Helper { ancillaryData, reward ); + IERC20(USDC_TOKEN).approve(address(umaV2AssertionProvider), 1000e6); // Configuring the pending answer uint256 previousTimestamp = block.timestamp; @@ -358,6 +359,7 @@ contract UmaV2AssertionProviderTest is Helper { ancillaryData, reward ); + IERC20(USDC_TOKEN).approve(address(umaV2AssertionProvider), 1000e6); // Configuring the pending answer umaV2AssertionProvider.requestLatestAssertion(); diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index 998df221..6b7c77a6 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -15,6 +15,7 @@ import { MockUmaV2, MockUmaFinder } from "../mocks/MockOracles.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // The configuration information for TOKEN_PRICE query: https://github.com/UMAprotocol/UMIPs/blob/master/UMIPs/umip-121.md // Price feeds to use in config: https://github.com/UMAprotocol/protocol/tree/master/packages/financial-templates-lib/src/price-feed @@ -59,7 +60,8 @@ contract UmaV2PriceProviderTest is Helper { ); uint256 condition = 2; umaV2PriceProvider.setConditionType(marketId, condition); - deal(USDC_TOKEN, address(umaV2PriceProvider), 1000e6); + deal(USDC_TOKEN, address(this), 1000e6); + IERC20(USDC_TOKEN).approve(address(umaV2PriceProvider), 1000e6); } //////////////////////////////////////////////// @@ -122,6 +124,7 @@ contract UmaV2PriceProviderTest is Helper { ancillaryData, reward ); + IERC20(USDC_TOKEN).approve(address(umaV2PriceProvider), 1000e6); // Configuring the pending answer uint256 previousTimestamp = block.timestamp; @@ -362,6 +365,7 @@ contract UmaV2PriceProviderTest is Helper { ancillaryData, reward ); + IERC20(USDC_TOKEN).approve(address(umaV2PriceProvider), 1000e6); // Configuring the pending answer umaV2PriceProvider.requestLatestPrice(); From 54a933905a0ad4a73a18526c74176ece442e2eb3 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Wed, 1 Nov 2023 10:33:43 -0600 Subject: [PATCH 22/25] feat: uma storage and pending approach optimised --- script/v2/V2DeployContracts.s.sol | 2 +- .../individual/UmaV2AssertionProvider.sol | 55 ++++++------- .../oracles/individual/UmaV2PriceProvider.sol | 45 +++++------ .../individual/UmaV2AssertionProvider.t.sol | 80 ++++++++++++++++--- .../individual/UmaV2PriceProvider.t.sol | 69 ++++++++++++++-- 5 files changed, 176 insertions(+), 75 deletions(-) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index e919d06f..11110f83 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -133,7 +133,7 @@ contract V2DeployContracts is Script, HelperV2 { uint256 timeOut = 2 hours; address umaCurrency = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; address umaV2Finder = 0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1; - uint256 reward = 5e6; + uint128 reward = 5e6; // uint256 umaDecimals = 18; // string memory umaDescription = "FUSD/USD"; diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol index b29ad097..f5f40822 100644 --- a/src/v2/oracles/individual/UmaV2AssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -17,29 +17,29 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; */ contract UmaV2AssertionProvider is Ownable { struct AssertionAnswer { - uint80 roundId; - int256 assertion; - uint256 startedAt; + uint8 roundId; + uint8 answeredInRound; + int8 assertion; + uint128 pendingRequestAt; uint256 updatedAt; - uint80 answeredInRound; } + uint256 public constant REQUEST_TIMEOUT = 3600 * 3; uint256 public constant ORACLE_LIVENESS_TIME = 3600 * 2; bytes32 public constant PRICE_IDENTIFIER = "YES_OR_NO_QUERY"; string public constant ANCILLARY_TAIL = ". P1: 0 for NO, P2: 1 for YES, P3: 2 for UNDETERMINED"; - uint256 public immutable timeOut; + uint256 public immutable assertionTimeOut; IUmaV2 public immutable oo; IFinder public immutable finder; IERC20 public immutable currency; + AssertionAnswer public answer; + uint128 public reward; + uint128 public coverageStart; string public description; string public ancillaryData; - AssertionAnswer public answer; - AssertionAnswer public pendingAnswer; - uint256 public reward; - uint256 public coverageStart; mapping(uint256 => uint256) public marketIdToConditionType; @@ -50,14 +50,14 @@ contract UmaV2AssertionProvider is Ownable { event PriceRequested(); constructor( - uint256 _timeOut, + uint256 _assertionTimeOut, string memory _description, address _finder, address _currency, string memory _ancillaryData, - uint256 _reward + uint128 _reward ) { - if (_timeOut == 0) revert InvalidInput(); + if (_assertionTimeOut == 0) revert InvalidInput(); if (keccak256(bytes(_description)) == keccak256("")) revert InvalidInput(); if (_finder == address(0)) revert ZeroAddress(); @@ -66,7 +66,7 @@ contract UmaV2AssertionProvider is Ownable { revert InvalidInput(); if (_reward == 0) revert InvalidInput(); - timeOut = _timeOut; + assertionTimeOut = _assertionTimeOut; description = _description; finder = IFinder(_finder); @@ -74,7 +74,7 @@ contract UmaV2AssertionProvider is Ownable { currency = IERC20(_currency); ancillaryData = _ancillaryData; reward = _reward; - coverageStart = block.timestamp; + coverageStart = uint128(block.timestamp); } /*////////////////////////////////////////////////////////////// @@ -90,13 +90,13 @@ contract UmaV2AssertionProvider is Ownable { emit MarketConditionSet(_marketId, _condition); } - function updateCoverageStart(uint256 _coverageStart) external onlyOwner { + function updateCoverageStart(uint128 _coverageStart) external onlyOwner { if (_coverageStart < coverageStart) revert InvalidInput(); coverageStart = _coverageStart; emit CoverageStartUpdated(_coverageStart); } - function updateReward(uint256 newReward) external onlyOwner { + function updateReward(uint128 newReward) external onlyOwner { if (newReward == 0) revert InvalidInput(); reward = newReward; emit RewardUpdated(newReward); @@ -113,14 +113,12 @@ contract UmaV2AssertionProvider is Ownable { ) external { if (msg.sender != address(oo)) revert InvalidCaller(); - AssertionAnswer memory _pendingAnswer = pendingAnswer; - AssertionAnswer memory _answer = answer; - - _answer.startedAt = _pendingAnswer.startedAt; - _answer.updatedAt = _timestamp; - _answer.assertion = _price; - _answer.roundId = 1; - _answer.answeredInRound = 1; + AssertionAnswer memory _answer; + _answer.updatedAt = uint128(_timestamp); + _answer.assertion = int8(_price); + _answer.roundId = answer.roundId + 1; + _answer.answeredInRound = answer.answeredInRound + 1; + _answer.pendingRequestAt = answer.pendingRequestAt; answer = _answer; emit PriceSettled(_price); @@ -130,7 +128,8 @@ contract UmaV2AssertionProvider is Ownable { PUBLIC //////////////////////////////////////////////////////////////*/ function requestLatestAssertion() external { - if (pendingAnswer.startedAt != 0) revert RequestInProgress(); + if (answer.pendingRequestAt + REQUEST_TIMEOUT > block.timestamp) + revert RequestInProgress(); bytes memory _bytesAncillary = abi.encodePacked( ancillaryData, @@ -161,9 +160,7 @@ contract UmaV2AssertionProvider is Ownable { true ); - AssertionAnswer memory _pendingAnswer; - _pendingAnswer.startedAt = block.timestamp; - pendingAnswer = _pendingAnswer; + answer.pendingRequestAt = uint128(block.timestamp); emit PriceRequested(); } @@ -175,7 +172,7 @@ contract UmaV2AssertionProvider is Ownable { AssertionAnswer memory assertionAnswer = answer; if (assertionAnswer.updatedAt == 0) revert OraclePriceZero(); - if ((block.timestamp - assertionAnswer.updatedAt) > timeOut) + if ((block.timestamp - assertionAnswer.updatedAt) > assertionTimeOut) revert PriceTimedOut(); if (assertionAnswer.assertion == 1) return true; diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol index 3c217428..2aa19f3b 100644 --- a/src/v2/oracles/individual/UmaV2PriceProvider.sol +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -19,15 +19,16 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; */ contract UmaV2PriceProvider is Ownable { struct PriceAnswer { - uint80 roundId; - int256 price; - uint256 startedAt; - uint256 updatedAt; - uint80 answeredInRound; + uint128 roundId; + uint128 answeredInRound; + int128 price; + uint128 updatedAt; + uint256 pendingRequestAt; } uint256 public constant ORACLE_LIVENESS_TIME = 3600 * 2; bytes32 public constant PRICE_IDENTIFIER = "TOKEN_PRICE"; + uint256 public constant REQUEST_TIMEOUT = 3600 * 3; uint256 public immutable timeOut; IUmaV2 public immutable oo; @@ -35,11 +36,10 @@ contract UmaV2PriceProvider is Ownable { uint256 public immutable decimals; IERC20 public immutable currency; - string public description; - string public ancillaryData; PriceAnswer public answer; - PriceAnswer public pendingAnswer; uint256 public reward; + string public description; + string public ancillaryData; mapping(uint256 => uint256) public marketIdToConditionType; @@ -107,14 +107,12 @@ contract UmaV2PriceProvider is Ownable { ) external { if (msg.sender != address(oo)) revert InvalidCaller(); - PriceAnswer memory _pendingAnswer = pendingAnswer; - PriceAnswer memory _answer = answer; - - _answer.startedAt = _pendingAnswer.startedAt; - _answer.updatedAt = _timestamp; - _answer.price = _price; - _answer.roundId = 1; - _answer.answeredInRound = 1; + PriceAnswer memory _answer; + _answer.updatedAt = uint128(_timestamp); + _answer.price = int128(_price); + _answer.roundId = answer.roundId + 1; + _answer.answeredInRound = answer.answeredInRound + 1; + _answer.pendingRequestAt = answer.pendingRequestAt; answer = _answer; emit PriceSettled(_price); @@ -124,7 +122,8 @@ contract UmaV2PriceProvider is Ownable { PUBLIC //////////////////////////////////////////////////////////////*/ function requestLatestPrice() external { - if (pendingAnswer.startedAt != 0) revert RequestInProgress(); + if (answer.pendingRequestAt + REQUEST_TIMEOUT > block.timestamp) + revert RequestInProgress(); bytes memory _bytesAncillary = abi.encodePacked(ancillaryData); currency.transferFrom(msg.sender, address(this), reward); @@ -151,9 +150,7 @@ contract UmaV2PriceProvider is Ownable { true ); - PriceAnswer memory _pendingAnswer; - _pendingAnswer.startedAt = block.timestamp; - pendingAnswer = _pendingAnswer; + answer.pendingRequestAt = block.timestamp; emit PriceRequested(); } @@ -164,17 +161,17 @@ contract UmaV2PriceProvider is Ownable { returns ( uint80 roundId, int256 price, - uint256 startedAt, + uint256 pendingRequestAt, uint256 updatedAt, uint80 answeredInRound ) { PriceAnswer memory _answer = answer; + roundId = uint80(_answer.roundId); price = _answer.price; updatedAt = _answer.updatedAt; - roundId = _answer.roundId; - startedAt = _answer.startedAt; - answeredInRound = _answer.answeredInRound; + pendingRequestAt = _answer.pendingRequestAt; + answeredInRound = uint80(_answer.answeredInRound); } /** @notice Fetch token price from priceFeed (Chainlink oracle address) diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index 924d62b6..9b6ba9c8 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -36,7 +36,7 @@ contract UmaV2AssertionProviderTest is Helper { string public umaDescription; address public umaCurrency; string public ancillaryData; - uint256 public reward; + uint128 public reward; function setUp() public { arbForkId = vm.createFork(ARBITRUM_RPC_URL); @@ -68,7 +68,7 @@ contract UmaV2AssertionProviderTest is Helper { function testUmaV2AssertionProvider() public { assertEq(umaV2AssertionProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); assertEq(umaV2AssertionProvider.PRICE_IDENTIFIER(), "YES_OR_NO_QUERY"); - assertEq(umaV2AssertionProvider.timeOut(), TIME_OUT); + assertEq(umaV2AssertionProvider.assertionTimeOut(), TIME_OUT); assertEq(address(umaV2AssertionProvider.oo()), umaV2); assertEq(address(umaV2AssertionProvider.finder()), UMAV2_FINDER); assertEq(address(umaV2AssertionProvider.currency()), umaCurrency); @@ -104,7 +104,7 @@ contract UmaV2AssertionProviderTest is Helper { } function testUpdateCoverageStartUmaV2Assert() public { - uint256 newCoverageStart = block.timestamp + 1 days; + uint128 newCoverageStart = uint128(block.timestamp + 1 days); vm.expectEmit(true, true, true, true); emit CoverageStartUpdated(newCoverageStart); umaV2AssertionProvider.updateCoverageStart(newCoverageStart); @@ -113,7 +113,7 @@ contract UmaV2AssertionProviderTest is Helper { } function testUpdateRewardUmaV2Assert() public { - uint256 newReward = 1000; + uint128 newReward = 1000; vm.expectEmit(true, true, true, true); emit RewardUpdated(newReward); umaV2AssertionProvider.updateReward(newReward); @@ -156,17 +156,17 @@ contract UmaV2AssertionProviderTest is Helper { price ); ( - uint80 roundId, - int256 _price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound + uint8 roundId, + uint8 answeredInRound, + int8 _price, + uint256 pendingRequestAt, + uint256 updatedAt ) = umaV2AssertionProvider.answer(); // Checking the data assertEq(roundId, 1); assertEq(_price, price); - assertEq(startedAt, previousTimestamp); + assertEq(pendingRequestAt, previousTimestamp); assertEq(updatedAt, block.timestamp); assertEq(answeredInRound, 1); } @@ -176,8 +176,8 @@ contract UmaV2AssertionProviderTest is Helper { //////////////////////////////////////////////// function testrequestLatestAssertionUmaV2Assert() public { umaV2AssertionProvider.requestLatestAssertion(); - (, , uint256 startedAt, , ) = umaV2AssertionProvider.pendingAnswer(); - assertEq(startedAt, block.timestamp); + (, , , uint128 pendingRequestAt, ) = umaV2AssertionProvider.answer(); + assertEq(pendingRequestAt, block.timestamp); } function testCheckAssertionUmaV2Assert() public { @@ -217,6 +217,36 @@ contract UmaV2AssertionProviderTest is Helper { assertEq(condition, false); } + function testPriceDeliveredRound2UmaV2Assert() public { + // Config the data with mock oracle + address mockUmaV2 = _configureSettledPrice(true); + + uint256 conditionType = 2; + umaV2AssertionProvider.setConditionType(marketId, conditionType); + (bool condition, int256 price) = umaV2AssertionProvider.conditionMet( + 2 ether, + marketId + ); + (uint8 roundId, uint8 answeredInRound, , , ) = umaV2AssertionProvider + .answer(); + assertEq(price, 0); + assertEq(condition, true); + assertEq(roundId, 1); + assertEq(answeredInRound, 1); + + // Updating the price + _updatePrice(false, mockUmaV2); + (condition, price) = umaV2AssertionProvider.conditionMet( + 2 ether, + marketId + ); + (roundId, answeredInRound, , , ) = umaV2AssertionProvider.answer(); + assertEq(price, 0); + assertEq(condition, false); + assertEq(roundId, 2); + assertEq(answeredInRound, 2); + } + //////////////////////////////////////////////// // REVERT CASES // //////////////////////////////////////////////// @@ -342,7 +372,9 @@ contract UmaV2AssertionProviderTest is Helper { //////////////////////////////////////////////// // HELPER // //////////////////////////////////////////////// - function _configureSettledPrice(bool condition) internal { + function _configureSettledPrice( + bool condition + ) internal returns (address mockUma) { // Config the data using the mock oracle bytes32 emptyBytes32; bytes memory emptyBytes; @@ -373,5 +405,27 @@ contract UmaV2AssertionProviderTest is Helper { emptyBytes, price ); + + return address(mockUmaV2); + } + + function _updatePrice(bool condition, address mockUmaV2) internal { + // Config the data using the mock oracle + bytes32 emptyBytes32; + bytes memory emptyBytes; + int256 price = condition ? int256(1) : int256(0); + + // Configuring the pending answer + umaV2AssertionProvider.requestLatestAssertion(); + vm.warp(block.timestamp + 1 days); + + // Configuring the answer via the callback + vm.prank(address(mockUmaV2)); + umaV2AssertionProvider.priceSettled( + emptyBytes32, + block.timestamp, + emptyBytes, + price + ); } } diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index 6b7c77a6..d8261f75 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -140,11 +140,11 @@ contract UmaV2PriceProviderTest is Helper { price ); ( - uint80 roundId, - int256 _price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound + uint128 roundId, + uint128 answeredInRound, + int128 _price, + uint128 updatedAt, + uint256 startedAt ) = umaV2PriceProvider.answer(); // Checking the data @@ -158,10 +158,11 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// // PUBLIC FUNCTIONS // //////////////////////////////////////////////// + function testrequestLatestPriceUmaV2Price() public { umaV2PriceProvider.requestLatestPrice(); - (, , uint256 startedAt, , ) = umaV2PriceProvider.pendingAnswer(); - assertEq(startedAt, block.timestamp); + (, , , , uint256 pendingRequestAt) = umaV2PriceProvider.answer(); + assertEq(pendingRequestAt, block.timestamp); } function testLatestRoundDataUmaV2Price() public { @@ -222,6 +223,35 @@ contract UmaV2PriceProviderTest is Helper { assertEq(condition, true); } + function testPriceDeliveredRound2UmaV2() public { + // Config the data with mock oracle + address mockUmaV2 = _configureSettledPrice(); + (uint80 roundId, , , , uint80 answeredInRound) = umaV2PriceProvider + .latestRoundData(); + assertEq(roundId, 1); + assertEq(answeredInRound, 1); + + uint256 conditionType = 2; + umaV2PriceProvider.setConditionType(marketId, conditionType); + (bool condition, int256 price) = umaV2PriceProvider.conditionMet( + 2 ether, + marketId + ); + assertTrue(price != 0); + assertEq(condition, true); + + // Updating the price + int256 newPrice = 10e6; + _updatePrice(newPrice, mockUmaV2); + (roundId, , , , answeredInRound) = umaV2PriceProvider.latestRoundData(); + assertEq(roundId, 2); + assertEq(answeredInRound, 2); + + (condition, price) = umaV2PriceProvider.conditionMet(2 ether, marketId); + assertEq(price, newPrice * 10 ** 10); + assertEq(condition, true); + } + //////////////////////////////////////////////// // REVERT CASES // //////////////////////////////////////////////// @@ -347,7 +377,7 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// // HELPER // //////////////////////////////////////////////// - function _configureSettledPrice() internal { + function _configureSettledPrice() internal returns (address) { // Config the data using the mock oracle bytes32 emptyBytes32; bytes memory emptyBytes; @@ -379,5 +409,28 @@ contract UmaV2PriceProviderTest is Helper { emptyBytes, price ); + + return address(mockUmaV2); + } + + function _updatePrice(int256 price, address mockUmaV2) internal { + // Config the data using the mock oracle + bytes32 emptyBytes32; + bytes memory emptyBytes; + + IERC20(USDC_TOKEN).approve(address(umaV2PriceProvider), 1000e6); + + // Configuring the pending answer + umaV2PriceProvider.requestLatestPrice(); + vm.warp(block.timestamp + 1 days); + + // Configuring the answer via the callback + vm.prank(address(mockUmaV2)); + umaV2PriceProvider.priceSettled( + emptyBytes32, + block.timestamp, + emptyBytes, + price + ); } } From a1600c912fd4ecb6ffc008b6ca753c6325b17f69 Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Wed, 13 Dec 2023 16:09:33 -0500 Subject: [PATCH 23/25] feat: refactored UmaV3 --- script/v2/V2DeployContracts.s.sol | 52 ++- src/v2/interfaces/IConditionProvider.sol | 6 - src/v2/interfaces/IOptimisticOracleV3.sol | 2 + .../UmaV3DynamicAssertionProvider.sol | 270 +++++++----- .../UmaV3EventAssertionProvider.sol | 10 +- .../UmaV3PriceAssertionProvider.sol | 10 +- test/V2/Helper.sol | 1 + .../individual/PythPriceProvider.t.sol | 9 +- .../individual/UmaV2AssertionProvider.t.sol | 20 +- .../UmaV3DynamicAssertionProvider.t.sol | 396 +++++++++--------- .../UmaV3EventAssertionProvider.t.sol | 49 +++ .../UmaV3PriceAssertionProvider.t.sol | 49 +++ .../MockRedstoneCoreUniversalProvider.sol | 2 +- test/V2/oracles/mocks/MockUma.sol | 13 +- 14 files changed, 541 insertions(+), 348 deletions(-) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index 11110f83..65e1dda1 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -17,6 +17,7 @@ import "../../src/v2/oracles/individual/GdaiPriceProvider.sol"; import "../../src/v2/oracles/individual/UmaV2PriceProvider.sol"; import "../../src/v2/oracles/individual/UmaV2AssertionProvider.sol"; import "../../src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol"; +import "../../src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol"; import "../../src/v2/oracles/individual/PythPriceProvider.sol"; import "../../src/v2/TimeLock.sol"; import "./V2Helper.sol"; @@ -131,9 +132,9 @@ contract V2DeployContracts is Script, HelperV2 { // DIAPriceProvider diaPriceProvider = new DIAPriceProvider(diaOracleV2); uint256 timeOut = 2 hours; - address umaCurrency = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; - address umaV2Finder = 0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1; - uint128 reward = 5e6; + // address umaCurrency = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + // address umaV2Finder = 0xB0b9f73B424AD8dc58156C2AE0D7A1115D1EcCd1; + // uint128 reward = 5e6; // uint256 umaDecimals = 18; // string memory umaDescription = "FUSD/USD"; @@ -150,20 +151,20 @@ contract V2DeployContracts is Script, HelperV2 { // reward // ); - string memory umaDescription = "AAVE aUSDC Hack Market"; - string - memory ancillaryData = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; - UmaV2AssertionProvider umaV2AssertionProvider = new UmaV2AssertionProvider( - timeOut, - umaDescription, - umaV2Finder, - umaCurrency, - ancillaryData, - reward - ); + // string memory umaDescription = "AAVE aUSDC Hack Market"; + // string + // memory ancillaryData = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: "; + // UmaV2AssertionProvider umaV2AssertionProvider = new UmaV2AssertionProvider( + // timeOut, + // umaDescription, + // umaV2Finder, + // umaCurrency, + // ancillaryData, + // reward + // ); // uint256 umaDecimals = 18; - // address umaOOV3 = address(0x123); + address umaOOV3 = 0xa6147867264374F324524E30C02C331cF28aa879; // string memory umaDescription = "USDC"; // uint256 requiredBond = 1e6; // bytes32 defaultIdentifier = bytes32("abc"); @@ -181,6 +182,17 @@ contract V2DeployContracts is Script, HelperV2 { // requiredBond // ); + string memory marketDescription = "ETH Volatility"; + uint256 requiredBond = 1e6; + address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS + UmaV3DynamicAssertionProvider umaV3Provider = new UmaV3DynamicAssertionProvider( + marketDescription, + timeOut, + umaOOV3, + currency, + requiredBond + ); + // address pythContract = 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C; // // bytes32 fdUsdFeedId = 0xccdc1a08923e2e4f4b1e6ea89de6acbc5fe1948e9706f5604b8cb50bc1ed3979; // bytes32 cUsdFeedId = 0x8f218655050a1476b780185e89f19d2b1e1f49e9bd629efad6ac547a946bf6ab; @@ -233,11 +245,11 @@ contract V2DeployContracts is Script, HelperV2 { // console2.log("Dia Price Provider", address(diaPriceProvider)); // console.log("Pyth Price Provider", address(pythProvider)); // console2.log("Uma V2 Price Provider", address(umaV2PriceProvider)); - console2.log( - "Uma V2 Assertion Provider", - address(umaV2AssertionProvider) - ); - // console2.log("Uma Price Provider", address(umaPriceProvider)); + // console2.log( + // "Uma V2 Assertion Provider", + // address(umaV2AssertionProvider) + // ); + // console2.log("Uma V3 Provider", address(umaV3Provider)); // console2.log("resolveKeeper address", address(resolveKeeper)); // console2.log("resolveKeeperGenericController address", address(resolveKeeperGenericController)); diff --git a/src/v2/interfaces/IConditionProvider.sol b/src/v2/interfaces/IConditionProvider.sol index 13b31155..491b6987 100644 --- a/src/v2/interfaces/IConditionProvider.sol +++ b/src/v2/interfaces/IConditionProvider.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.17; interface IConditionProvider { - function setConditionType(uint256 _marketId, uint256 _condition) external; - function getLatestPrice() external view returns (int256); function conditionMet( @@ -15,8 +13,4 @@ interface IConditionProvider { external view returns (uint80, int256, uint256, uint256, uint80); - - function marketIdToConditionType( - uint256 _marketId - ) external view returns (uint256); } diff --git a/src/v2/interfaces/IOptimisticOracleV3.sol b/src/v2/interfaces/IOptimisticOracleV3.sol index b43484e5..60954495 100644 --- a/src/v2/interfaces/IOptimisticOracleV3.sol +++ b/src/v2/interfaces/IOptimisticOracleV3.sol @@ -17,4 +17,6 @@ interface IOptimisticOracleV3 { ) external payable returns (bytes32 assertionId); function getMinimumBond(address currency) external returns (uint256); + + function defaultIdentifier() external returns (bytes32); } diff --git a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol index e0718619..c3266ac0 100644 --- a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol @@ -7,10 +7,10 @@ import {IOptimisticOracleV3} from "../../interfaces/IOptimisticOracleV3.sol"; import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - import "forge-std/console.sol"; -/// @notice Assertion provider where the condition can be checked without a required begin time e.g. 1 PEPE > $1000 or BTC market > 2x ETH market cap +/// @notice Assertion provider for one data point with differing description +/// @dev Example: (a) ETH vol is above , and (b) ETH vol is below /// @dev This provider would not work if you needed to check if x happened between time y and z contract UmaV3DynamicAssertionProvider is Ownable { using SafeTransferLib for ERC20; @@ -26,78 +26,59 @@ contract UmaV3DynamicAssertionProvider is Ownable { uint128 updatedAt; } - string public constant OUTCOME_1 = "true. "; - string public constant OUTCOME_2 = "false. "; - // Uma V3 uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. + uint256 public constant ASSERTION_COOLDOWN = 300; // 5 minutes. address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; // Market info uint256 public immutable timeOut; - uint256 public immutable decimals; string public description; - bytes public assertionDescription; AssertionData public assertionData; // The uint data value for the market uint256 public requiredBond; // Bond required to assert on a market - mapping(uint256 => uint256) public marketIdToConditionType; + mapping(uint256 => string) public marketIdToAssertionDescription; mapping(uint256 => MarketAnswer) public marketIdToAnswer; mapping(bytes32 => uint256) public assertionIdToMarket; mapping(address => bool) public whitelistRelayer; event MarketAsserted(uint256 marketId, bytes32 assertionId); event AssertionResolved(bytes32 assertionId, bool assertion); - event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); + event DescriptionSet(uint256 marketId, string description); event BondUpdated(uint256 newBond); event AssertionDataUpdated(uint256 newData); event RelayerUpdated(address relayer, bool state); + event BondWithdrawn(uint256 amount); + event AnswersReset(uint256[] marketIds); /** - @param _decimals is decimals for the provider maker if relevant @param _description is for the price provider market @param _timeOut is the max time between receiving callback and resolving market condition @param _umaV3 is the V3 Uma Optimistic Oracle - @param _defaultIdentifier is UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. @param _currency is currency used to post the bond - @param _assertionDescription is description used for the market @param _requiredBond is bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This must be >= getMinimumBond(address(currency)). */ constructor( - uint256 _decimals, string memory _description, uint256 _timeOut, address _umaV3, - bytes32 _defaultIdentifier, address _currency, - bytes memory _assertionDescription, uint256 _requiredBond ) { - if (_decimals == 0) revert InvalidInput(); if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) revert InvalidInput(); if (_timeOut == 0) revert InvalidInput(); if (_umaV3 == address(0)) revert ZeroAddress(); - if ( - keccak256(abi.encodePacked(_defaultIdentifier)) == - keccak256(abi.encodePacked(bytes32(""))) - ) revert InvalidInput(); if (_currency == address(0)) revert ZeroAddress(); - if ( - keccak256(abi.encodePacked(_assertionDescription)) == - keccak256(bytes("")) - ) revert InvalidInput(); if (_requiredBond == 0) revert InvalidInput(); - decimals = _decimals; description = _description; timeOut = _timeOut; umaV3 = IOptimisticOracleV3(_umaV3); - defaultIdentifier = _defaultIdentifier; + defaultIdentifier = umaV3.defaultIdentifier(); currency = _currency; - assertionDescription = _assertionDescription; requiredBond = _requiredBond; assertionData.updatedAt = uint128(block.timestamp); } @@ -105,40 +86,59 @@ contract UmaV3DynamicAssertionProvider is Ownable { /*////////////////////////////////////////////////////////////// ADMIN //////////////////////////////////////////////////////////////*/ - function setConditionType( + /** + @notice Defines the assertion description for a market + @dev Can only be set once and the assertionData is expected to be updated for assertions + */ + function setAssertionDescription( uint256 _marketId, - uint256 _condition + string calldata _description ) external onlyOwner { - if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); - if (_condition != 1 && _condition != 2) revert InvalidInput(); - marketIdToConditionType[_marketId] = _condition; - emit MarketConditionSet(_marketId, _condition); + if ( + keccak256( + abi.encodePacked(marketIdToAssertionDescription[_marketId]) + ) != keccak256(abi.encodePacked("")) + ) revert DescriptionAlreadySet(); + marketIdToAssertionDescription[_marketId] = _description; + emit DescriptionSet(_marketId, _description); } + /** + @notice Updates the default bond used by this contract when asserting on Uma + @param newBond is the new bond amount + */ function updateRequiredBond(uint256 newBond) external onlyOwner { if (newBond == 0) revert InvalidInput(); requiredBond = newBond; emit BondUpdated(newBond); } - function updateAssertionData(uint256 _newData) public onlyOwner { - if (_newData == 0) revert InvalidInput(); - assertionData = AssertionData({ - assertionData: uint128(_newData), - updatedAt: uint128(block.timestamp) - }); - - emit AssertionDataUpdated(_newData); + /** + @notice Updates the data being used to assert in the uma assertion request + @param _newData is the new data for the assertion + */ + function updateAssertionData(uint256 _newData) external onlyOwner { + _updateAssertionData(_newData); } + /** + @notice Updates the assertion data and makes a request to Uma V3 for a market + @dev Updated data will be used for assertion then a callback will be received after LIVENESS_PERIOD + @param _newData is the new data for the assertion + @param _marketId is the marketId for the market + */ function updateAssertionDataAndFetch( uint256 _newData, uint256 _marketId ) external onlyOwner returns (bytes32) { - updateAssertionData(_newData); - return fetchAssertion(_marketId); + _updateAssertionData(_newData); + return _fetchAssertion(_marketId); } + /** + @notice Toggles relayer status for an address + @param _relayer is the address to update + */ function updateRelayer(address _relayer) external onlyOwner { if (_relayer == address(0)) revert ZeroAddress(); bool relayerState = whitelistRelayer[_relayer]; @@ -146,6 +146,89 @@ contract UmaV3DynamicAssertionProvider is Ownable { emit RelayerUpdated(_relayer, relayerState); } + /** + @notice Withdraws the balance of the currency in the contract + @dev This is likely to be the bond value remaining in the contract + */ + function withdrawBond() external onlyOwner { + ERC20 bondCurrency = ERC20(currency); + uint256 balance = bondCurrency.balanceOf(address(this)); + bondCurrency.safeTransfer(msg.sender, balance); + emit BondWithdrawn(balance); + } + + /** + @notice Resets answer to 0 + @dev If the answer has expired then it can be reset - allowing checks to default to false + @param _marketId is the marketId for the market + */ + function resetAnswerAfterTimeout( + uint256[] memory _marketId + ) external onlyOwner { + for (uint i; i < _marketId.length; ) { + uint256 currentId = _marketId[i]; + MarketAnswer memory marketAnswer = marketIdToAnswer[currentId]; + + if ( + marketAnswer.answer == 1 && + (block.timestamp - marketAnswer.updatedAt) > timeOut + ) marketIdToAnswer[currentId].answer = 0; + + unchecked { + i++; + } + } + emit AnswersReset(_marketId); + } + + /*////////////////////////////////////////////////////////////// + EXTERNAL + //////////////////////////////////////////////////////////////*/ + /** + @notice Fetches the assertion from the UMA V3 Optimistic Oracle + @param _marketId is the marketId for the market + @return assertionId is the id for the assertion + */ + function fetchAssertion(uint256 _marketId) external returns (bytes32) { + return _fetchAssertion(_marketId); + } + + /** + @notice Fetch the assertion state of the market + @dev If asserition is active then will revert + @dev If assertion is true and timedOut then will revert + @param _marketId is the marketId for the market + @return bool If assertion is true or false for the market condition + */ + function checkAssertion( + uint256 _marketId + ) public view virtual returns (bool) { + MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; + + if (marketAnswer.activeAssertion == true) revert AssertionActive(); + if ( + marketAnswer.answer == 1 && + (block.timestamp - marketAnswer.updatedAt) > timeOut + ) revert PriceTimedOut(); + + if (marketAnswer.answer == 1) return true; + else return false; + } + + // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future + /** @notice Fetch price and return condition + * @param _marketId the marketId for the market + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256, + uint256 _marketId + ) public view virtual returns (bool, int256 price) { + bool _conditionMet = checkAssertion(_marketId); + return (_conditionMet, price); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -171,31 +254,57 @@ contract UmaV3DynamicAssertionProvider is Ownable { } /*////////////////////////////////////////////////////////////// - EXTERNAL + INTERNAL //////////////////////////////////////////////////////////////*/ + /** + @param _newData is the new data for the assertion + @dev updates the assertion data + */ + function _updateAssertionData(uint256 _newData) private { + if (_newData == 0) revert InvalidInput(); + assertionData = AssertionData({ + assertionData: uint128(_newData), + updatedAt: uint128(block.timestamp) + }); - function fetchAssertion( + emit AssertionDataUpdated(_newData); + } + + /** + @dev AssertionDataOutdated check ensures the data being asserted is up to date + @dev CooldownPending check ensures the cooldown period has passed since the last assertion + @param _marketId is the marketId for the market + */ + function _fetchAssertion( uint256 _marketId - ) public returns (bytes32 assertionId) { + ) private returns (bytes32 assertionId) { if (whitelistRelayer[msg.sender] == false) revert InvalidCaller(); MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; if (marketAnswer.activeAssertion == true) revert AssertionActive(); + if (block.timestamp - marketAnswer.updatedAt < ASSERTION_COOLDOWN) + revert CooldownPending(); if ((block.timestamp - assertionData.updatedAt) > timeOut) - revert AssertionDataEmpty(); + revert AssertionDataOutdated(); // Configure bond and claim information uint256 minimumBond = umaV3.getMinimumBond(address(currency)); uint256 reqBond = requiredBond; uint256 bond = reqBond > minimumBond ? reqBond : minimumBond; - bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); + bytes memory claim = _composeClaim( + marketIdToAssertionDescription[_marketId] + ); // Transfer bond from sender and request assertion - ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); - ERC20(currency).safeApprove(address(umaV3), bond); + ERC20 bondCurrency = ERC20(currency); + if (bondCurrency.balanceOf(address(this)) < bond) + bondCurrency.safeTransferFrom(msg.sender, address(this), bond); + bondCurrency.safeApprove(address(umaV3), bond); + + // Request assertion from UMA V3 assertionId = umaV3.assertTruth( claim, - msg.sender, // Asserter + address(this), // Asserter address(this), // Receive callback to this contract address(0), // No sovereign security ASSERTION_LIVENESS, @@ -206,64 +315,28 @@ contract UmaV3DynamicAssertionProvider is Ownable { ); assertionIdToMarket[assertionId] = _marketId; - marketIdToAnswer[_marketId].activeAssertion = true; - marketIdToAnswer[_marketId].assertionId = assertionId; + marketAnswer.activeAssertion = true; + marketAnswer.assertionId = assertionId; + if (marketAnswer.answer == 1) marketAnswer.answer = 0; + marketIdToAnswer[_marketId] = marketAnswer; emit MarketAsserted(_marketId, assertionId); } - /** @notice Fetch the assertion state of the market - * @return bool If assertion is true or false for the market condition - */ - function checkAssertion( - uint256 _marketId - ) public view virtual returns (bool) { - MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; - - if ((block.timestamp - marketAnswer.updatedAt) > timeOut) - revert PriceTimedOut(); - - if (marketAnswer.answer == 1) return true; - else return false; - } - - // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future - /** @notice Fetch price and return condition - * @param _marketId the marketId for the market - * @return boolean If condition is met i.e. strike > price - * @return price Current price for token - */ - function conditionMet( - uint256, - uint256 _marketId - ) public view virtual returns (bool, int256 price) { - uint256 conditionType = marketIdToConditionType[_marketId]; - bool condition = checkAssertion(_marketId); - - if (conditionType == 1) return (condition, price); - else if (conditionType == 2) return (condition, price); - else revert ConditionTypeNotSet(); - } - - /*////////////////////////////////////////////////////////////// - INTERNAL - //////////////////////////////////////////////////////////////*/ /** - @param _conditionType is the condition type for the market - @dev encode claim would look like: "As of assertion timestamp , " + @param _assertionDescription assertion description fetched with the marketId + @dev encode claim would look like: "As of assertion timestamp , " Where inputs could be: "As of assertion timestamp 1625097600, <0.997>" @return bytes for the claim */ function _composeClaim( - uint256 _conditionType + string memory _assertionDescription ) internal view returns (bytes memory) { return abi.encodePacked( "As of assertion timestamp ", _toUtf8BytesUint(block.timestamp), - ", the following statement is", - _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, - assertionDescription, + _assertionDescription, _toUtf8BytesUint(assertionData.assertionData) ); } @@ -301,11 +374,10 @@ contract UmaV3DynamicAssertionProvider is Ownable { error ZeroAddress(); error InvalidInput(); error PriceTimedOut(); - error ConditionTypeNotSet(); - error ConditionTypeSet(); error InvalidCaller(); error AssertionActive(); error AssertionInactive(); - error InvalidCallback(); - error AssertionDataEmpty(); + error AssertionDataOutdated(); + error CooldownPending(); + error DescriptionAlreadySet(); } diff --git a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol index 4ecaf5d1..f1eb8f51 100644 --- a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol @@ -126,6 +126,12 @@ contract UmaV3EventAssertionProvider is Ownable { emit RelayerUpdated(_relayer, relayerState); } + function withdrawBond() external onlyOwner { + ERC20 bondCurrency = ERC20(currency); + uint256 balance = bondCurrency.balanceOf(address(this)); + bondCurrency.safeTransfer(msg.sender, balance); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -169,7 +175,9 @@ contract UmaV3EventAssertionProvider is Ownable { bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); // Transfer bond from sender and request assertion - ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); + ERC20 bondCurrency = ERC20(currency); + if (bondCurrency.balanceOf(address(this)) < bond) + ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); ERC20(currency).safeApprove(address(umaV3), bond); assertionId = umaV3.assertTruth( claim, diff --git a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol index c4a5e461..20d6081e 100644 --- a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol @@ -118,6 +118,12 @@ contract UmaV3PriceAssertionProvider is Ownable { emit RelayerUpdated(_relayer, relayerState); } + function withdrawBond() external onlyOwner { + ERC20 bondCurrency = ERC20(currency); + uint256 balance = bondCurrency.balanceOf(address(this)); + bondCurrency.safeTransfer(msg.sender, balance); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -160,7 +166,9 @@ contract UmaV3PriceAssertionProvider is Ownable { bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); // Transfer bond from sender and request assertion - ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); + ERC20 bondCurrency = ERC20(currency); + if (bondCurrency.balanceOf(address(this)) < bond) + ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); ERC20(currency).safeApprove(address(umaV3), bond); assertionId = umaV3.assertTruth( claim, diff --git a/test/V2/Helper.sol b/test/V2/Helper.sol index abb5cf95..c50d978c 100644 --- a/test/V2/Helper.sol +++ b/test/V2/Helper.sol @@ -16,6 +16,7 @@ contract Helper is Test { event CoverageStartUpdated(uint256 startTime); event AssertionDataUpdated(uint256 newData); event RelayerUpdated(address relayer, bool state); + event DescriptionSet(uint256 marketId, string description); uint256 public constant STRIKE = 1000000000000000000; uint256 public constant COLLATERAL_MINUS_FEES = 21989999998398551453; diff --git a/test/V2/oracles/individual/PythPriceProvider.t.sol b/test/V2/oracles/individual/PythPriceProvider.t.sol index 9b1d62dd..8ebe382e 100644 --- a/test/V2/oracles/individual/PythPriceProvider.t.sol +++ b/test/V2/oracles/individual/PythPriceProvider.t.sol @@ -48,13 +48,8 @@ contract PythPriceProviderTest is Helper { // FUNCTIONS // //////////////////////////////////////////////// function testLatestRoundDataPyth() public { - ( - uint80 roundId, - int256 price, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) = pythProvider.latestRoundData(); + (, int256 price, , uint256 updatedAt, ) = pythProvider + .latestRoundData(); assertTrue(price != 0); assertTrue(updatedAt != 0); } diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index 9b6ba9c8..cb9e7832 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -77,16 +77,16 @@ contract UmaV2AssertionProviderTest is Helper { assertEq(umaV2AssertionProvider.reward(), reward); assertEq(umaV2AssertionProvider.coverageStart(), block.timestamp); - string - memory ancillaryDataHead = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: string"; - uint256 coverageStart = 1697498162; - string - memory ancillaryDataTail = ". P1: 0 for NO, P2: 1 for YES, P3: 2 for UNDETERMINED"; - bytes memory output = abi.encodePacked( - ancillaryDataHead, - coverageStart, - ancillaryDataTail - ); + // string + // memory ancillaryDataHead = "q: Aave USDC.e pool (address: 0x625E7708f30cA75bfd92586e17077590C60eb4cD) on Arbitrum One was hacked or compromised leading to locked funds or >25% loss in TVL value after the timestamp of: string"; + // uint256 coverageStart = 1697498162; + // string + // memory ancillaryDataTail = ". P1: 0 for NO, P2: 1 for YES, P3: 2 for UNDETERMINED"; + // bytes memory output = abi.encodePacked( + // ancillaryDataHead, + // coverageStart, + // ancillaryDataTail + // ); } //////////////////////////////////////////////// diff --git a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol index aea89ee1..9fbe60be 100644 --- a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol @@ -14,6 +14,9 @@ import { } from "../mocks/MockOracles.sol"; import {MockUma} from "../mocks/MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; +import { + IOptimisticOracleV3 +} from "../../../../src/v2/interfaces/IOptimisticOracleV3.sol"; contract UmaV3DynamicAssertionProviderTest is Helper { uint256 public arbForkId; @@ -25,60 +28,49 @@ contract UmaV3DynamicAssertionProviderTest is Helper { //////////////////////////////////////////////// // HELPERS // //////////////////////////////////////////////// - uint256 public UMA_DECIMALS = 18; - address public UMA_OO_V3 = address(0x123); - string public UMA_DESCRIPTION = "USDC"; - uint256 public REQUIRED_BOND = 1e6; - bytes32 public defaultIdentifier = bytes32("abc"); - bytes public assertionDescription; + uint256 public constant UMA_DECIMALS = 18; + address public constant UMA_OO_V3 = + 0xa6147867264374F324524E30C02C331cF28aa879; + string public constant UMA_DESCRIPTION = "USDC"; + uint256 public constant REQUIRED_BOND = 1e6; function setUp() public { arbForkId = vm.createFork(ARBITRUM_RPC_URL); vm.selectFork(arbForkId); wethAsset = ERC20(WETH_ADDRESS); - // TODO: Should this be encoded or encode packed? - assertionDescription = abi.encode( - "USDC/USD exchange rate is above 0.997" - ); - address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - uint256 condition = 2; - umaPriceProvider.setConditionType(marketId, condition); umaPriceProvider.updateRelayer(address(this)); } //////////////////////////////////////////////// // STATE // //////////////////////////////////////////////// - function testUmaCreation() public { + function testUmaCreationDynamic() public { assertEq(umaPriceProvider.ASSERTION_LIVENESS(), 7200); assertEq(umaPriceProvider.currency(), WETH_ADDRESS); - assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); + assertEq( + umaPriceProvider.defaultIdentifier(), + IOptimisticOracleV3(UMA_OO_V3).defaultIdentifier() + ); assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); assertEq(umaPriceProvider.requiredBond(), REQUIRED_BOND); assertEq(umaPriceProvider.timeOut(), TIME_OUT); - assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); - assertEq(umaPriceProvider.assertionDescription(), assertionDescription); (uint256 assertionData, uint256 updatedAt) = umaPriceProvider .assertionData(); assertEq(assertionData, 0); assertEq(updatedAt, block.timestamp); - assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); assertEq(umaPriceProvider.whitelistRelayer(address(this)), true); } @@ -91,6 +83,22 @@ contract UmaV3DynamicAssertionProviderTest is Helper { assertEq(umaPriceProvider.requiredBond(), newBond); } + function testSetAssertionDescription() public { + string memory newDescription = " USDC/USD exchange rate is above"; + + vm.expectEmit(true, true, false, false); + emit DescriptionSet(marketId, newDescription); + umaPriceProvider.setAssertionDescription(marketId, newDescription); + assertEq( + keccak256( + abi.encodePacked( + umaPriceProvider.marketIdToAssertionDescription(marketId) + ) + ), + keccak256(abi.encodePacked(newDescription)) + ); + } + function testUpdateAssertionData() public { uint256 newData = 1e6; @@ -106,18 +114,16 @@ contract UmaV3DynamicAssertionProviderTest is Helper { function testUpdateAssertionDataAndFetch() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3PriceAssertionProvider + // Deploying new UmaV3DynamicAssertionProvider umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 1); + string memory newDescription = " USDC/USD exchange rate is above"; + umaPriceProvider.setAssertionDescription(marketId, newDescription); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo @@ -162,24 +168,57 @@ contract UmaV3DynamicAssertionProviderTest is Helper { assertEq(umaPriceProvider.whitelistRelayer(newRelayer), false); } + function testWithdrawBond() public { + uint256 bondAmount = 1e18; + deal(WETH_ADDRESS, address(umaPriceProvider), bondAmount); + ERC20 bondAsset = ERC20(WETH_ADDRESS); + + assertEq(bondAsset.balanceOf(address(umaPriceProvider)), bondAmount); + assertEq(bondAsset.balanceOf(address(this)), 0); + + umaPriceProvider.withdrawBond(); + assertEq(bondAsset.balanceOf(address(umaPriceProvider)), 0); + assertEq(bondAsset.balanceOf(address(this)), bondAmount); + } + + function testResetMarketAnswerAfterTimeout() public { + // Setting truthy state and checking is truth + uint256 customMarketId = 3; + MockUma mockUma = _stagingTruthyAssertion(); + _stagingTruthyAssertionCustom(customMarketId, mockUma); + + // Checking both markets state set to true + assertEq(umaPriceProvider.checkAssertion(marketId), true); + assertEq(umaPriceProvider.checkAssertion(customMarketId), true); + + // Moving timer and resetting answer as timeOut has passed + uint256[] memory _markets = new uint256[](2); + _markets[0] = marketId; + _markets[1] = customMarketId; + vm.warp(block.timestamp + umaPriceProvider.timeOut() + 1); + umaPriceProvider.resetAnswerAfterTimeout(_markets); + + // Checking both markets answer is now zero + assertEq(umaPriceProvider.checkAssertion(marketId), false); + assertEq(umaPriceProvider.checkAssertion(customMarketId), false); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// - function testConditionOneMetUma() public { + function testConditionMetUmaDynamic() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3PriceAssertionProvider + // Deploying new UmaV3DynamicAssertionProvider umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 1); + string memory newDescription = " USDC/USD exchange rate is above"; + umaPriceProvider.setAssertionDescription(marketId, newDescription); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo @@ -231,6 +270,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { assertEq(updatedAt, uint128(block.timestamp)); assertEq(answer, 1); + // Checking condition met is true and price is 0 (bool condition, int256 price) = umaPriceProvider.conditionMet( 2 ether, marketId @@ -239,106 +279,39 @@ contract UmaV3DynamicAssertionProviderTest is Helper { assertEq(condition, true); } - function testConditionTwoMetUma() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 2); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); - - // Moving forward so the constructor data is invalid - vm.warp(block.timestamp + 2 days); - uint256 _newData = 4e6; - umaPriceProvider.updateAssertionData(_newData); - - // Querying for assertion - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); - mockUma.assertionResolvedCallback( - address(umaPriceProvider), - _assertionId, - true - ); - - (bool condition, int256 price) = umaPriceProvider.conditionMet( - 2 ether, - marketId - ); - assertTrue(price == 0); + function testCheckAssertionTrue() public { + _stagingTruthyAssertion(); + bool condition = umaPriceProvider.checkAssertion(marketId); assertEq(condition, true); } - function testCheckAssertionTrue() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 2); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); - - // Moving forward so the constructor data is invalid + function testCheckAssertionFalse() public { vm.warp(block.timestamp + 2 days); - uint256 _newData = 4e6; - umaPriceProvider.updateAssertionData(_newData); - - // Querying for assertion - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); - mockUma.assertionResolvedCallback( - address(umaPriceProvider), - _assertionId, - true - ); - bool condition = umaPriceProvider.checkAssertion(marketId); - assertEq(condition, true); + assertEq(condition, false); } - function testCheckAssertionFalse() public { + function testFetchAssertionContractHasBalance() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3PriceAssertionProvider + // Deploying new UmaV3DynamicAssertionProvider umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 2); + string memory newDescription = " USDC/USD exchange rate is above"; + umaPriceProvider.setAssertionDescription(marketId, newDescription); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); + deal(WETH_ADDRESS, address(umaPriceProvider), 1e18); + uint256 umaProviderBal = wethAsset.balanceOf(address(umaPriceProvider)); + uint256 senderBal = wethAsset.balanceOf(address(this)); + assertEq(umaProviderBal, 1e18); + assertEq(senderBal, 0); // Moving forward so the constructor data is invalid vm.warp(block.timestamp + 2 days); @@ -346,15 +319,14 @@ contract UmaV3DynamicAssertionProviderTest is Helper { umaPriceProvider.updateAssertionData(_newData); // Querying for assertion - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); - mockUma.assertionResolvedCallback( - address(umaPriceProvider), - _assertionId, - false - ); + umaPriceProvider.fetchAssertion(marketId); - bool condition = umaPriceProvider.checkAssertion(marketId); - assertEq(condition, false); + // Checking umaPriceProvide balance declined + assertEq( + wethAsset.balanceOf(address(umaPriceProvider)), + umaProviderBal - REQUIRED_BOND + ); + assertEq(wethAsset.balanceOf(address(this)), 0); } //////////////////////////////////////////////// @@ -363,116 +335,50 @@ contract UmaV3DynamicAssertionProviderTest is Helper { function testRevertConstructorInputsUma() public { vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); new UmaV3DynamicAssertionProvider( - 0, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, string(""), TIME_OUT, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, 0, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, address(0), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - bytes32(""), - WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, address(0), - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - bytes(""), REQUIRED_BOND ); vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, 0 ); } - function testRevertConditionTypeSetUma() public { - vm.expectRevert( - UmaV3DynamicAssertionProvider.ConditionTypeSet.selector - ); - umaPriceProvider.setConditionType(2, 0); - } - - function testRevertInvalidInputConditionUma() public { - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - umaPriceProvider.setConditionType(0, 0); - - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - umaPriceProvider.setConditionType(0, 3); - } - function testRevertInvalidInputRequiredBond() public { vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); umaPriceProvider.updateRequiredBond(0); @@ -488,6 +394,16 @@ contract UmaV3DynamicAssertionProviderTest is Helper { umaPriceProvider.updateAssertionData(0); } + function testRevertSetAssertionDescription() public { + string memory newDescription = " USDC/USD exchange rate is above"; + umaPriceProvider.setAssertionDescription(marketId, newDescription); + + vm.expectRevert( + UmaV3DynamicAssertionProvider.DescriptionAlreadySet.selector + ); + umaPriceProvider.setAssertionDescription(marketId, string("")); + } + function testRevertInvalidCallerCallback() public { vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); @@ -505,21 +421,17 @@ contract UmaV3DynamicAssertionProviderTest is Helper { ); } - function testRevertAssertionActive() public { + function testRevertCheckAssertionActive() public { MockUma mockUma = new MockUma(); // Deploying new UmaV3DynamicAssertionProvider umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 1); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo @@ -531,6 +443,28 @@ contract UmaV3DynamicAssertionProviderTest is Helper { umaPriceProvider.fetchAssertion(marketId); } + function testRevertFetchAssertionActive() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3DynamicAssertionProvider + umaPriceProvider = new UmaV3DynamicAssertionProvider( + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + WETH_ADDRESS, + REQUIRED_BOND + ); + umaPriceProvider.updateRelayer(address(this)); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + umaPriceProvider.fetchAssertion(marketId); + + vm.expectRevert(UmaV3DynamicAssertionProvider.AssertionActive.selector); + umaPriceProvider.checkAssertion(marketId); + } + function testRevertFetchAssertionInvalidCaller() public { vm.startPrank(address(0x123)); vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidCaller.selector); @@ -538,26 +472,86 @@ contract UmaV3DynamicAssertionProviderTest is Helper { vm.stopPrank(); } - function testRevertAssertionDataEmpty() public { + function testRevertAssertionDataOutdated() public { vm.warp(block.timestamp + 2 days); vm.expectRevert( - UmaV3DynamicAssertionProvider.AssertionDataEmpty.selector + UmaV3DynamicAssertionProvider.AssertionDataOutdated.selector ); umaPriceProvider.fetchAssertion(marketId); } - function testRevertTimeOutUma() public { + function testRevertCooldownPending() public { + _stagingTruthyAssertion(); + + vm.expectRevert(UmaV3DynamicAssertionProvider.CooldownPending.selector); + umaPriceProvider.fetchAssertion(marketId); + } + + function testRevertPriceTimeOutUma() public { + _stagingTruthyAssertion(); + + vm.warp(block.timestamp + umaPriceProvider.timeOut() + 1); + vm.expectRevert(UmaV3DynamicAssertionProvider.PriceTimedOut.selector); + umaPriceProvider.checkAssertion(marketId); + } + + //////////////////////////////////////////////// + // STAGING // + //////////////////////////////////////////////// + function _stagingTruthyAssertion() internal returns (MockUma mockUma) { + mockUma = new MockUma(); + + // Deploying new UmaV3DynamicAssertionProvider umaPriceProvider = new UmaV3DynamicAssertionProvider( - UMA_DECIMALS, UMA_DESCRIPTION, TIME_OUT, - UMA_OO_V3, - defaultIdentifier, + address(mockUma), WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaV3DynamicAssertionProvider.PriceTimedOut.selector); - umaPriceProvider.checkAssertion(123); + string memory newDescription = " USDC/USD exchange rate is above"; + umaPriceProvider.setAssertionDescription(marketId, newDescription); + umaPriceProvider.updateRelayer(address(this)); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + // Moving forward so the constructor data is invalid + vm.warp(block.timestamp + 2 days); + uint256 _newData = 4e6; + umaPriceProvider.updateAssertionData(_newData); + + // Querying for assertion + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + } + + function _stagingTruthyAssertionCustom( + uint256 _marketId, + MockUma mockUma + ) internal { + string memory newDescription = " USDC/USD exchange rate is above"; + umaPriceProvider.setAssertionDescription(_marketId, newDescription); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + + // Moving forward so the constructor data is invalid + uint256 _newData = 4e6; + umaPriceProvider.updateAssertionData(_newData); + + // Querying for assertion + bytes32 _assertionId = umaPriceProvider.fetchAssertion(_marketId); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); } } diff --git a/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol index b8662510..abcb457d 100644 --- a/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol @@ -103,6 +103,19 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(umaPriceProvider.whitelistRelayer(newRelayer), false); } + function testWithdrawBond() public { + uint256 bondAmount = 1e18; + deal(WETH_ADDRESS, address(umaPriceProvider), bondAmount); + ERC20 bondAsset = ERC20(WETH_ADDRESS); + + assertEq(bondAsset.balanceOf(address(umaPriceProvider)), bondAmount); + assertEq(bondAsset.balanceOf(address(this)), 0); + + umaPriceProvider.withdrawBond(); + assertEq(bondAsset.balanceOf(address(umaPriceProvider)), 0); + assertEq(bondAsset.balanceOf(address(this)), bondAmount); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// @@ -272,6 +285,42 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(condition, false); } + function testFetchAssertionWithBalance() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3EventAssertionProvider + umaPriceProvider = new UmaV3EventAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(umaPriceProvider), 1e18); + uint256 umaProviderBal = wethAsset.balanceOf(address(umaPriceProvider)); + uint256 senderBal = wethAsset.balanceOf(address(this)); + assertEq(umaProviderBal, 1e18); + assertEq(senderBal, 0); + + // Querying for assertion + vm.warp(block.timestamp + 2 days); + umaPriceProvider.fetchAssertion(marketId); + + // Checking umaPriceProvide balance declined + assertEq( + wethAsset.balanceOf(address(umaPriceProvider)), + umaProviderBal - REQUIRED_BOND + ); + assertEq(wethAsset.balanceOf(address(this)), 0); + } + //////////////////////////////////////////////// // REVERT CASES // //////////////////////////////////////////////// diff --git a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol index b60ad623..ecd49211 100644 --- a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol @@ -100,6 +100,19 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(umaPriceProvider.whitelistRelayer(newRelayer), false); } + function testWithdrawBond() public { + uint256 bondAmount = 1e18; + deal(WETH_ADDRESS, address(umaPriceProvider), bondAmount); + ERC20 bondAsset = ERC20(WETH_ADDRESS); + + assertEq(bondAsset.balanceOf(address(umaPriceProvider)), bondAmount); + assertEq(bondAsset.balanceOf(address(this)), 0); + + umaPriceProvider.withdrawBond(); + assertEq(bondAsset.balanceOf(address(umaPriceProvider)), 0); + assertEq(bondAsset.balanceOf(address(this)), bondAmount); + } + //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// @@ -269,6 +282,42 @@ contract UmaV3EventAssertionProviderTest is Helper { assertEq(condition, false); } + function testFetchAssertionWithBalance() public { + MockUma mockUma = new MockUma(); + + // Deploying new UmaV3PriceAssertionProvider + umaPriceProvider = new UmaV3PriceAssertionProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + TIME_OUT, + address(mockUma), + defaultIdentifier, + WETH_ADDRESS, + assertionDescription, + REQUIRED_BOND + ); + umaPriceProvider.setConditionType(marketId, 2); + umaPriceProvider.updateRelayer(address(this)); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(umaPriceProvider), 1e18); + uint256 umaProviderBal = wethAsset.balanceOf(address(umaPriceProvider)); + uint256 senderBal = wethAsset.balanceOf(address(this)); + assertEq(umaProviderBal, 1e18); + assertEq(senderBal, 0); + + // Querying for assertion + vm.warp(block.timestamp + 2 days); + umaPriceProvider.fetchAssertion(marketId); + + // Checking umaPriceProvide balance declined + assertEq( + wethAsset.balanceOf(address(umaPriceProvider)), + umaProviderBal - REQUIRED_BOND + ); + assertEq(wethAsset.balanceOf(address(this)), 0); + } + //////////////////////////////////////////////// // REVERT CASES // //////////////////////////////////////////////// diff --git a/test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol b/test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol index 4fcae9be..203aab37 100644 --- a/test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol +++ b/test/V2/oracles/mocks/MockRedstoneCoreUniversalProvider.sol @@ -19,7 +19,7 @@ contract MockRedstoneCoreUniversalProvider is RedstoneCoreUniversalProvider { } function getAuthorisedSignerIndex( - address signerAddress + address ) public view virtual override returns (uint8) { // authorize everyone return 0; diff --git a/test/V2/oracles/mocks/MockUma.sol b/test/V2/oracles/mocks/MockUma.sol index d7d82385..ad8fad44 100644 --- a/test/V2/oracles/mocks/MockUma.sol +++ b/test/V2/oracles/mocks/MockUma.sol @@ -22,6 +22,15 @@ contract MockUma { ); } + function defaultIdentifier() external pure returns (bytes32) { + return + bytes32( + abi.encode( + 0x4153534552545f54525554480000000000000000000000000000000000000000 + ) + ); + } + function assertTruth( bytes calldata claim, address asserter, @@ -53,13 +62,13 @@ contract MockUma { address callBackAddress, address sovereignSecurity, uint64 assertionLiveness, - bytes32 defaultIdentifier, + bytes32 _defaultIdentifier, bytes32 domain ) internal pure { asserter = callBackAddress; sovereignSecurity = callBackAddress; assertionLiveness += 1; - defaultIdentifier = domain; + _defaultIdentifier = domain; domain = keccak256(claim); } From 42f355999069a8ed1808e41373c84779b0272d1e Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 21 Dec 2023 12:38:45 -0500 Subject: [PATCH 24/25] fix: additional checks and bond logic --- script/v2/V2DeployContracts.s.sol | 8 +- .../individual/RedstonePriceProvider.sol | 1 - .../individual/UmaV2AssertionProvider.sol | 73 ++++++------ .../oracles/individual/UmaV2PriceProvider.sol | 75 ++++++------ .../individual/UmaV2AssertionProvider.t.sol | 112 +++++++++--------- .../individual/UmaV2PriceProvider.t.sol | 110 ++++++++++------- 6 files changed, 205 insertions(+), 174 deletions(-) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index 65e1dda1..56d00f25 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -164,7 +164,6 @@ contract V2DeployContracts is Script, HelperV2 { // ); // uint256 umaDecimals = 18; - address umaOOV3 = 0xa6147867264374F324524E30C02C331cF28aa879; // string memory umaDescription = "USDC"; // uint256 requiredBond = 1e6; // bytes32 defaultIdentifier = bytes32("abc"); @@ -182,9 +181,10 @@ contract V2DeployContracts is Script, HelperV2 { // requiredBond // ); + address umaOOV3 = 0xa6147867264374F324524E30C02C331cF28aa879; string memory marketDescription = "ETH Volatility"; - uint256 requiredBond = 1e6; - address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS + uint256 requiredBond = 501e6; + address currency = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; // USDC.e UmaV3DynamicAssertionProvider umaV3Provider = new UmaV3DynamicAssertionProvider( marketDescription, timeOut, @@ -249,7 +249,7 @@ contract V2DeployContracts is Script, HelperV2 { // "Uma V2 Assertion Provider", // address(umaV2AssertionProvider) // ); - // console2.log("Uma V3 Provider", address(umaV3Provider)); + console2.log("Uma V3 Provider", address(umaV3Provider)); // console2.log("resolveKeeper address", address(resolveKeeper)); // console2.log("resolveKeeperGenericController address", address(resolveKeeperGenericController)); diff --git a/src/v2/oracles/individual/RedstonePriceProvider.sol b/src/v2/oracles/individual/RedstonePriceProvider.sol index 35008c4e..09decaa2 100644 --- a/src/v2/oracles/individual/RedstonePriceProvider.sol +++ b/src/v2/oracles/individual/RedstonePriceProvider.sol @@ -125,5 +125,4 @@ contract RedstonePriceProvider is Ownable, IConditionProvider { error RoundIdOutdated(); error PriceTimedOut(); error ConditionTypeNotSet(); - error ConditionTypeSet(); } diff --git a/src/v2/oracles/individual/UmaV2AssertionProvider.sol b/src/v2/oracles/individual/UmaV2AssertionProvider.sol index f5f40822..cd65d856 100644 --- a/src/v2/oracles/individual/UmaV2AssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV2AssertionProvider.sol @@ -20,12 +20,13 @@ contract UmaV2AssertionProvider is Ownable { uint8 roundId; uint8 answeredInRound; int8 assertion; - uint128 pendingRequestAt; - uint256 updatedAt; + uint128 updatedAt; + bool activeAssertion; } - uint256 public constant REQUEST_TIMEOUT = 3600 * 3; - uint256 public constant ORACLE_LIVENESS_TIME = 3600 * 2; + uint256 public constant REQUEST_TIMEOUT = 3600 * 2; + uint256 public constant COOLDOWN_TIME = 600; + uint256 public constant ORACLE_LIVENESS_TIME = 3600; bytes32 public constant PRICE_IDENTIFIER = "YES_OR_NO_QUERY"; string public constant ANCILLARY_TAIL = ". P1: 0 for NO, P2: 1 for YES, P3: 2 for UNDETERMINED"; @@ -41,13 +42,11 @@ contract UmaV2AssertionProvider is Ownable { string public description; string public ancillaryData; - mapping(uint256 => uint256) public marketIdToConditionType; - - event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event CoverageStartUpdated(uint256 startTime); event RewardUpdated(uint256 newReward); event PriceSettled(int256 price); event PriceRequested(); + event BondWithdrawn(uint256 amount); constructor( uint256 _assertionTimeOut, @@ -80,16 +79,6 @@ contract UmaV2AssertionProvider is Ownable { /*////////////////////////////////////////////////////////////// ADMIN //////////////////////////////////////////////////////////////*/ - function setConditionType( - uint256 _marketId, - uint256 _condition - ) external onlyOwner { - if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); - if (_condition != 1 && _condition != 2) revert InvalidInput(); - marketIdToConditionType[_marketId] = _condition; - emit MarketConditionSet(_marketId, _condition); - } - function updateCoverageStart(uint128 _coverageStart) external onlyOwner { if (_coverageStart < coverageStart) revert InvalidInput(); coverageStart = _coverageStart; @@ -102,6 +91,16 @@ contract UmaV2AssertionProvider is Ownable { emit RewardUpdated(newReward); } + /** + @notice Withdraws the balance of the currency in the contract + @dev This is likely to be the bond value remaining in the contract + */ + function withdrawBond() external onlyOwner { + uint256 balance = currency.balanceOf(address(this)); + currency.transfer(msg.sender, balance); + emit BondWithdrawn(balance); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -118,7 +117,7 @@ contract UmaV2AssertionProvider is Ownable { _answer.assertion = int8(_price); _answer.roundId = answer.roundId + 1; _answer.answeredInRound = answer.answeredInRound + 1; - _answer.pendingRequestAt = answer.pendingRequestAt; + _answer.activeAssertion = false; answer = _answer; emit PriceSettled(_price); @@ -128,22 +127,27 @@ contract UmaV2AssertionProvider is Ownable { PUBLIC //////////////////////////////////////////////////////////////*/ function requestLatestAssertion() external { - if (answer.pendingRequestAt + REQUEST_TIMEOUT > block.timestamp) - revert RequestInProgress(); + if (answer.activeAssertion) revert RequestInProgress(); + if (answer.updatedAt + COOLDOWN_TIME > block.timestamp) + revert CooldownPeriod(); bytes memory _bytesAncillary = abi.encodePacked( ancillaryData, _toUtf8BytesUint(coverageStart), ANCILLARY_TAIL ); - currency.transferFrom(msg.sender, address(this), reward); - currency.approve(address(oo), reward); + uint256 _reward = reward; + + if (currency.balanceOf(address(this)) < _reward) + currency.transferFrom(msg.sender, address(this), _reward); + currency.approve(address(oo), _reward); + oo.requestPrice( PRICE_IDENTIFIER, block.timestamp, _bytesAncillary, currency, - reward + _reward ); oo.setCustomLiveness( PRICE_IDENTIFIER, @@ -160,7 +164,7 @@ contract UmaV2AssertionProvider is Ownable { true ); - answer.pendingRequestAt = uint128(block.timestamp); + answer.activeAssertion = true; emit PriceRequested(); } @@ -171,29 +175,25 @@ contract UmaV2AssertionProvider is Ownable { function checkAssertion() public view virtual returns (bool) { AssertionAnswer memory assertionAnswer = answer; - if (assertionAnswer.updatedAt == 0) revert OraclePriceZero(); - if ((block.timestamp - assertionAnswer.updatedAt) > assertionTimeOut) - revert PriceTimedOut(); - + if (assertionAnswer.activeAssertion) revert RequestInProgress(); if (assertionAnswer.assertion == 1) return true; else return false; } /** @notice Fetch price and return condition - * @param _marketId Market id + * @param _strike The strike price * @return boolean If condition is met i.e. strike > price * @return price Current price for token */ function conditionMet( - uint256, - uint256 _marketId + uint256 _strike, + uint256 ) public view virtual returns (bool, int256 price) { - uint256 conditionType = marketIdToConditionType[_marketId]; + uint256 conditionType = _strike % 2 ** 1; bool condition = checkAssertion(); if (conditionType == 1) return (condition, price); - else if (conditionType == 2) return (condition, price); - else revert ConditionTypeNotSet(); + else return (condition, price); } /*////////////////////////////////////////////////////////////// @@ -229,12 +229,9 @@ contract UmaV2AssertionProvider is Ownable { /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ - error OraclePriceZero(); error ZeroAddress(); - error PriceTimedOut(); error InvalidInput(); - error ConditionTypeNotSet(); - error ConditionTypeSet(); error InvalidCaller(); error RequestInProgress(); + error CooldownPeriod(); } diff --git a/src/v2/oracles/individual/UmaV2PriceProvider.sol b/src/v2/oracles/individual/UmaV2PriceProvider.sol index 2aa19f3b..cb3cd562 100644 --- a/src/v2/oracles/individual/UmaV2PriceProvider.sol +++ b/src/v2/oracles/individual/UmaV2PriceProvider.sol @@ -23,12 +23,13 @@ contract UmaV2PriceProvider is Ownable { uint128 answeredInRound; int128 price; uint128 updatedAt; - uint256 pendingRequestAt; + bool activeAssertion; } - uint256 public constant ORACLE_LIVENESS_TIME = 3600 * 2; + uint256 public constant REQUEST_TIMEOUT = 3600 * 2; + uint256 public constant COOLDOWN_TIME = 600; + uint256 public constant ORACLE_LIVENESS_TIME = 3600; bytes32 public constant PRICE_IDENTIFIER = "TOKEN_PRICE"; - uint256 public constant REQUEST_TIMEOUT = 3600 * 3; uint256 public immutable timeOut; IUmaV2 public immutable oo; @@ -41,12 +42,10 @@ contract UmaV2PriceProvider is Ownable { string public description; string public ancillaryData; - mapping(uint256 => uint256) public marketIdToConditionType; - - event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event RewardUpdated(uint256 newReward); event PriceSettled(int256 price); event PriceRequested(); + event BondWithdrawn(uint256 amount); constructor( uint256 _timeOut, @@ -80,22 +79,22 @@ contract UmaV2PriceProvider is Ownable { /*////////////////////////////////////////////////////////////// ADMIN //////////////////////////////////////////////////////////////*/ - function setConditionType( - uint256 _marketId, - uint256 _condition - ) external onlyOwner { - if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); - if (_condition != 1 && _condition != 2) revert InvalidInput(); - marketIdToConditionType[_marketId] = _condition; - emit MarketConditionSet(_marketId, _condition); - } - function updateReward(uint256 newReward) external onlyOwner { if (newReward == 0) revert InvalidInput(); reward = newReward; emit RewardUpdated(newReward); } + /** + @notice Withdraws the balance of the currency in the contract + @dev This is likely to be the bond value remaining in the contract + */ + function withdrawBond() external onlyOwner { + uint256 balance = currency.balanceOf(address(this)); + currency.transfer(msg.sender, balance); + emit BondWithdrawn(balance); + } + /*////////////////////////////////////////////////////////////// CALLBACK //////////////////////////////////////////////////////////////*/ @@ -112,7 +111,7 @@ contract UmaV2PriceProvider is Ownable { _answer.price = int128(_price); _answer.roundId = answer.roundId + 1; _answer.answeredInRound = answer.answeredInRound + 1; - _answer.pendingRequestAt = answer.pendingRequestAt; + _answer.activeAssertion = false; answer = _answer; emit PriceSettled(_price); @@ -122,18 +121,22 @@ contract UmaV2PriceProvider is Ownable { PUBLIC //////////////////////////////////////////////////////////////*/ function requestLatestPrice() external { - if (answer.pendingRequestAt + REQUEST_TIMEOUT > block.timestamp) - revert RequestInProgress(); - + if (answer.activeAssertion) revert RequestInProgress(); + if (answer.updatedAt + COOLDOWN_TIME > block.timestamp) + revert CooldownPeriod(); bytes memory _bytesAncillary = abi.encodePacked(ancillaryData); - currency.transferFrom(msg.sender, address(this), reward); - currency.approve(address(oo), reward); + uint256 _reward = reward; + + if (currency.balanceOf(address(this)) < _reward) + currency.transferFrom(msg.sender, address(this), _reward); + currency.approve(address(oo), _reward); + oo.requestPrice( PRICE_IDENTIFIER, block.timestamp, _bytesAncillary, currency, - reward + _reward ); oo.setCustomLiveness( PRICE_IDENTIFIER, @@ -150,7 +153,7 @@ contract UmaV2PriceProvider is Ownable { true ); - answer.pendingRequestAt = block.timestamp; + answer.activeAssertion = true; emit PriceRequested(); } @@ -161,7 +164,7 @@ contract UmaV2PriceProvider is Ownable { returns ( uint80 roundId, int256 price, - uint256 pendingRequestAt, + bool activeAssertion, uint256 updatedAt, uint80 answeredInRound ) @@ -170,7 +173,7 @@ contract UmaV2PriceProvider is Ownable { roundId = uint80(_answer.roundId); price = _answer.price; updatedAt = _answer.updatedAt; - pendingRequestAt = _answer.pendingRequestAt; + activeAssertion = _answer.activeAssertion; answeredInRound = uint80(_answer.answeredInRound); } @@ -178,8 +181,15 @@ contract UmaV2PriceProvider is Ownable { * @return int256 Current token price */ function getLatestPrice() public view returns (int256) { - (, int256 price, , uint256 updatedAt, ) = latestRoundData(); + ( + , + int256 price, + bool activeAssertion, + uint256 updatedAt, + + ) = latestRoundData(); if (price <= 0) revert OraclePriceZero(); + if (activeAssertion) revert RequestInProgress(); if ((block.timestamp - updatedAt) > timeOut) revert PriceTimedOut(); if (decimals < 18) { uint256 calcDecimals = 10 ** (18 - (decimals)); @@ -193,19 +203,17 @@ contract UmaV2PriceProvider is Ownable { /** @notice Fetch price and return condition * @param _strike Strike price - * @param _marketId Market id * @return boolean If condition is met i.e. strike > price * @return price Current price for token */ function conditionMet( uint256 _strike, - uint256 _marketId + uint256 ) public view virtual returns (bool, int256 price) { - uint256 conditionType = marketIdToConditionType[_marketId]; + uint256 conditionType = _strike % 2 ** 1; price = getLatestPrice(); if (conditionType == 1) return (int256(_strike) < price, price); - else if (conditionType == 2) return (int256(_strike) > price, price); - else revert ConditionTypeNotSet(); + else return (int256(_strike) > price, price); } /*////////////////////////////////////////////////////////////// @@ -215,8 +223,7 @@ contract UmaV2PriceProvider is Ownable { error ZeroAddress(); error PriceTimedOut(); error InvalidInput(); - error ConditionTypeNotSet(); - error ConditionTypeSet(); error InvalidCaller(); error RequestInProgress(); + error CooldownPeriod(); } diff --git a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol index cb9e7832..7c6907b8 100644 --- a/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2AssertionProvider.t.sol @@ -56,8 +56,6 @@ contract UmaV2AssertionProviderTest is Helper { ancillaryData, reward ); - uint256 condition = 2; - umaV2AssertionProvider.setConditionType(marketId, condition); deal(USDC_TOKEN, address(this), 1000e6); IERC20(USDC_TOKEN).approve(address(umaV2AssertionProvider), 1000e6); } @@ -66,7 +64,7 @@ contract UmaV2AssertionProviderTest is Helper { // STATE // //////////////////////////////////////////////// function testUmaV2AssertionProvider() public { - assertEq(umaV2AssertionProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); + assertEq(umaV2AssertionProvider.ORACLE_LIVENESS_TIME(), 3600); assertEq(umaV2AssertionProvider.PRICE_IDENTIFIER(), "YES_OR_NO_QUERY"); assertEq(umaV2AssertionProvider.assertionTimeOut(), TIME_OUT); assertEq(address(umaV2AssertionProvider.oo()), umaV2); @@ -92,17 +90,6 @@ contract UmaV2AssertionProviderTest is Helper { //////////////////////////////////////////////// // ADMIN // //////////////////////////////////////////////// - function testSetConditionTypeUmaV2Assert() public { - uint256 _marketId = 911; - uint256 _condition = 1; - - vm.expectEmit(true, true, true, true); - emit MarketConditionSet(_marketId, _condition); - umaV2AssertionProvider.setConditionType(_marketId, _condition); - - assertEq(umaV2AssertionProvider.marketIdToConditionType(_marketId), 1); - } - function testUpdateCoverageStartUmaV2Assert() public { uint128 newCoverageStart = uint128(block.timestamp + 1 days); vm.expectEmit(true, true, true, true); @@ -121,6 +108,27 @@ contract UmaV2AssertionProviderTest is Helper { assertEq(umaV2AssertionProvider.reward(), newReward); } + function testWithdrawBondUmaV2Assert() public { + // Fetching balance before + IERC20 bondAsset = IERC20(USDC_TOKEN); + uint256 balance = bondAsset.balanceOf(address(this)); + + // Sending funds + uint256 bondAmount = 1e18; + deal(USDC_TOKEN, address(umaV2AssertionProvider), bondAmount); + + // Checking contract balance updated + assertEq( + bondAsset.balanceOf(address(umaV2AssertionProvider)), + bondAmount + ); + assertEq(bondAsset.balanceOf(address(this)), balance); + + umaV2AssertionProvider.withdrawBond(); + assertEq(bondAsset.balanceOf(address(umaV2AssertionProvider)), 0); + assertEq(bondAsset.balanceOf(address(this)), balance + bondAmount); + } + //////////////////////////////////////////////// // PUBLIC CALLBACK // //////////////////////////////////////////////// @@ -143,7 +151,6 @@ contract UmaV2AssertionProviderTest is Helper { IERC20(USDC_TOKEN).approve(address(umaV2AssertionProvider), 1000e6); // Configuring the pending answer - uint256 previousTimestamp = block.timestamp; umaV2AssertionProvider.requestLatestAssertion(); vm.warp(block.timestamp + 1 days); @@ -159,14 +166,14 @@ contract UmaV2AssertionProviderTest is Helper { uint8 roundId, uint8 answeredInRound, int8 _price, - uint256 pendingRequestAt, - uint256 updatedAt + uint256 updatedAt, + bool activeAssertion ) = umaV2AssertionProvider.answer(); // Checking the data assertEq(roundId, 1); assertEq(_price, price); - assertEq(pendingRequestAt, previousTimestamp); + assertEq(activeAssertion, false); assertEq(updatedAt, block.timestamp); assertEq(answeredInRound, 1); } @@ -175,9 +182,26 @@ contract UmaV2AssertionProviderTest is Helper { // PUBLIC FUNCTIONS // //////////////////////////////////////////////// function testrequestLatestAssertionUmaV2Assert() public { + uint256 balance = IERC20(USDC_TOKEN).balanceOf(address(this)); + assertEq(balance, 1000e6); + umaV2AssertionProvider.requestLatestAssertion(); - (, , , uint128 pendingRequestAt, ) = umaV2AssertionProvider.answer(); - assertEq(pendingRequestAt, block.timestamp); + (, , , , bool activeAssertion) = umaV2AssertionProvider.answer(); + assertEq(activeAssertion, true); + + assertEq(IERC20(USDC_TOKEN).balanceOf(address(this)), balance - reward); + } + + function testrequestLatestAssertionNoTransferUmaV2Assert() public { + deal(USDC_TOKEN, address(umaV2AssertionProvider), 1000e6); + uint256 balance = IERC20(USDC_TOKEN).balanceOf(address(this)); + assertEq(balance, 1000e6); + + umaV2AssertionProvider.requestLatestAssertion(); + (, , , , bool activeAssertion) = umaV2AssertionProvider.answer(); + assertEq(activeAssertion, true); + + assertEq(IERC20(USDC_TOKEN).balanceOf(address(this)), balance); } function testCheckAssertionUmaV2Assert() public { @@ -192,11 +216,11 @@ contract UmaV2AssertionProviderTest is Helper { // Config the data with mock oracle _configureSettledPrice(true); - uint256 conditionType = 1; + // Condition type 1 + uint256 strikeMetPrice = 100000000000001; // 0.001 ether uint256 marketIdOne = 1; - umaV2AssertionProvider.setConditionType(marketIdOne, conditionType); (bool condition, ) = umaV2AssertionProvider.conditionMet( - 0.001 ether, + strikeMetPrice, marketIdOne ); @@ -207,8 +231,7 @@ contract UmaV2AssertionProviderTest is Helper { // Config the data with mock oracle _configureSettledPrice(false); - uint256 conditionType = 2; - umaV2AssertionProvider.setConditionType(marketId, conditionType); + // Condition type 2 (bool condition, ) = umaV2AssertionProvider.conditionMet( 2 ether, marketId @@ -221,8 +244,7 @@ contract UmaV2AssertionProviderTest is Helper { // Config the data with mock oracle address mockUmaV2 = _configureSettledPrice(true); - uint256 conditionType = 2; - umaV2AssertionProvider.setConditionType(marketId, conditionType); + // Condition type 2 (bool condition, int256 price) = umaV2AssertionProvider.conditionMet( 2 ether, marketId @@ -235,6 +257,7 @@ contract UmaV2AssertionProviderTest is Helper { assertEq(answeredInRound, 1); // Updating the price + vm.warp(block.timestamp + 601); _updatePrice(false, mockUmaV2); (condition, price) = umaV2AssertionProvider.conditionMet( 2 ether, @@ -312,16 +335,6 @@ contract UmaV2AssertionProviderTest is Helper { ); } - function testRevertConditionTypeSetUmaV2Assert() public { - vm.expectRevert(UmaV2AssertionProvider.ConditionTypeSet.selector); - umaV2AssertionProvider.setConditionType(2, 0); - } - - function testRevertInvalidInputConditionUmaV2Assert() public { - vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); - umaV2AssertionProvider.setConditionType(0, 0); - } - function testRevertInvalidInputCoverageStartUmaV2Assert() public { vm.expectRevert(UmaV2AssertionProvider.InvalidInput.selector); umaV2AssertionProvider.updateCoverageStart(0); @@ -348,25 +361,18 @@ contract UmaV2AssertionProviderTest is Helper { umaV2AssertionProvider.requestLatestAssertion(); } - function testRevertOraclePriceZeroCheckAssertionUmaV2Assert() public { - vm.expectRevert(UmaV2AssertionProvider.OraclePriceZero.selector); - umaV2AssertionProvider.checkAssertion(); - } - - function testRevertPriceTimedOutCheckAssertionUmaV2Assert() public { - // Config the data with mock oracle + function testRevertCooldownPeriodRequestLatestAssertionUmaV2Assert() + public + { _configureSettledPrice(true); - - vm.warp(block.timestamp + 2 days); - vm.expectRevert(UmaV2AssertionProvider.PriceTimedOut.selector); - umaV2AssertionProvider.checkAssertion(); + vm.expectRevert(UmaV2AssertionProvider.CooldownPeriod.selector); + umaV2AssertionProvider.requestLatestAssertion(); } - function testRevertConditionTypeNotSetUmaV2Assert() public { - _configureSettledPrice(true); - - vm.expectRevert(UmaV2AssertionProvider.ConditionTypeNotSet.selector); - umaV2AssertionProvider.conditionMet(0.001 ether, 1); + function testRevertRequestInProgCheckAssertion() public { + umaV2AssertionProvider.requestLatestAssertion(); + vm.expectRevert(UmaV2AssertionProvider.RequestInProgress.selector); + umaV2AssertionProvider.checkAssertion(); } //////////////////////////////////////////////// diff --git a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol index d8261f75..6666ef6f 100644 --- a/test/V2/oracles/individual/UmaV2PriceProvider.t.sol +++ b/test/V2/oracles/individual/UmaV2PriceProvider.t.sol @@ -58,8 +58,6 @@ contract UmaV2PriceProviderTest is Helper { ancillaryData, reward ); - uint256 condition = 2; - umaV2PriceProvider.setConditionType(marketId, condition); deal(USDC_TOKEN, address(this), 1000e6); IERC20(USDC_TOKEN).approve(address(umaV2PriceProvider), 1000e6); } @@ -69,7 +67,7 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// function testUmaV2Price() public { - assertEq(umaV2PriceProvider.ORACLE_LIVENESS_TIME(), 3600 * 2); + assertEq(umaV2PriceProvider.ORACLE_LIVENESS_TIME(), 3600); assertEq(umaV2PriceProvider.PRICE_IDENTIFIER(), "TOKEN_PRICE"); assertEq(umaV2PriceProvider.timeOut(), TIME_OUT); assertEq(address(umaV2PriceProvider.oo()), umaV2); @@ -84,17 +82,6 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// // ADMIN // //////////////////////////////////////////////// - function testSetConditionTypeUmaV2Price() public { - uint256 _marketId = 911; - uint256 _condition = 1; - - vm.expectEmit(true, true, true, true); - emit MarketConditionSet(_marketId, _condition); - umaV2PriceProvider.setConditionType(_marketId, _condition); - - assertEq(umaV2PriceProvider.marketIdToConditionType(_marketId), 1); - } - function testUpdateRewardUmaV2Price() public { uint256 newReward = 1000; vm.expectEmit(true, true, true, true); @@ -104,6 +91,24 @@ contract UmaV2PriceProviderTest is Helper { assertEq(umaV2PriceProvider.reward(), newReward); } + function testWithdrawBondUmaV2Price() public { + // Fetching balance before + IERC20 bondAsset = IERC20(USDC_TOKEN); + uint256 balance = bondAsset.balanceOf(address(this)); + + // Sending funds + uint256 bondAmount = 1e18; + deal(USDC_TOKEN, address(umaV2PriceProvider), bondAmount); + + // Checking contract balance updated + assertEq(bondAsset.balanceOf(address(umaV2PriceProvider)), bondAmount); + assertEq(bondAsset.balanceOf(address(this)), balance); + + umaV2PriceProvider.withdrawBond(); + assertEq(bondAsset.balanceOf(address(umaV2PriceProvider)), 0); + assertEq(bondAsset.balanceOf(address(this)), balance + bondAmount); + } + //////////////////////////////////////////////// // PUBLIC CALLBACK // //////////////////////////////////////////////// @@ -131,6 +136,9 @@ contract UmaV2PriceProviderTest is Helper { umaV2PriceProvider.requestLatestPrice(); vm.warp(block.timestamp + 1 days); + (, , , , bool preActiveAssertion) = umaV2PriceProvider.answer(); + assertEq(preActiveAssertion, true); + // Configuring the answer via the callback vm.prank(address(mockUmaV2)); umaV2PriceProvider.priceSettled( @@ -144,13 +152,13 @@ contract UmaV2PriceProviderTest is Helper { uint128 answeredInRound, int128 _price, uint128 updatedAt, - uint256 startedAt + bool activeAssertion ) = umaV2PriceProvider.answer(); // Checking the data assertEq(roundId, 1); assertEq(_price, price); - assertEq(startedAt, previousTimestamp); + assertEq(activeAssertion, false); assertEq(updatedAt, block.timestamp); assertEq(answeredInRound, 1); } @@ -160,9 +168,26 @@ contract UmaV2PriceProviderTest is Helper { //////////////////////////////////////////////// function testrequestLatestPriceUmaV2Price() public { + uint256 balance = IERC20(USDC_TOKEN).balanceOf(address(this)); + assertEq(balance, 1000e6); + umaV2PriceProvider.requestLatestPrice(); - (, , , , uint256 pendingRequestAt) = umaV2PriceProvider.answer(); - assertEq(pendingRequestAt, block.timestamp); + (, , , , bool activeAssertion) = umaV2PriceProvider.answer(); + assertEq(activeAssertion, true); + + assertEq(IERC20(USDC_TOKEN).balanceOf(address(this)), balance - reward); + } + + function testrequestLatestPriceNoTransferUmaV2Price() public { + deal(USDC_TOKEN, address(umaV2PriceProvider), 1000e6); + uint256 balance = IERC20(USDC_TOKEN).balanceOf(address(this)); + assertEq(balance, 1000e6); + + umaV2PriceProvider.requestLatestPrice(); + (, , , , bool activeAssertion) = umaV2PriceProvider.answer(); + assertEq(activeAssertion, true); + + assertEq(IERC20(USDC_TOKEN).balanceOf(address(this)), balance); } function testLatestRoundDataUmaV2Price() public { @@ -172,13 +197,13 @@ contract UmaV2PriceProviderTest is Helper { ( uint80 roundId, int256 _price, - uint256 startedAt, + bool activeAssertion, uint256 updatedAt, uint80 answeredInRound ) = umaV2PriceProvider.latestRoundData(); assertTrue(_price != 0); assertTrue(roundId != 0); - assertTrue(startedAt != 0); + assertTrue(activeAssertion == false); assertTrue(updatedAt != 0); assertTrue(answeredInRound != 0); } @@ -198,11 +223,11 @@ contract UmaV2PriceProviderTest is Helper { // Config the data with mock oracle _configureSettledPrice(); - uint256 conditionType = 1; + // Condition type 1 uint256 marketIdOne = 1; - umaV2PriceProvider.setConditionType(marketIdOne, conditionType); + uint256 strikeMetPrice = 2000001; (bool condition, int256 price) = umaV2PriceProvider.conditionMet( - 0.001 ether, + strikeMetPrice, marketIdOne ); assertTrue(price != 0); @@ -213,8 +238,7 @@ contract UmaV2PriceProviderTest is Helper { // Config the data with mock oracle _configureSettledPrice(); - uint256 conditionType = 2; - umaV2PriceProvider.setConditionType(marketId, conditionType); + // Condition type 2 (bool condition, int256 price) = umaV2PriceProvider.conditionMet( 2 ether, marketId @@ -231,8 +255,7 @@ contract UmaV2PriceProviderTest is Helper { assertEq(roundId, 1); assertEq(answeredInRound, 1); - uint256 conditionType = 2; - umaV2PriceProvider.setConditionType(marketId, conditionType); + // Condition type 2 (bool condition, int256 price) = umaV2PriceProvider.conditionMet( 2 ether, marketId @@ -241,6 +264,7 @@ contract UmaV2PriceProviderTest is Helper { assertEq(condition, true); // Updating the price + vm.warp(block.timestamp + 601); int256 newPrice = 10e6; _updatePrice(newPrice, mockUmaV2); (roundId, , , , answeredInRound) = umaV2PriceProvider.latestRoundData(); @@ -323,17 +347,7 @@ contract UmaV2PriceProviderTest is Helper { ); } - function testRevertConditionTypeSetUmaV2Price() public { - vm.expectRevert(UmaV2PriceProvider.ConditionTypeSet.selector); - umaV2PriceProvider.setConditionType(2, 0); - } - - function testRevertInvalidInputConditionUmaV2Price() public { - vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); - umaV2PriceProvider.setConditionType(0, 0); - } - - function testRevertInvalidInputupdateRewardUmaV2Price() public { + function testRevertInvalidInputUpdateRewardUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.InvalidInput.selector); umaV2PriceProvider.updateReward(0); } @@ -354,24 +368,32 @@ contract UmaV2PriceProviderTest is Helper { umaV2PriceProvider.requestLatestPrice(); } + function testRevertCoodownRequestLatestPriceUmaV2Price() public { + _configureSettledPrice(); + vm.expectRevert(UmaV2PriceProvider.CooldownPeriod.selector); + umaV2PriceProvider.requestLatestPrice(); + } + function testRevertOraclePriceZeroUmaV2Price() public { vm.expectRevert(UmaV2PriceProvider.OraclePriceZero.selector); umaV2PriceProvider.getLatestPrice(); } - function testRevertPricedTimedOutUmaV2Price() public { + function testRevertRequestInProgGetLatestUmaV2Price() public { _configureSettledPrice(); - vm.warp(block.timestamp + 2 days); + vm.warp(block.timestamp + 601); + umaV2PriceProvider.requestLatestPrice(); - vm.expectRevert(UmaV2PriceProvider.PriceTimedOut.selector); + vm.expectRevert(UmaV2PriceProvider.RequestInProgress.selector); umaV2PriceProvider.getLatestPrice(); } - function testRevertConditionTypeNotSetUmaV2Price() public { + function testRevertPricedTimedOutUmaV2Price() public { _configureSettledPrice(); + vm.warp(block.timestamp + 2 days); - vm.expectRevert(UmaV2PriceProvider.ConditionTypeNotSet.selector); - umaV2PriceProvider.conditionMet(0.001 ether, 1); + vm.expectRevert(UmaV2PriceProvider.PriceTimedOut.selector); + umaV2PriceProvider.getLatestPrice(); } //////////////////////////////////////////////// From 1142507bc21ffe20487f58595f9da7b6e630b98c Mon Sep 17 00:00:00 2001 From: 0xNyxos <0xNyxos@proton.me> Date: Thu, 21 Dec 2023 16:43:07 -0500 Subject: [PATCH 25/25] feat: refactored UmaV3 providers --- script/v2/V2DeployContracts.s.sol | 20 +- ...rovider.sol => UmaV3AssertionProvider.sol} | 94 +--- .../UmaV3EventAssertionProvider.sol | 297 ---------- ...ionProvider.sol => UmaV3PriceProvider.sol} | 208 +++---- ...der.t.sol => UmaV3AssertionProvider.t.sol} | 127 ++--- .../UmaV3EventAssertionProvider.t.sol | 515 ------------------ ...rovider.t.sol => UmaV3PriceProvider.t.sol} | 354 +++++++----- test/V2/oracles/mocks/MockUma.sol | 4 +- 8 files changed, 405 insertions(+), 1214 deletions(-) rename src/v2/oracles/individual/{UmaV3DynamicAssertionProvider.sol => UmaV3AssertionProvider.sol} (89%) delete mode 100644 src/v2/oracles/individual/UmaV3EventAssertionProvider.sol rename src/v2/oracles/individual/{UmaV3PriceAssertionProvider.sol => UmaV3PriceProvider.sol} (69%) rename test/V2/oracles/individual/{UmaV3DynamicAssertionProvider.t.sol => UmaV3AssertionProvider.t.sol} (80%) delete mode 100644 test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol rename test/V2/oracles/individual/{UmaV3PriceAssertionProvider.t.sol => UmaV3PriceProvider.t.sol} (57%) diff --git a/script/v2/V2DeployContracts.s.sol b/script/v2/V2DeployContracts.s.sol index 56d00f25..a47ec93c 100644 --- a/script/v2/V2DeployContracts.s.sol +++ b/script/v2/V2DeployContracts.s.sol @@ -16,8 +16,8 @@ import "../../src/v2/oracles/individual/CVIPriceProvider.sol"; import "../../src/v2/oracles/individual/GdaiPriceProvider.sol"; import "../../src/v2/oracles/individual/UmaV2PriceProvider.sol"; import "../../src/v2/oracles/individual/UmaV2AssertionProvider.sol"; -import "../../src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol"; -import "../../src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol"; +import "../../src/v2/oracles/individual/UmaV3PriceProvider.sol"; +import "../../src/v2/oracles/individual/UmaV3AssertionProvider.sol"; import "../../src/v2/oracles/individual/PythPriceProvider.sol"; import "../../src/v2/TimeLock.sol"; import "./V2Helper.sol"; @@ -170,7 +170,7 @@ contract V2DeployContracts is Script, HelperV2 { // bytes // memory assertionDescription = "The USDC/USD exchange is above 0.997"; // address currency = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; // WETH_ADDRESS - // UmaV3PriceAssertionProvider umaPriceProvider = new UmaV3PriceAssertionProvider( + // UmaV3PriceProvider umaPriceProvider = new UmaV3PriceProvider( // umaDecimals, // umaDescription, // timeOut, @@ -185,13 +185,13 @@ contract V2DeployContracts is Script, HelperV2 { string memory marketDescription = "ETH Volatility"; uint256 requiredBond = 501e6; address currency = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; // USDC.e - UmaV3DynamicAssertionProvider umaV3Provider = new UmaV3DynamicAssertionProvider( - marketDescription, - timeOut, - umaOOV3, - currency, - requiredBond - ); + UmaV3AssertionProvider umaV3Provider = new UmaV3AssertionProvider( + marketDescription, + timeOut, + umaOOV3, + currency, + requiredBond + ); // address pythContract = 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C; // // bytes32 fdUsdFeedId = 0xccdc1a08923e2e4f4b1e6ea89de6acbc5fe1948e9706f5604b8cb50bc1ed3979; diff --git a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol b/src/v2/oracles/individual/UmaV3AssertionProvider.sol similarity index 89% rename from src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol rename to src/v2/oracles/individual/UmaV3AssertionProvider.sol index c3266ac0..78721959 100644 --- a/src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3AssertionProvider.sol @@ -12,7 +12,7 @@ import "forge-std/console.sol"; /// @notice Assertion provider for one data point with differing description /// @dev Example: (a) ETH vol is above , and (b) ETH vol is below /// @dev This provider would not work if you needed to check if x happened between time y and z -contract UmaV3DynamicAssertionProvider is Ownable { +contract UmaV3AssertionProvider is Ownable { using SafeTransferLib for ERC20; struct MarketAnswer { bool activeAssertion; @@ -28,7 +28,7 @@ contract UmaV3DynamicAssertionProvider is Ownable { // Uma V3 uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. - uint256 public constant ASSERTION_COOLDOWN = 300; // 5 minutes. + uint256 public constant ASSERTION_COOLDOWN = 600; // 10 minutes. address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; @@ -113,28 +113,6 @@ contract UmaV3DynamicAssertionProvider is Ownable { emit BondUpdated(newBond); } - /** - @notice Updates the data being used to assert in the uma assertion request - @param _newData is the new data for the assertion - */ - function updateAssertionData(uint256 _newData) external onlyOwner { - _updateAssertionData(_newData); - } - - /** - @notice Updates the assertion data and makes a request to Uma V3 for a market - @dev Updated data will be used for assertion then a callback will be received after LIVENESS_PERIOD - @param _newData is the new data for the assertion - @param _marketId is the marketId for the market - */ - function updateAssertionDataAndFetch( - uint256 _newData, - uint256 _marketId - ) external onlyOwner returns (bytes32) { - _updateAssertionData(_newData); - return _fetchAssertion(_marketId); - } - /** @notice Toggles relayer status for an address @param _relayer is the address to update @@ -181,6 +159,30 @@ contract UmaV3DynamicAssertionProvider is Ownable { emit AnswersReset(_marketId); } + /*////////////////////////////////////////////////////////////// + CALLBACK + //////////////////////////////////////////////////////////////*/ + // Callback from settled assertion. + // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. + // Otherwise, assertedOutcomeId is reset and the market can be asserted again. + function assertionResolvedCallback( + bytes32 _assertionId, + bool _assertedTruthfully + ) external { + if (msg.sender != address(umaV3)) revert InvalidCaller(); + + uint256 marketId = assertionIdToMarket[_assertionId]; + MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; + if (marketAnswer.activeAssertion == false) revert AssertionInactive(); + + marketAnswer.updatedAt = uint128(block.timestamp); + marketAnswer.answer = _assertedTruthfully ? 1 : 0; + marketAnswer.activeAssertion = false; + marketIdToAnswer[marketId] = marketAnswer; + + emit AssertionResolved(_assertionId, _assertedTruthfully); + } + /*////////////////////////////////////////////////////////////// EXTERNAL //////////////////////////////////////////////////////////////*/ @@ -229,47 +231,9 @@ contract UmaV3DynamicAssertionProvider is Ownable { return (_conditionMet, price); } - /*////////////////////////////////////////////////////////////// - CALLBACK - //////////////////////////////////////////////////////////////*/ - // Callback from settled assertion. - // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. - // Otherwise, assertedOutcomeId is reset and the market can be asserted again. - function assertionResolvedCallback( - bytes32 _assertionId, - bool _assertedTruthfully - ) external { - if (msg.sender != address(umaV3)) revert InvalidCaller(); - - uint256 marketId = assertionIdToMarket[_assertionId]; - MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; - if (marketAnswer.activeAssertion == false) revert AssertionInactive(); - - marketAnswer.updatedAt = uint128(block.timestamp); - marketAnswer.answer = _assertedTruthfully ? 1 : 0; - marketAnswer.activeAssertion = false; - marketIdToAnswer[marketId] = marketAnswer; - - emit AssertionResolved(_assertionId, _assertedTruthfully); - } - /*////////////////////////////////////////////////////////////// INTERNAL //////////////////////////////////////////////////////////////*/ - /** - @param _newData is the new data for the assertion - @dev updates the assertion data - */ - function _updateAssertionData(uint256 _newData) private { - if (_newData == 0) revert InvalidInput(); - assertionData = AssertionData({ - assertionData: uint128(_newData), - updatedAt: uint128(block.timestamp) - }); - - emit AssertionDataUpdated(_newData); - } - /** @dev AssertionDataOutdated check ensures the data being asserted is up to date @dev CooldownPending check ensures the cooldown period has passed since the last assertion @@ -284,8 +248,6 @@ contract UmaV3DynamicAssertionProvider is Ownable { if (marketAnswer.activeAssertion == true) revert AssertionActive(); if (block.timestamp - marketAnswer.updatedAt < ASSERTION_COOLDOWN) revert CooldownPending(); - if ((block.timestamp - assertionData.updatedAt) > timeOut) - revert AssertionDataOutdated(); // Configure bond and claim information uint256 minimumBond = umaV3.getMinimumBond(address(currency)); @@ -336,8 +298,7 @@ contract UmaV3DynamicAssertionProvider is Ownable { abi.encodePacked( "As of assertion timestamp ", _toUtf8BytesUint(block.timestamp), - _assertionDescription, - _toUtf8BytesUint(assertionData.assertionData) + _assertionDescription ); } @@ -377,7 +338,6 @@ contract UmaV3DynamicAssertionProvider is Ownable { error InvalidCaller(); error AssertionActive(); error AssertionInactive(); - error AssertionDataOutdated(); error CooldownPending(); error DescriptionAlreadySet(); } diff --git a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol b/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol deleted file mode 100644 index f1eb8f51..00000000 --- a/src/v2/oracles/individual/UmaV3EventAssertionProvider.sol +++ /dev/null @@ -1,297 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {IConditionProvider} from "../../interfaces/IConditionProvider.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IOptimisticOracleV3} from "../../interfaces/IOptimisticOracleV3.sol"; -import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol"; -import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/// @notice This provider is build to work where you need to define between x and y e.g. there was a hack between Aug31st -contract UmaV3EventAssertionProvider is Ownable { - using SafeTransferLib for ERC20; - struct MarketAnswer { - bool activeAssertion; - uint128 updatedAt; - uint8 answer; - bytes32 assertionId; - } - - string public constant OUTCOME_1 = "true. "; - string public constant OUTCOME_2 = "false. "; - - // Uma V3 - uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. - address public immutable currency; // Currency used for all prediction markets - bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. - IOptimisticOracleV3 public immutable umaV3; - - // Market info - uint256 public immutable timeOut; - uint256 public immutable decimals; - string public description; - bytes public assertionDescription; - uint256 public requiredBond; // Bond required to assert on a market - uint256 public coverageStart; - - mapping(uint256 => uint256) public marketIdToConditionType; - mapping(uint256 => MarketAnswer) public marketIdToAnswer; - mapping(bytes32 => uint256) public assertionIdToMarket; - mapping(address => bool) public whitelistRelayer; - - event MarketAsserted(uint256 marketId, bytes32 assertionId); - event AssertionResolved(bytes32 assertionId, bool assertion); - event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); - event CoverageStartUpdated(uint256 startTime); - event BondUpdated(uint256 newBond); - event RelayerUpdated(address relayer, bool state); - - /** - @param _decimals is decimals for the provider maker if relevant - @param _description is for the price provider market - @param _timeOut is the max time between receiving callback and resolving market condition - @param _umaV3 is the V3 Uma Optimistic Oracle - @param _defaultIdentifier is UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. - @param _currency is currency used to post the bond - @param _assertionDescription is description used for the market - @param _requiredBond is bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This must be >= getMinimumBond(address(currency)). - */ - constructor( - uint256 _decimals, - string memory _description, - uint256 _timeOut, - address _umaV3, - bytes32 _defaultIdentifier, - address _currency, - bytes memory _assertionDescription, - uint256 _requiredBond - ) { - if (_decimals == 0) revert InvalidInput(); - if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) - revert InvalidInput(); - if (_timeOut == 0) revert InvalidInput(); - if (_umaV3 == address(0)) revert ZeroAddress(); - if ( - keccak256(abi.encodePacked(_defaultIdentifier)) == - keccak256(abi.encodePacked(bytes32(""))) - ) revert InvalidInput(); - if (_currency == address(0)) revert ZeroAddress(); - if ( - keccak256(abi.encodePacked(_assertionDescription)) == - keccak256(bytes("")) - ) revert InvalidInput(); - if (_requiredBond == 0) revert InvalidInput(); - - decimals = _decimals; - description = _description; - timeOut = _timeOut; - umaV3 = IOptimisticOracleV3(_umaV3); - defaultIdentifier = _defaultIdentifier; - currency = _currency; - assertionDescription = _assertionDescription; - requiredBond = _requiredBond; - coverageStart = block.timestamp; - } - - /*////////////////////////////////////////////////////////////// - ADMIN - //////////////////////////////////////////////////////////////*/ - function setConditionType( - uint256 _marketId, - uint256 _condition - ) external onlyOwner { - if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); - if (_condition != 1 && _condition != 2) revert InvalidInput(); - marketIdToConditionType[_marketId] = _condition; - emit MarketConditionSet(_marketId, _condition); - } - - function updateCoverageStart(uint256 _coverageStart) external onlyOwner { - if (_coverageStart < coverageStart) revert InvalidInput(); - coverageStart = _coverageStart; - emit CoverageStartUpdated(_coverageStart); - } - - function updateRequiredBond(uint256 newBond) external onlyOwner { - if (newBond == 0) revert InvalidInput(); - requiredBond = newBond; - emit BondUpdated(newBond); - } - - function updateRelayer(address _relayer) external onlyOwner { - if (_relayer == address(0)) revert ZeroAddress(); - bool relayerState = whitelistRelayer[_relayer]; - whitelistRelayer[_relayer] = !relayerState; - emit RelayerUpdated(_relayer, relayerState); - } - - function withdrawBond() external onlyOwner { - ERC20 bondCurrency = ERC20(currency); - uint256 balance = bondCurrency.balanceOf(address(this)); - bondCurrency.safeTransfer(msg.sender, balance); - } - - /*////////////////////////////////////////////////////////////// - CALLBACK - //////////////////////////////////////////////////////////////*/ - // Callback from settled assertion. - // If the assertion was resolved true, then the asserter gets the reward and the market is marked as resolved. - // Otherwise, assertedOutcomeId is reset and the market can be asserted again. - function assertionResolvedCallback( - bytes32 _assertionId, - bool _assertedTruthfully - ) external { - if (msg.sender != address(umaV3)) revert InvalidCaller(); - - uint256 marketId = assertionIdToMarket[_assertionId]; - MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; - if (marketAnswer.activeAssertion == false) revert AssertionInactive(); - - marketAnswer.updatedAt = uint128(block.timestamp); - marketAnswer.answer = _assertedTruthfully ? 1 : 0; - marketAnswer.activeAssertion = false; - marketIdToAnswer[marketId] = marketAnswer; - - emit AssertionResolved(_assertionId, _assertedTruthfully); - } - - /*////////////////////////////////////////////////////////////// - EXTERNAL - //////////////////////////////////////////////////////////////*/ - - function fetchAssertion( - uint256 _marketId - ) external returns (bytes32 assertionId) { - if (whitelistRelayer[msg.sender] == false) revert InvalidCaller(); - - MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; - if (marketAnswer.activeAssertion == true) revert AssertionActive(); - - // Configure bond and claim information - uint256 minimumBond = umaV3.getMinimumBond(address(currency)); - uint256 reqBond = requiredBond; - uint256 bond = reqBond > minimumBond ? reqBond : minimumBond; - bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); - - // Transfer bond from sender and request assertion - ERC20 bondCurrency = ERC20(currency); - if (bondCurrency.balanceOf(address(this)) < bond) - ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); - ERC20(currency).safeApprove(address(umaV3), bond); - assertionId = umaV3.assertTruth( - claim, - msg.sender, // Asserter - address(this), // Receive callback to this contract - address(0), // No sovereign security - ASSERTION_LIVENESS, - IERC20(currency), - bond, - defaultIdentifier, - bytes32(0) // No domain - ); - - assertionIdToMarket[assertionId] = _marketId; - marketIdToAnswer[_marketId].activeAssertion = true; - marketIdToAnswer[_marketId].assertionId = assertionId; - - emit MarketAsserted(_marketId, assertionId); - } - - /** @notice Fetch the assertion state of the market - * @return bool If assertion is true or false for the market condition - */ - function checkAssertion( - uint256 _marketId - ) public view virtual returns (bool) { - MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; - - if ((block.timestamp - marketAnswer.updatedAt) > timeOut) - revert PriceTimedOut(); - - if (marketAnswer.answer == 1) return true; - else return false; - } - - // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future - /** @notice Fetch price and return condition - * @param _marketId the marketId for the market - * @return boolean If condition is met i.e. strike > price - * @return price Current price for token - */ - function conditionMet( - uint256, - uint256 _marketId - ) public view virtual returns (bool, int256 price) { - uint256 conditionType = marketIdToConditionType[_marketId]; - bool condition = checkAssertion(_marketId); - - if (conditionType == 1) return (condition, price); - else if (conditionType == 2) return (condition, price); - else revert ConditionTypeNotSet(); - } - - /*////////////////////////////////////////////////////////////// - INTERNAL - //////////////////////////////////////////////////////////////*/ - /** - @param _conditionType is the condition type for the market - @dev encode claim would look like: "As of assertion timestamp , " - Where inputs could be: "As of assertion timestamp 1625097600, <0.997>" - @return bytes for the claim - */ - function _composeClaim( - uint256 _conditionType - ) internal view returns (bytes memory) { - return - abi.encodePacked( - "As of assertion timestamp ", - _toUtf8BytesUint(block.timestamp), - ", the following statement is", - _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, - assertionDescription, - "This occured after the timestamp of ", - _toUtf8BytesUint(coverageStart) - ); - } - - /** - * @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type. - * @dev This method is based off of this code: https://stackoverflow.com/a/65707309. - * @dev Pulled from UMA protocol packages: https://github.com/UMAprotocol/protocol/blob/9bfbbe98bed0ac7d9c924115018bb0e26987e2b5/packages/core/contracts/common/implementation/AncillaryData.sol - */ - function _toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) { - if (x == 0) { - return "0"; - } - uint256 j = x; - uint256 len; - while (j != 0) { - len++; - j /= 10; - } - bytes memory bstr = new bytes(len); - uint256 k = len; - while (x != 0) { - k = k - 1; - uint8 temp = (48 + uint8(x - (x / 10) * 10)); - bytes1 b1 = bytes1(temp); - bstr[k] = b1; - x /= 10; - } - return bstr; - } - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - error ZeroAddress(); - error InvalidInput(); - error PriceTimedOut(); - error ConditionTypeNotSet(); - error ConditionTypeSet(); - error InvalidCaller(); - error AssertionActive(); - error AssertionInactive(); - error InvalidCallback(); -} diff --git a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol b/src/v2/oracles/individual/UmaV3PriceProvider.sol similarity index 69% rename from src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol rename to src/v2/oracles/individual/UmaV3PriceProvider.sol index 20d6081e..56721910 100644 --- a/src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol +++ b/src/v2/oracles/individual/UmaV3PriceProvider.sol @@ -10,20 +10,23 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @notice Assertion provider where the condition can be checked without a required begin time e.g. 1 PEPE > $1000 or BTC market > 2x ETH market cap /// @dev This provider would not work if you needed to check if x happened between time y and z -contract UmaV3PriceAssertionProvider is Ownable { +contract UmaV3PriceProvider is Ownable { using SafeTransferLib for ERC20; struct MarketAnswer { bool activeAssertion; uint128 updatedAt; - uint8 answer; + uint256 answer; bytes32 assertionId; } - string public constant OUTCOME_1 = "true. "; - string public constant OUTCOME_2 = "false. "; + struct AssertionData { + uint128 assertionData; + uint128 updatedAt; + } // Uma V3 uint64 public constant ASSERTION_LIVENESS = 7200; // 2 hours. + uint256 public constant ASSERTION_COOLDOWN = 600; // 10 minutes. address public immutable currency; // Currency used for all prediction markets bytes32 public immutable defaultIdentifier; // Identifier used for all prediction markets. IOptimisticOracleV3 public immutable umaV3; @@ -32,79 +35,62 @@ contract UmaV3PriceAssertionProvider is Ownable { uint256 public immutable timeOut; uint256 public immutable decimals; string public description; - bytes public assertionDescription; + string public assertionDescription; + MarketAnswer public globalAnswer; // The answer for the market + AssertionData public assertionData; // The uint data value for the market uint256 public requiredBond; // Bond required to assert on a market - mapping(uint256 => uint256) public marketIdToConditionType; - mapping(uint256 => MarketAnswer) public marketIdToAnswer; - mapping(bytes32 => uint256) public assertionIdToMarket; mapping(address => bool) public whitelistRelayer; event MarketAsserted(uint256 marketId, bytes32 assertionId); event AssertionResolved(bytes32 assertionId, bool assertion); - event MarketConditionSet(uint256 indexed marketId, uint256 conditionType); event BondUpdated(uint256 newBond); + event AssertionDataUpdated(uint256 newData); event RelayerUpdated(address relayer, bool state); + event BondWithdrawn(uint256 amount); /** @param _decimals is decimals for the provider maker if relevant @param _description is for the price provider market @param _timeOut is the max time between receiving callback and resolving market condition @param _umaV3 is the V3 Uma Optimistic Oracle - @param _defaultIdentifier is UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved. @param _currency is currency used to post the bond - @param _assertionDescription is description used for the market @param _requiredBond is bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This must be >= getMinimumBond(address(currency)). */ constructor( uint256 _decimals, string memory _description, + string memory _assertionDescription, uint256 _timeOut, address _umaV3, - bytes32 _defaultIdentifier, address _currency, - bytes memory _assertionDescription, uint256 _requiredBond ) { if (_decimals == 0) revert InvalidInput(); if (keccak256(bytes(_description)) == keccak256(bytes(string("")))) revert InvalidInput(); - if (_timeOut == 0) revert InvalidInput(); - if (_umaV3 == address(0)) revert ZeroAddress(); if ( - keccak256(abi.encodePacked(_defaultIdentifier)) == - keccak256(abi.encodePacked(bytes32(""))) + keccak256(bytes(_assertionDescription)) == + keccak256(bytes(string(""))) ) revert InvalidInput(); + if (_timeOut == 0) revert InvalidInput(); + if (_umaV3 == address(0)) revert ZeroAddress(); if (_currency == address(0)) revert ZeroAddress(); - if ( - keccak256(abi.encodePacked(_assertionDescription)) == - keccak256(bytes("")) - ) revert InvalidInput(); if (_requiredBond == 0) revert InvalidInput(); - decimals = _decimals; description = _description; + decimals = _decimals; + assertionDescription = _assertionDescription; timeOut = _timeOut; umaV3 = IOptimisticOracleV3(_umaV3); - defaultIdentifier = _defaultIdentifier; + defaultIdentifier = umaV3.defaultIdentifier(); currency = _currency; - assertionDescription = _assertionDescription; requiredBond = _requiredBond; } /*////////////////////////////////////////////////////////////// ADMIN //////////////////////////////////////////////////////////////*/ - function setConditionType( - uint256 _marketId, - uint256 _condition - ) external onlyOwner { - if (marketIdToConditionType[_marketId] != 0) revert ConditionTypeSet(); - if (_condition != 1 && _condition != 2) revert InvalidInput(); - marketIdToConditionType[_marketId] = _condition; - emit MarketConditionSet(_marketId, _condition); - } - function updateRequiredBond(uint256 newBond) external onlyOwner { if (newBond == 0) revert InvalidInput(); requiredBond = newBond; @@ -118,10 +104,15 @@ contract UmaV3PriceAssertionProvider is Ownable { emit RelayerUpdated(_relayer, relayerState); } + /** + @notice Withdraws the balance of the currency in the contract + @dev This is likely to be the bond value remaining in the contract + */ function withdrawBond() external onlyOwner { ERC20 bondCurrency = ERC20(currency); uint256 balance = bondCurrency.balanceOf(address(this)); bondCurrency.safeTransfer(msg.sender, balance); + emit BondWithdrawn(balance); } /*////////////////////////////////////////////////////////////// @@ -136,14 +127,15 @@ contract UmaV3PriceAssertionProvider is Ownable { ) external { if (msg.sender != address(umaV3)) revert InvalidCaller(); - uint256 marketId = assertionIdToMarket[_assertionId]; - MarketAnswer memory marketAnswer = marketIdToAnswer[marketId]; + MarketAnswer memory marketAnswer = globalAnswer; if (marketAnswer.activeAssertion == false) revert AssertionInactive(); marketAnswer.updatedAt = uint128(block.timestamp); - marketAnswer.answer = _assertedTruthfully ? 1 : 0; + marketAnswer.answer = _assertedTruthfully + ? assertionData.assertionData + : 0; marketAnswer.activeAssertion = false; - marketIdToAnswer[marketId] = marketAnswer; + globalAnswer = marketAnswer; emit AssertionResolved(_assertionId, _assertedTruthfully); } @@ -151,28 +143,96 @@ contract UmaV3PriceAssertionProvider is Ownable { /*////////////////////////////////////////////////////////////// EXTERNAL //////////////////////////////////////////////////////////////*/ - function fetchAssertion( + /** + @notice Updates the assertion data and makes a request to Uma V3 for a market + @dev Updated data will be used for assertion then a callback will be received after LIVENESS_PERIOD + @param _newData is the new data for the assertion + @param _marketId is the marketId for the market + */ + function updateAssertionDataAndFetch( + uint256 _newData, uint256 _marketId - ) external returns (bytes32 assertionId) { + ) external returns (bytes32) { + if (_newData == 0) revert InvalidInput(); if (whitelistRelayer[msg.sender] == false) revert InvalidCaller(); + _updateAssertionData(_newData); + return _fetchAssertion(_marketId); + } + + /** @notice Fetch the assertion state of the market + * @return bool If assertion is true or false for the market condition + */ + function getLatestPrice() public view virtual returns (int256) { + MarketAnswer memory marketAnswer = globalAnswer; - MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; if (marketAnswer.activeAssertion == true) revert AssertionActive(); + if ((block.timestamp - marketAnswer.updatedAt) > timeOut) + revert PriceTimedOut(); + + return int256(marketAnswer.answer); + } + + /** @notice Fetch price and return condition + * @param _strike is the strike price for the market + * @return boolean If condition is met i.e. strike > price + * @return price Current price for token + */ + function conditionMet( + uint256 _strike, + uint256 + ) public view virtual returns (bool, int256 price) { + uint256 conditionType = _strike % 2 ** 1; + price = getLatestPrice(); + + if (conditionType == 1) return (int256(_strike) < price, price); + else if (conditionType == 0) return (int256(_strike) > price, price); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////*/ + /** + @param _newData is the new data for the assertion + @dev updates the assertion data + */ + function _updateAssertionData(uint256 _newData) internal { + assertionData = AssertionData({ + assertionData: uint128(_newData), + updatedAt: uint128(block.timestamp) + }); + + emit AssertionDataUpdated(_newData); + } + + /** + @dev AssertionDataOutdated check ensures the data being asserted is up to date + @dev CooldownPending check ensures the cooldown period has passed since the last assertion + @param _marketId is the marketId for the market + */ + function _fetchAssertion( + uint256 _marketId + ) internal returns (bytes32 assertionId) { + MarketAnswer memory marketAnswer = globalAnswer; + if (marketAnswer.activeAssertion == true) revert AssertionActive(); + if (block.timestamp - marketAnswer.updatedAt < ASSERTION_COOLDOWN) + revert CooldownPending(); // Configure bond and claim information uint256 minimumBond = umaV3.getMinimumBond(address(currency)); uint256 reqBond = requiredBond; uint256 bond = reqBond > minimumBond ? reqBond : minimumBond; - bytes memory claim = _composeClaim(marketIdToConditionType[_marketId]); + bytes memory claim = _composeClaim(); // Transfer bond from sender and request assertion ERC20 bondCurrency = ERC20(currency); if (bondCurrency.balanceOf(address(this)) < bond) - ERC20(currency).safeTransferFrom(msg.sender, address(this), bond); - ERC20(currency).safeApprove(address(umaV3), bond); + bondCurrency.safeTransferFrom(msg.sender, address(this), bond); + bondCurrency.safeApprove(address(umaV3), bond); + + // Request assertion from UMA V3 assertionId = umaV3.assertTruth( claim, - msg.sender, // Asserter + address(this), // Asserter address(this), // Receive callback to this contract address(0), // No sovereign security ASSERTION_LIVENESS, @@ -182,65 +242,25 @@ contract UmaV3PriceAssertionProvider is Ownable { bytes32(0) // No domain ); - assertionIdToMarket[assertionId] = _marketId; - marketIdToAnswer[_marketId].activeAssertion = true; - marketIdToAnswer[_marketId].assertionId = assertionId; + marketAnswer.activeAssertion = true; + marketAnswer.assertionId = assertionId; + globalAnswer = marketAnswer; emit MarketAsserted(_marketId, assertionId); } - /** @notice Fetch the assertion state of the market - * @return bool If assertion is true or false for the market condition - */ - function checkAssertion( - uint256 _marketId - ) public view virtual returns (bool) { - MarketAnswer memory marketAnswer = marketIdToAnswer[_marketId]; - - if ((block.timestamp - marketAnswer.updatedAt) > timeOut) - revert PriceTimedOut(); - - if (marketAnswer.answer == 1) return true; - else return false; - } - - // NOTE: _marketId unused but receiving marketId makes Generic controller composabile for future - /** @notice Fetch price and return condition - * @param _marketId the marketId for the market - * @return boolean If condition is met i.e. strike > price - * @return price Current price for token - */ - function conditionMet( - uint256, - uint256 _marketId - ) public view virtual returns (bool, int256 price) { - uint256 conditionType = marketIdToConditionType[_marketId]; - bool condition = checkAssertion(_marketId); - - if (conditionType == 1) return (condition, price); - else if (conditionType == 2) return (condition, price); - else revert ConditionTypeNotSet(); - } - - /*////////////////////////////////////////////////////////////// - INTERNAL - //////////////////////////////////////////////////////////////*/ /** - @param _conditionType is the condition type for the market - @dev encode claim would look like: "As of assertion timestamp , " + @dev encode claim would look like: "As of assertion timestamp , " Where inputs could be: "As of assertion timestamp 1625097600, <0.997>" @return bytes for the claim */ - function _composeClaim( - uint256 _conditionType - ) internal view returns (bytes memory) { + function _composeClaim() internal view returns (bytes memory) { return abi.encodePacked( "As of assertion timestamp ", _toUtf8BytesUint(block.timestamp), - ", the following statement is", - _conditionType == 1 ? OUTCOME_1 : OUTCOME_2, - assertionDescription + assertionDescription, + _toUtf8BytesUint(assertionData.assertionData) ); } @@ -277,10 +297,8 @@ contract UmaV3PriceAssertionProvider is Ownable { error ZeroAddress(); error InvalidInput(); error PriceTimedOut(); - error ConditionTypeNotSet(); - error ConditionTypeSet(); error InvalidCaller(); error AssertionActive(); error AssertionInactive(); - error InvalidCallback(); + error CooldownPending(); } diff --git a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3AssertionProvider.t.sol similarity index 80% rename from test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol rename to test/V2/oracles/individual/UmaV3AssertionProvider.t.sol index 9fbe60be..ad2161fd 100644 --- a/test/V2/oracles/individual/UmaV3DynamicAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3AssertionProvider.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; import { - UmaV3DynamicAssertionProvider -} from "../../../../src/v2/oracles/individual/UmaV3DynamicAssertionProvider.sol"; + UmaV3AssertionProvider +} from "../../../../src/v2/oracles/individual/UmaV3AssertionProvider.sol"; import {TimeLock} from "../../../../src/v2/TimeLock.sol"; import { MockOracleAnswerZero, @@ -18,10 +18,10 @@ import { IOptimisticOracleV3 } from "../../../../src/v2/interfaces/IOptimisticOracleV3.sol"; -contract UmaV3DynamicAssertionProviderTest is Helper { +contract UmaV3AssertionProviderTest is Helper { uint256 public arbForkId; VaultFactoryV2 public factory; - UmaV3DynamicAssertionProvider public umaPriceProvider; + UmaV3AssertionProvider public umaPriceProvider; uint256 public marketId = 2; ERC20 public wethAsset; @@ -41,7 +41,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaPriceProvider = new UmaV3DynamicAssertionProvider( + umaPriceProvider = new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, @@ -57,6 +57,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { //////////////////////////////////////////////// function testUmaCreationDynamic() public { assertEq(umaPriceProvider.ASSERTION_LIVENESS(), 7200); + assertEq(umaPriceProvider.ASSERTION_COOLDOWN(), 600); assertEq(umaPriceProvider.currency(), WETH_ADDRESS); assertEq( umaPriceProvider.defaultIdentifier(), @@ -99,23 +100,11 @@ contract UmaV3DynamicAssertionProviderTest is Helper { ); } - function testUpdateAssertionData() public { - uint256 newData = 1e6; - - vm.expectEmit(true, true, true, true); - emit AssertionDataUpdated(newData); - umaPriceProvider.updateAssertionData(newData); - (uint256 assertionData, uint256 updatedAt) = umaPriceProvider - .assertionData(); - assertEq(assertionData, newData); - assertEq(updatedAt, block.timestamp); - } - function testUpdateAssertionDataAndFetch() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3DynamicAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( + // Deploying new UmaV3AssertionProvider + umaPriceProvider = new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, address(mockUma), @@ -132,11 +121,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { // Moving forward so the constructor data is invalid vm.warp(block.timestamp + 2 days); - uint256 _newData = 4e6; - bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( - _newData, - marketId - ); + bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); // Checking assertion links to marketId uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); @@ -185,6 +170,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { // Setting truthy state and checking is truth uint256 customMarketId = 3; MockUma mockUma = _stagingTruthyAssertion(); + + vm.warp(block.timestamp + 601); _stagingTruthyAssertionCustom(customMarketId, mockUma); // Checking both markets state set to true @@ -209,8 +196,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { function testConditionMetUmaDynamic() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3DynamicAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( + // Deploying new UmaV3AssertionProvider + umaPriceProvider = new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, address(mockUma), @@ -227,9 +214,6 @@ contract UmaV3DynamicAssertionProviderTest is Helper { // Moving forward so the constructor data is invalid vm.warp(block.timestamp + 2 days); - uint256 _newData = 4e6; - umaPriceProvider.updateAssertionData(_newData); - vm.expectEmit(true, false, false, true); emit MarketAsserted(marketId, bytes32(abi.encode(0x12))); bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); @@ -294,8 +278,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { function testFetchAssertionContractHasBalance() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3DynamicAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( + // Deploying new UmaV3AssertionProvider + umaPriceProvider = new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, address(mockUma), @@ -315,10 +299,6 @@ contract UmaV3DynamicAssertionProviderTest is Helper { // Moving forward so the constructor data is invalid vm.warp(block.timestamp + 2 days); - uint256 _newData = 4e6; - umaPriceProvider.updateAssertionData(_newData); - - // Querying for assertion umaPriceProvider.fetchAssertion(marketId); // Checking umaPriceProvide balance declined @@ -333,8 +313,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { // REVERT CASES // //////////////////////////////////////////////// function testRevertConstructorInputsUma() public { - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - new UmaV3DynamicAssertionProvider( + vm.expectRevert(UmaV3AssertionProvider.InvalidInput.selector); + new UmaV3AssertionProvider( string(""), TIME_OUT, UMA_OO_V3, @@ -342,8 +322,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - new UmaV3DynamicAssertionProvider( + vm.expectRevert(UmaV3AssertionProvider.InvalidInput.selector); + new UmaV3AssertionProvider( UMA_DESCRIPTION, 0, UMA_OO_V3, @@ -351,8 +331,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); - new UmaV3DynamicAssertionProvider( + vm.expectRevert(UmaV3AssertionProvider.ZeroAddress.selector); + new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, address(0), @@ -360,8 +340,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); - new UmaV3DynamicAssertionProvider( + vm.expectRevert(UmaV3AssertionProvider.ZeroAddress.selector); + new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, @@ -369,8 +349,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { REQUIRED_BOND ); - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - new UmaV3DynamicAssertionProvider( + vm.expectRevert(UmaV3AssertionProvider.InvalidInput.selector); + new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, UMA_OO_V3, @@ -380,41 +360,32 @@ contract UmaV3DynamicAssertionProviderTest is Helper { } function testRevertInvalidInputRequiredBond() public { - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); + vm.expectRevert(UmaV3AssertionProvider.InvalidInput.selector); umaPriceProvider.updateRequiredBond(0); } function testRevertZeroAddressUpdateRelayer() public { - vm.expectRevert(UmaV3DynamicAssertionProvider.ZeroAddress.selector); + vm.expectRevert(UmaV3AssertionProvider.ZeroAddress.selector); umaPriceProvider.updateRelayer(address(0)); } - function testRevertInvalidInputAssertionData() public { - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidInput.selector); - umaPriceProvider.updateAssertionData(0); - } - function testRevertSetAssertionDescription() public { string memory newDescription = " USDC/USD exchange rate is above"; umaPriceProvider.setAssertionDescription(marketId, newDescription); - vm.expectRevert( - UmaV3DynamicAssertionProvider.DescriptionAlreadySet.selector - ); + vm.expectRevert(UmaV3AssertionProvider.DescriptionAlreadySet.selector); umaPriceProvider.setAssertionDescription(marketId, string("")); } function testRevertInvalidCallerCallback() public { - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidCaller.selector); + vm.expectRevert(UmaV3AssertionProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); } function testRevertAssertionInactive() public { vm.prank(UMA_OO_V3); - vm.expectRevert( - UmaV3DynamicAssertionProvider.AssertionInactive.selector - ); + vm.expectRevert(UmaV3AssertionProvider.AssertionInactive.selector); umaPriceProvider.assertionResolvedCallback( bytes32(abi.encode(0x12)), true @@ -424,8 +395,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { function testRevertCheckAssertionActive() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3DynamicAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( + // Deploying new UmaV3AssertionProvider + umaPriceProvider = new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, address(mockUma), @@ -439,15 +410,15 @@ contract UmaV3DynamicAssertionProviderTest is Helper { wethAsset.approve(address(umaPriceProvider), 1e18); umaPriceProvider.fetchAssertion(marketId); - vm.expectRevert(UmaV3DynamicAssertionProvider.AssertionActive.selector); + vm.expectRevert(UmaV3AssertionProvider.AssertionActive.selector); umaPriceProvider.fetchAssertion(marketId); } function testRevertFetchAssertionActive() public { MockUma mockUma = new MockUma(); - // Deploying new UmaV3DynamicAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( + // Deploying new UmaV3AssertionProvider + umaPriceProvider = new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, address(mockUma), @@ -461,29 +432,21 @@ contract UmaV3DynamicAssertionProviderTest is Helper { wethAsset.approve(address(umaPriceProvider), 1e18); umaPriceProvider.fetchAssertion(marketId); - vm.expectRevert(UmaV3DynamicAssertionProvider.AssertionActive.selector); + vm.expectRevert(UmaV3AssertionProvider.AssertionActive.selector); umaPriceProvider.checkAssertion(marketId); } function testRevertFetchAssertionInvalidCaller() public { vm.startPrank(address(0x123)); - vm.expectRevert(UmaV3DynamicAssertionProvider.InvalidCaller.selector); + vm.expectRevert(UmaV3AssertionProvider.InvalidCaller.selector); umaPriceProvider.fetchAssertion(marketId); vm.stopPrank(); } - function testRevertAssertionDataOutdated() public { - vm.warp(block.timestamp + 2 days); - vm.expectRevert( - UmaV3DynamicAssertionProvider.AssertionDataOutdated.selector - ); - umaPriceProvider.fetchAssertion(marketId); - } - function testRevertCooldownPending() public { _stagingTruthyAssertion(); - vm.expectRevert(UmaV3DynamicAssertionProvider.CooldownPending.selector); + vm.expectRevert(UmaV3AssertionProvider.CooldownPending.selector); umaPriceProvider.fetchAssertion(marketId); } @@ -491,7 +454,7 @@ contract UmaV3DynamicAssertionProviderTest is Helper { _stagingTruthyAssertion(); vm.warp(block.timestamp + umaPriceProvider.timeOut() + 1); - vm.expectRevert(UmaV3DynamicAssertionProvider.PriceTimedOut.selector); + vm.expectRevert(UmaV3AssertionProvider.PriceTimedOut.selector); umaPriceProvider.checkAssertion(marketId); } @@ -501,8 +464,8 @@ contract UmaV3DynamicAssertionProviderTest is Helper { function _stagingTruthyAssertion() internal returns (MockUma mockUma) { mockUma = new MockUma(); - // Deploying new UmaV3DynamicAssertionProvider - umaPriceProvider = new UmaV3DynamicAssertionProvider( + // Deploying new UmaV3AssertionProvider + umaPriceProvider = new UmaV3AssertionProvider( UMA_DESCRIPTION, TIME_OUT, address(mockUma), @@ -519,10 +482,6 @@ contract UmaV3DynamicAssertionProviderTest is Helper { // Moving forward so the constructor data is invalid vm.warp(block.timestamp + 2 days); - uint256 _newData = 4e6; - umaPriceProvider.updateAssertionData(_newData); - - // Querying for assertion bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); mockUma.assertionResolvedCallback( address(umaPriceProvider), @@ -543,10 +502,6 @@ contract UmaV3DynamicAssertionProviderTest is Helper { wethAsset.approve(address(umaPriceProvider), 1e18); // Moving forward so the constructor data is invalid - uint256 _newData = 4e6; - umaPriceProvider.updateAssertionData(_newData); - - // Querying for assertion bytes32 _assertionId = umaPriceProvider.fetchAssertion(_marketId); mockUma.assertionResolvedCallback( address(umaPriceProvider), diff --git a/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol deleted file mode 100644 index abcb457d..00000000 --- a/test/V2/oracles/individual/UmaV3EventAssertionProvider.t.sol +++ /dev/null @@ -1,515 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {Helper} from "../../Helper.sol"; -import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; -import { - UmaV3EventAssertionProvider -} from "../../../../src/v2/oracles/individual/UmaV3EventAssertionProvider.sol"; -import {TimeLock} from "../../../../src/v2/TimeLock.sol"; -import { - MockOracleAnswerZero, - MockOracleRoundOutdated, - MockOracleTimeOut -} from "../mocks/MockOracles.sol"; -import {MockUma} from "../mocks/MockUma.sol"; -import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; - -contract UmaV3EventAssertionProviderTest is Helper { - uint256 public arbForkId; - VaultFactoryV2 public factory; - UmaV3EventAssertionProvider public umaPriceProvider; - uint256 public marketId = 2; - ERC20 public wethAsset; - - //////////////////////////////////////////////// - // HELPERS // - //////////////////////////////////////////////// - uint256 public UMA_DECIMALS = 18; - address public UMA_OO_V3 = address(0x123); - string public UMA_DESCRIPTION = "USDC"; - uint256 public REQUIRED_BOND = 1e6; - bytes32 public defaultIdentifier = bytes32("abc"); - bytes public assertionDescription; - - function setUp() public { - arbForkId = vm.createFork(ARBITRUM_RPC_URL); - vm.selectFork(arbForkId); - wethAsset = ERC20(WETH_ADDRESS); - - // TODO: Should this be encoded or encode packed? - assertionDescription = abi.encode("Curve was hacked"); - - address timelock = address(new TimeLock(ADMIN)); - factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - uint256 condition = 2; - umaPriceProvider.setConditionType(marketId, condition); - umaPriceProvider.updateRelayer(address(this)); - } - - //////////////////////////////////////////////// - // STATE // - //////////////////////////////////////////////// - function testUmaCreation() public { - assertEq(umaPriceProvider.ASSERTION_LIVENESS(), 7200); - assertEq(umaPriceProvider.currency(), WETH_ADDRESS); - assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); - assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); - assertEq(umaPriceProvider.requiredBond(), REQUIRED_BOND); - - assertEq(umaPriceProvider.timeOut(), TIME_OUT); - assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); - assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); - assertEq(umaPriceProvider.assertionDescription(), assertionDescription); - - assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); - assertEq(umaPriceProvider.whitelistRelayer(address(this)), true); - } - - function testUpdateCoverageTime() public { - umaPriceProvider.updateCoverageStart(block.timestamp * 2); - assertEq(umaPriceProvider.coverageStart(), block.timestamp * 2); - } - - function testUpdateRequiredBond() public { - uint256 newBond = 1e6; - - vm.expectEmit(true, true, false, false); - emit BondUpdated(newBond); - umaPriceProvider.updateRequiredBond(newBond); - assertEq(umaPriceProvider.requiredBond(), newBond); - } - - function testUpdateRelayer() public { - address newRelayer = address(0x123); - - vm.expectEmit(true, true, false, false); - emit RelayerUpdated(newRelayer, true); - umaPriceProvider.updateRelayer(newRelayer); - assertEq(umaPriceProvider.whitelistRelayer(newRelayer), true); - - umaPriceProvider.updateRelayer(newRelayer); - assertEq(umaPriceProvider.whitelistRelayer(newRelayer), false); - } - - function testWithdrawBond() public { - uint256 bondAmount = 1e18; - deal(WETH_ADDRESS, address(umaPriceProvider), bondAmount); - ERC20 bondAsset = ERC20(WETH_ADDRESS); - - assertEq(bondAsset.balanceOf(address(umaPriceProvider)), bondAmount); - assertEq(bondAsset.balanceOf(address(this)), 0); - - umaPriceProvider.withdrawBond(); - assertEq(bondAsset.balanceOf(address(umaPriceProvider)), 0); - assertEq(bondAsset.balanceOf(address(this)), bondAmount); - } - - //////////////////////////////////////////////// - // FUNCTIONS // - //////////////////////////////////////////////// - function testConditionOneMetUma() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3EventAssertionProvider - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 1); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); - - vm.expectEmit(true, false, false, true); - emit MarketAsserted(marketId, bytes32(abi.encode(0x12))); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); - - // Checking assertion links to marketId - uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); - assertEq(_marketId, marketId); - assertEq(wethAsset.balanceOf(address(mockUma)), 1e6); - - // Checking marketId info is correct - ( - bool activeAssertion, - uint128 updatedAt, - uint8 answer, - bytes32 assertionIdReturned - ) = umaPriceProvider.marketIdToAnswer(_marketId); - assertEq(activeAssertion, true); - assertEq(updatedAt, uint128(0)); - assertEq(answer, 0); - assertEq(assertionIdReturned, _assertionId); - - vm.expectEmit(true, false, false, true); - emit AssertionResolved(_assertionId, true); - mockUma.assertionResolvedCallback( - address(umaPriceProvider), - _assertionId, - true - ); - - // Checking resolved callback info - ( - activeAssertion, - updatedAt, - answer, - assertionIdReturned - ) = umaPriceProvider.marketIdToAnswer(_marketId); - assertEq(activeAssertion, false); - assertEq(updatedAt, uint128(block.timestamp)); - assertEq(answer, 1); - - (bool condition, int256 price) = umaPriceProvider.conditionMet( - 2 ether, - marketId - ); - assertTrue(price == 0); - assertEq(condition, true); - } - - function testConditionTwoMetUma() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3EventAssertionProvider - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 2); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); - mockUma.assertionResolvedCallback( - address(umaPriceProvider), - _assertionId, - true - ); - - (bool condition, int256 price) = umaPriceProvider.conditionMet( - 2 ether, - marketId - ); - assertTrue(price == 0); - assertEq(condition, true); - } - - function testCheckAssertionTrue() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3EventAssertionProvider - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 2); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); - mockUma.assertionResolvedCallback( - address(umaPriceProvider), - _assertionId, - true - ); - - bool condition = umaPriceProvider.checkAssertion(marketId); - assertEq(condition, true); - } - - function testCheckAssertionFalse() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3EventAssertionProvider - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 2); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); - mockUma.assertionResolvedCallback( - address(umaPriceProvider), - _assertionId, - false - ); - - bool condition = umaPriceProvider.checkAssertion(marketId); - assertEq(condition, false); - } - - function testFetchAssertionWithBalance() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3EventAssertionProvider - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 2); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(umaPriceProvider), 1e18); - uint256 umaProviderBal = wethAsset.balanceOf(address(umaPriceProvider)); - uint256 senderBal = wethAsset.balanceOf(address(this)); - assertEq(umaProviderBal, 1e18); - assertEq(senderBal, 0); - - // Querying for assertion - vm.warp(block.timestamp + 2 days); - umaPriceProvider.fetchAssertion(marketId); - - // Checking umaPriceProvide balance declined - assertEq( - wethAsset.balanceOf(address(umaPriceProvider)), - umaProviderBal - REQUIRED_BOND - ); - assertEq(wethAsset.balanceOf(address(this)), 0); - } - - //////////////////////////////////////////////// - // REVERT CASES // - //////////////////////////////////////////////// - function testRevertConstructorInputsUma() public { - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - new UmaV3EventAssertionProvider( - 0, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - new UmaV3EventAssertionProvider( - UMA_DECIMALS, - string(""), - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - 0, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3EventAssertionProvider.ZeroAddress.selector); - new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(0), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - bytes32(""), - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3EventAssertionProvider.ZeroAddress.selector); - new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - address(0), - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - bytes(""), - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - 0 - ); - } - - function testRevertConditionTypeSetUma() public { - vm.expectRevert(UmaV3EventAssertionProvider.ConditionTypeSet.selector); - umaPriceProvider.setConditionType(2, 0); - } - - function testRevertInvalidInputConditionUma() public { - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - umaPriceProvider.setConditionType(0, 0); - - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - umaPriceProvider.setConditionType(0, 3); - } - - function testRevertInvalidInputUpdateCoverageStart() public { - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - umaPriceProvider.updateCoverageStart(0); - } - - function testRevertInvalidInputRequiredBond() public { - vm.expectRevert(UmaV3EventAssertionProvider.InvalidInput.selector); - umaPriceProvider.updateRequiredBond(0); - } - - function testRevertZeroAddressUpdateRelayer() public { - vm.expectRevert(UmaV3EventAssertionProvider.ZeroAddress.selector); - umaPriceProvider.updateRelayer(address(0)); - } - - function testRevertInvalidCallerCallback() public { - vm.expectRevert(UmaV3EventAssertionProvider.InvalidCaller.selector); - umaPriceProvider.assertionResolvedCallback(bytes32(""), true); - } - - function testRevertAssertionInactive() public { - vm.prank(UMA_OO_V3); - - vm.expectRevert(UmaV3EventAssertionProvider.AssertionInactive.selector); - umaPriceProvider.assertionResolvedCallback( - bytes32(abi.encode(0x12)), - true - ); - } - - function testRevertFetchAssertionZeroInvalidCaller() public { - vm.startPrank(address(0x123)); - vm.expectRevert(UmaV3EventAssertionProvider.InvalidCaller.selector); - umaPriceProvider.fetchAssertion(marketId); - vm.stopPrank(); - } - - function testRevertAssertionActive() public { - MockUma mockUma = new MockUma(); - - // Deploying new UmaV3EventAssertionProvider - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(mockUma), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - umaPriceProvider.setConditionType(marketId, 1); - umaPriceProvider.updateRelayer(address(this)); - - // Configuring the assertionInfo - deal(WETH_ADDRESS, address(this), 1e18); - wethAsset.approve(address(umaPriceProvider), 1e18); - umaPriceProvider.fetchAssertion(marketId); - - vm.expectRevert(UmaV3EventAssertionProvider.AssertionActive.selector); - umaPriceProvider.fetchAssertion(marketId); - } - - function testRevertTimeOutUma() public { - umaPriceProvider = new UmaV3EventAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - vm.expectRevert(UmaV3EventAssertionProvider.PriceTimedOut.selector); - umaPriceProvider.checkAssertion(123); - } -} diff --git a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol b/test/V2/oracles/individual/UmaV3PriceProvider.t.sol similarity index 57% rename from test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol rename to test/V2/oracles/individual/UmaV3PriceProvider.t.sol index ecd49211..0ddd5043 100644 --- a/test/V2/oracles/individual/UmaV3PriceAssertionProvider.t.sol +++ b/test/V2/oracles/individual/UmaV3PriceProvider.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.17; import {Helper} from "../../Helper.sol"; import {VaultFactoryV2} from "../../../../src/v2/VaultFactoryV2.sol"; import { - UmaV3PriceAssertionProvider -} from "../../../../src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol"; + UmaV3PriceProvider +} from "../../../../src/v2/oracles/individual/UmaV3PriceProvider.sol"; import {TimeLock} from "../../../../src/v2/TimeLock.sol"; import { MockOracleAnswerZero, @@ -14,11 +14,14 @@ import { } from "../mocks/MockOracles.sol"; import {MockUma} from "../mocks/MockUma.sol"; import {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; +import { + IOptimisticOracleV3 +} from "../../../../src/v2/interfaces/IOptimisticOracleV3.sol"; -contract UmaV3EventAssertionProviderTest is Helper { +contract UmaV3PriceProviderTest is Helper { uint256 public arbForkId; VaultFactoryV2 public factory; - UmaV3PriceAssertionProvider public umaPriceProvider; + UmaV3PriceProvider public umaPriceProvider; uint256 public marketId = 2; ERC20 public wethAsset; @@ -26,37 +29,29 @@ contract UmaV3EventAssertionProviderTest is Helper { // HELPERS // //////////////////////////////////////////////// uint256 public UMA_DECIMALS = 18; - address public UMA_OO_V3 = address(0x123); + address public UMA_OO_V3 = 0xa6147867264374F324524E30C02C331cF28aa879; string public UMA_DESCRIPTION = "USDC"; uint256 public REQUIRED_BOND = 1e6; bytes32 public defaultIdentifier = bytes32("abc"); - bytes public assertionDescription; + string public ASSERTION_DESCRIPTION = " USDC/USD exchange rate is"; function setUp() public { arbForkId = vm.createFork(ARBITRUM_RPC_URL); vm.selectFork(arbForkId); wethAsset = ERC20(WETH_ADDRESS); - // TODO: Should this be encoded or encode packed? - assertionDescription = abi.encode( - "USDC/USD exchange rate is above 0.997" - ); - address timelock = address(new TimeLock(ADMIN)); factory = new VaultFactoryV2(WETH, TREASURY, address(timelock)); - umaPriceProvider = new UmaV3PriceAssertionProvider( + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - uint256 condition = 2; - umaPriceProvider.setConditionType(marketId, condition); umaPriceProvider.updateRelayer(address(this)); } @@ -65,17 +60,22 @@ contract UmaV3EventAssertionProviderTest is Helper { //////////////////////////////////////////////// function testUmaCreation() public { assertEq(umaPriceProvider.ASSERTION_LIVENESS(), 7200); + assertEq(umaPriceProvider.ASSERTION_COOLDOWN(), 600); assertEq(umaPriceProvider.currency(), WETH_ADDRESS); - assertEq(umaPriceProvider.defaultIdentifier(), defaultIdentifier); + assertEq( + umaPriceProvider.defaultIdentifier(), + IOptimisticOracleV3(UMA_OO_V3).defaultIdentifier() + ); assertEq(address(umaPriceProvider.umaV3()), UMA_OO_V3); assertEq(umaPriceProvider.requiredBond(), REQUIRED_BOND); assertEq(umaPriceProvider.timeOut(), TIME_OUT); assertEq(umaPriceProvider.decimals(), UMA_DECIMALS); assertEq(umaPriceProvider.description(), UMA_DESCRIPTION); - assertEq(umaPriceProvider.assertionDescription(), assertionDescription); - - assertEq(umaPriceProvider.marketIdToConditionType(marketId), 2); + assertEq( + umaPriceProvider.assertionDescription(), + ASSERTION_DESCRIPTION + ); assertEq(umaPriceProvider.whitelistRelayer(address(this)), true); } @@ -116,21 +116,20 @@ contract UmaV3EventAssertionProviderTest is Helper { //////////////////////////////////////////////// // FUNCTIONS // //////////////////////////////////////////////// - function testConditionOneMetUma() public { + function testConditionOneMetUmaV3Assert() public { MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3PriceAssertionProvider( + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 1); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo @@ -139,20 +138,21 @@ contract UmaV3EventAssertionProviderTest is Helper { vm.expectEmit(true, false, false, true); emit MarketAsserted(marketId, bytes32(abi.encode(0x12))); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + assertionPrice, + marketId + ); // Checking assertion links to marketId - uint256 _marketId = umaPriceProvider.assertionIdToMarket(_assertionId); - assertEq(_marketId, marketId); assertEq(wethAsset.balanceOf(address(mockUma)), 1e6); // Checking marketId info is correct ( bool activeAssertion, uint128 updatedAt, - uint8 answer, + uint256 answer, bytes32 assertionIdReturned - ) = umaPriceProvider.marketIdToAnswer(_marketId); + ) = umaPriceProvider.globalAnswer(); assertEq(activeAssertion, true); assertEq(updatedAt, uint128(0)); assertEq(answer, 0); @@ -172,40 +172,43 @@ contract UmaV3EventAssertionProviderTest is Helper { updatedAt, answer, assertionIdReturned - ) = umaPriceProvider.marketIdToAnswer(_marketId); + ) = umaPriceProvider.globalAnswer(); assertEq(activeAssertion, false); assertEq(updatedAt, uint128(block.timestamp)); - assertEq(answer, 1); + assertEq(answer, assertionPrice); + uint256 strikePrice = 1000000000000001; (bool condition, int256 price) = umaPriceProvider.conditionMet( - 2 ether, + strikePrice, marketId ); - assertTrue(price == 0); + assertEq(price, int256(assertionPrice)); assertEq(condition, true); } - function testConditionTwoMetUma() public { + function testConditionTwoMetUmaV3Assert() public { MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3PriceAssertionProvider( + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 2); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); wethAsset.approve(address(umaPriceProvider), 1e18); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + assertionPrice, + marketId + ); mockUma.assertionResolvedCallback( address(umaPriceProvider), _assertionId, @@ -216,87 +219,90 @@ contract UmaV3EventAssertionProviderTest is Helper { 2 ether, marketId ); - assertTrue(price == 0); + assertEq(price, int256(assertionPrice)); assertEq(condition, true); } function testCheckAssertionTrue() public { MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3PriceAssertionProvider( + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 2); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); wethAsset.approve(address(umaPriceProvider), 1e18); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + assertionPrice, + marketId + ); mockUma.assertionResolvedCallback( address(umaPriceProvider), _assertionId, true ); - bool condition = umaPriceProvider.checkAssertion(marketId); - assertEq(condition, true); + int256 price = umaPriceProvider.getLatestPrice(); + assertEq(price, int256(assertionPrice)); } function testCheckAssertionFalse() public { MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3PriceAssertionProvider( + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 2); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); wethAsset.approve(address(umaPriceProvider), 1e18); - bytes32 _assertionId = umaPriceProvider.fetchAssertion(marketId); + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + assertionPrice, + marketId + ); mockUma.assertionResolvedCallback( address(umaPriceProvider), _assertionId, false ); - bool condition = umaPriceProvider.checkAssertion(marketId); - assertEq(condition, false); + int256 price = umaPriceProvider.getLatestPrice(); + assertEq(price, 0); } function testFetchAssertionWithBalance() public { MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3PriceAssertionProvider( + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 2); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo @@ -308,7 +314,7 @@ contract UmaV3EventAssertionProviderTest is Helper { // Querying for assertion vm.warp(block.timestamp + 2 days); - umaPriceProvider.fetchAssertion(marketId); + umaPriceProvider.updateAssertionDataAndFetch(assertionPrice, marketId); // Checking umaPriceProvide balance declined assertEq( @@ -322,186 +328,250 @@ contract UmaV3EventAssertionProviderTest is Helper { // REVERT CASES // //////////////////////////////////////////////// function testRevertConstructorInputsUma() public { - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - new UmaV3PriceAssertionProvider( + vm.expectRevert(UmaV3PriceProvider.InvalidInput.selector); + new UmaV3PriceProvider( 0, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - new UmaV3PriceAssertionProvider( + vm.expectRevert(UmaV3PriceProvider.InvalidInput.selector); + new UmaV3PriceProvider( UMA_DECIMALS, string(""), + ASSERTION_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - new UmaV3PriceAssertionProvider( + vm.expectRevert(UmaV3PriceProvider.InvalidInput.selector); + new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, 0, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaV3PriceAssertionProvider.ZeroAddress.selector); - new UmaV3PriceAssertionProvider( - UMA_DECIMALS, - UMA_DESCRIPTION, - TIME_OUT, - address(0), - defaultIdentifier, - WETH_ADDRESS, - assertionDescription, - REQUIRED_BOND - ); - - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - new UmaV3PriceAssertionProvider( + vm.expectRevert(UmaV3PriceProvider.InvalidInput.selector); + new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + string(""), TIME_OUT, UMA_OO_V3, - bytes32(""), WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaV3PriceAssertionProvider.ZeroAddress.selector); - new UmaV3PriceAssertionProvider( + vm.expectRevert(UmaV3PriceProvider.ZeroAddress.selector); + new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, - UMA_OO_V3, - defaultIdentifier, address(0), - assertionDescription, + WETH_ADDRESS, REQUIRED_BOND ); - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - new UmaV3PriceAssertionProvider( + vm.expectRevert(UmaV3PriceProvider.ZeroAddress.selector); + new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, - WETH_ADDRESS, - bytes(""), + address(0), REQUIRED_BOND ); - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - new UmaV3PriceAssertionProvider( + vm.expectRevert(UmaV3PriceProvider.InvalidInput.selector); + new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, UMA_OO_V3, - defaultIdentifier, WETH_ADDRESS, - assertionDescription, 0 ); } - function testRevertConditionTypeSetUma() public { - vm.expectRevert(UmaV3PriceAssertionProvider.ConditionTypeSet.selector); - umaPriceProvider.setConditionType(2, 0); - } - - function testRevertInvalidInputConditionUma() public { - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - umaPriceProvider.setConditionType(0, 0); - - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); - umaPriceProvider.setConditionType(0, 3); - } - function testRevertInvalidInpudRequiredBond() public { - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidInput.selector); + vm.expectRevert(UmaV3PriceProvider.InvalidInput.selector); umaPriceProvider.updateRequiredBond(0); } function testRevertInvalidInputUpdateRelayer() public { - vm.expectRevert(UmaV3PriceAssertionProvider.ZeroAddress.selector); + vm.expectRevert(UmaV3PriceProvider.ZeroAddress.selector); umaPriceProvider.updateRelayer(address(0)); } function testRevertInvalidCallerCallback() public { - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidCaller.selector); + vm.expectRevert(UmaV3PriceProvider.InvalidCaller.selector); umaPriceProvider.assertionResolvedCallback(bytes32(""), true); } function testRevertAssertionInactive() public { vm.prank(UMA_OO_V3); - vm.expectRevert(UmaV3PriceAssertionProvider.AssertionInactive.selector); + vm.expectRevert(UmaV3PriceProvider.AssertionInactive.selector); umaPriceProvider.assertionResolvedCallback( bytes32(abi.encode(0x12)), true ); } + function testRevertInvalidInputAssertionData() public { + vm.expectRevert(UmaV3PriceProvider.InvalidInput.selector); + umaPriceProvider.updateAssertionDataAndFetch(0, marketId); + } + function testRevertAssertionInvalidCaller() public { + uint256 assertionPrice = 1e18; + vm.startPrank(address(0x123)); - vm.expectRevert(UmaV3PriceAssertionProvider.InvalidCaller.selector); - umaPriceProvider.fetchAssertion(marketId); + vm.expectRevert(UmaV3PriceProvider.InvalidCaller.selector); + umaPriceProvider.updateAssertionDataAndFetch(assertionPrice, marketId); vm.stopPrank(); } - function testRevertAssertionActive() public { + function testRevertAssertionActiveUpdateDataAndFetch() public { MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; - // Deploying new UmaV3PriceAssertionProvider - umaPriceProvider = new UmaV3PriceAssertionProvider( + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, address(mockUma), - defaultIdentifier, WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - umaPriceProvider.setConditionType(marketId, 1); umaPriceProvider.updateRelayer(address(this)); // Configuring the assertionInfo deal(WETH_ADDRESS, address(this), 1e18); wethAsset.approve(address(umaPriceProvider), 1e18); - umaPriceProvider.fetchAssertion(marketId); + umaPriceProvider.updateAssertionDataAndFetch(assertionPrice, marketId); - vm.expectRevert(UmaV3PriceAssertionProvider.AssertionActive.selector); - umaPriceProvider.fetchAssertion(marketId); + vm.expectRevert(UmaV3PriceProvider.AssertionActive.selector); + umaPriceProvider.updateAssertionDataAndFetch(assertionPrice, marketId); } - function testRevertTimeOutUma() public { - umaPriceProvider = new UmaV3PriceAssertionProvider( + function testRevertCooldownPendingUpdateDataAndFetch() public { + MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; + + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( UMA_DECIMALS, UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, TIME_OUT, - UMA_OO_V3, - defaultIdentifier, + address(mockUma), WETH_ADDRESS, - assertionDescription, REQUIRED_BOND ); - vm.expectRevert(UmaV3PriceAssertionProvider.PriceTimedOut.selector); - umaPriceProvider.checkAssertion(123); + umaPriceProvider.updateRelayer(address(this)); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + assertionPrice, + marketId + ); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + vm.expectRevert(UmaV3PriceProvider.CooldownPending.selector); + umaPriceProvider.updateAssertionDataAndFetch(assertionPrice, marketId); + } + + function testRevertAssertionActiveUmaV3Price() public { + MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; + + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, + TIME_OUT, + address(mockUma), + WETH_ADDRESS, + REQUIRED_BOND + ); + umaPriceProvider.updateRelayer(address(this)); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + assertionPrice, + marketId + ); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + // Moving time forward to revert + vm.warp(block.timestamp + TIME_OUT + 1); + umaPriceProvider.updateAssertionDataAndFetch(assertionPrice, marketId); + + vm.expectRevert(UmaV3PriceProvider.AssertionActive.selector); + umaPriceProvider.getLatestPrice(); + } + + function testRevertTimeOutLatestPriceUma() public { + MockUma mockUma = new MockUma(); + uint256 assertionPrice = 1e18; + + // Deploying new UmaV3PriceProvider + umaPriceProvider = new UmaV3PriceProvider( + UMA_DECIMALS, + UMA_DESCRIPTION, + ASSERTION_DESCRIPTION, + TIME_OUT, + address(mockUma), + WETH_ADDRESS, + REQUIRED_BOND + ); + umaPriceProvider.updateRelayer(address(this)); + + // Configuring the assertionInfo + deal(WETH_ADDRESS, address(this), 1e18); + wethAsset.approve(address(umaPriceProvider), 1e18); + bytes32 _assertionId = umaPriceProvider.updateAssertionDataAndFetch( + assertionPrice, + marketId + ); + mockUma.assertionResolvedCallback( + address(umaPriceProvider), + _assertionId, + true + ); + + // Moving time forward to revert + vm.warp(block.timestamp + TIME_OUT + 1); + vm.expectRevert(UmaV3PriceProvider.PriceTimedOut.selector); + umaPriceProvider.getLatestPrice(); } } diff --git a/test/V2/oracles/mocks/MockUma.sol b/test/V2/oracles/mocks/MockUma.sol index ad8fad44..aa8cb8f0 100644 --- a/test/V2/oracles/mocks/MockUma.sol +++ b/test/V2/oracles/mocks/MockUma.sol @@ -39,7 +39,7 @@ contract MockUma { uint64 assertionLiveness, IERC20 currency, uint256 bond, - bytes32 defaultIdentifier, + bytes32 _defaultIdentifier, bytes32 domain ) external payable returns (bytes32 assertionId) { currency.transferFrom(msg.sender, address(this), bond); @@ -51,7 +51,7 @@ contract MockUma { callBackAddress, sovereignSecurity, assertionLiveness, - defaultIdentifier, + _defaultIdentifier, domain ); }