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 9a050dac..c296a684 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); + } +}