Skip to content

Commit

Permalink
feat: UmaV2 assertion provider
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnyxos committed Oct 9, 2023
1 parent d5e2bc5 commit 4390986
Show file tree
Hide file tree
Showing 8 changed files with 638 additions and 39 deletions.
210 changes: 209 additions & 1 deletion src/v2/oracles/individual/UmaV2AssertionProvider.sol
Original file line number Diff line number Diff line change
@@ -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();
}
20 changes: 9 additions & 11 deletions src/v2/oracles/individual/UmaV2PriceProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -151,7 +149,7 @@ contract UmaV2PriceProvider is Ownable {
true
);

MarketAnswer memory _pendingAnswer;
PriceAnswer memory _pendingAnswer;
_pendingAnswer.startedAt = block.timestamp;
pendingAnswer = _pendingAnswer;

Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/v2/oracles/individual/UmaV3EventAssertionProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
4 changes: 2 additions & 2 deletions src/v2/oracles/individual/UmaV3PriceAssertionProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
1 change: 1 addition & 0 deletions test/V2/Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions test/V2/oracles/MockOracles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -342,7 +342,7 @@ contract MockOracleConditionMetCVI {
////////////////// Pyth Implementation //////////////////
contract MockOracleAnswerNegativePyth {
function getPriceUnsafe(
bytes32 priceFeedId
bytes32
) external view returns (PythStructs.Price memory price) {
return PythStructs.Price(0, 0, -8, block.timestamp);
}
Expand All @@ -357,7 +357,7 @@ contract MockOracleAnswerNegativePyth {

contract MockOracleExponentTooSmallPyth {
function getPriceUnsafe(
bytes32 priceFeedId
bytes32
) external view returns (PythStructs.Price memory price) {
return PythStructs.Price(899898, 0, -19, block.timestamp);
}
Expand Down
Loading

0 comments on commit 4390986

Please sign in to comment.