Skip to content

Instantly share code, notes, and snippets.

@coburncoburn
Created June 13, 2020 00:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coburncoburn/6634f88849cc840aa1afe7faabc455d6 to your computer and use it in GitHub Desktop.
Save coburncoburn/6634f88849cc840aa1afe7faabc455d6 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.6.6+commit.6c089d02.js&optimize=false&gist=
pragma solidity ^0.5.10;
pragma experimental ABIEncoderV2;
/**
* @title The Open Oracle View Base Contract
* @author Compound Labs, Inc.
*/
contract OpenOracleView {
/**
* @notice The Oracle Data Contract backing this View
*/
OpenOracleData public data;
/**
* @notice The static list of sources used by this View
* @dev Note that while it is possible to create a view with dynamic sources,
* that would not conform to the Open Oracle Standard specification.
*/
address[] public sources;
/**
* @notice Construct a view given the oracle backing address and the list of sources
* @dev According to the protocol, Views must be immutable to be considered conforming.
* @param data_ The address of the oracle data contract which is backing the view
* @param sources_ The list of source addresses to include in the aggregate value
*/
constructor(OpenOracleData data_, address[] memory sources_) public {
data = data_;
sources = sources_;
}
}
/**
* @notice The DelFi Price Feed View
* @author Compound Labs, Inc.
*/
contract DelFiPrice is OpenOracleView {
/**
* @notice The event emitted when a price is written to storage
*/
event Price(string symbol, uint64 price);
/**
* @notice The mapping of medianized prices per symbol
*/
mapping(string => uint64) public prices;
constructor(OpenOraclePriceData data_, address[] memory sources_) public OpenOracleView(data_, sources_) {}
/**
* @notice Primary entry point to post and recalculate prices
* @dev We let anyone pay to post anything, but only sources count for prices.
* @param messages The messages to post to the oracle
* @param signatures The signatures for the corresponding messages
*/
function postPrices(bytes[] calldata messages, bytes[] calldata signatures, string[] calldata symbols) external {
require(messages.length == signatures.length, "messages and signatures must be 1:1");
// Post the messages, whatever they are
for (uint i = 0; i < messages.length; i++) {
OpenOraclePriceData(address(data)).put(messages[i], signatures[i]);
}
// Recalculate the asset prices for the symbols to update
for (uint i = 0; i < symbols.length; i++) {
string memory symbol = symbols[i];
// Calculate the median price, write to storage, and emit an event
uint64 price = medianPrice(symbol, sources);
prices[symbol] = price;
emit Price(symbol, price);
}
}
/**
* @notice Calculates the median price over any set of sources
* @param symbol The symbol to calculate the median price of
* @param sources_ The sources to use when calculating the median price
* @return median The median price over the set of sources
*/
function medianPrice(string memory symbol, address[] memory sources_) public view returns (uint64 median) {
require(sources_.length > 0, "sources list must not be empty");
uint N = sources_.length;
uint64[] memory postedPrices = new uint64[](N);
for (uint i = 0; i < N; i++) {
postedPrices[i] = OpenOraclePriceData(address(data)).getPrice(sources_[i], symbol);
}
uint64[] memory sortedPrices = sort(postedPrices);
return sortedPrices[N / 2];
}
/**
* @notice Helper to sort an array of uints
* @param array Array of integers to sort
* @return The sorted array of integers
*/
function sort(uint64[] memory array) private pure returns (uint64[] memory) {
uint N = array.length;
for (uint i = 0; i < N; i++) {
for (uint j = i + 1; j < N; j++) {
if (array[i] > array[j]) {
uint64 tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
return array;
}
}
/**
* @title The Open Oracle Data Base Contract
* @author Compound Labs, Inc.
*/
contract OpenOracleData {
/**
* @notice The event emitted when a source writes to its storage
*/
//event Write(address indexed source, <Key> indexed key, string kind, uint64 timestamp, <Value> value);
/**
* @notice Write a bunch of signed datum to the authenticated storage mapping
* @param message The payload containing the timestamp, and (key, value) pairs
* @param signature The cryptographic signature of the message payload, authorizing the source to write
* @return The keys that were written
*/
//function put(bytes calldata message, bytes calldata signature) external returns (<Key> memory);
/**
* @notice Read a single key with a pre-defined type signature from an authenticated source
* @param source The verifiable author of the data
* @param key The selector for the value to return
* @return The claimed Unix timestamp for the data and the encoded value (defaults to (0, 0x))
*/
//function get(address source, <Key> key) external view returns (uint, <Value>);
/**
* @notice Recovers the source address which signed a message
* @dev Comparing to a claimed address would add nothing,
* as the caller could simply perform the recover and claim that address.
* @param message The data that was presumably signed
* @param signature The fingerprint of the data + private key
* @return The source address which signed the message, presumably
*/
function source(bytes memory message, bytes memory signature) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = abi.decode(signature, (bytes32, bytes32, uint8));
bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(message)));
return ecrecover(hash, v, r, s);
}
}
/**
* @title The Open Oracle Price Data Contract
* @notice Values stored in this contract should represent a USD price with 6 decimals precision
* @author Compound Labs, Inc.
*/
contract OpenOraclePriceData is OpenOracleData {
/**
* @notice The event emitted when a source writes to its storage
*/
event Write(address indexed source, string key, uint64 timestamp, uint64 value);
/**
* @notice The fundamental unit of storage for a reporter source
*/
struct Datum {
uint64 timestamp;
uint64 value;
}
/**
* @notice The most recent authenticated data from all sources
* @dev This is private because dynamic mapping keys preclude auto-generated getters.
*/
mapping(address => mapping(string => Datum)) private data;
/**
* @notice Write a bunch of signed datum to the authenticated storage mapping
* @param message The payload containing the timestamp, and (key, value) pairs
* @param signature The cryptographic signature of the message payload, authorizing the source to write
* @return The keys that were written
*/
function put(bytes calldata message, bytes calldata signature) external returns (string memory) {
// Recover the source address
address source = source(message, signature);
// Decode the message and check the kind
(string memory kind, uint64 timestamp, string memory key, uint64 value) = abi.decode(message, (string, uint64, string, uint64));
require(keccak256(abi.encodePacked(kind)) == keccak256(abi.encodePacked("prices")), "Kind of data must be 'prices'");
// Only update if newer than stored, according to source
Datum storage prior = data[source][key];
if (prior.timestamp < timestamp) {
data[source][key] = Datum(timestamp, value);
emit Write(source, key, timestamp, value);
}
return key;
}
/**
* @notice Read a single key from an authenticated source
* @param source The verifiable author of the data
* @param key The selector for the value to return
* @return The claimed Unix timestamp for the data and the price value (defaults to (0, 0))
*/
function get(address source, string calldata key) external view returns (uint64, uint64) {
Datum storage datum = data[source][key];
return (datum.timestamp, datum.value);
}
/**
* @notice Read only the value for a single key from an authenticated source
* @param source The verifiable author of the data
* @param key The selector for the value to return
* @return The price value (defaults to 0)
*/
function getPrice(address source, string calldata key) external view returns (uint64) {
return data[source][key].value;
}
}
pragma solidity ^0.6.6;
pragma experimental ABIEncoderV2;
contract Bacon {
struct B {
uint a;
uint b;
uint c;
}
function eat() public returns (B memory) {
return B(1, 2, 3);
}
}
contract Potatos {
struct B {
uint a;
uint b;
uint c;
uint d;
}
function eat() public returns (B memory) {
return B(1, 2, 3, 4);
}
}
contract Eggs {
function eat(Bacon bacon) public returns (Bacon.B memory) {
return bacon.eat();
}
}
contract Test {
for (uint i = 0; i < messages.length; i++) { for (uint i = 0; i < messages.length; i++) {
string memory symbol = OpenOraclePriceData(address(data)).put(messages[i], signatures[i]); OpenOraclePriceData(address(data)).put(messages[i], signatures[i]);
// Possibly add to the unique symbol set
bool found = false;
for (uint j = 0; j < symbols.length; j++) {
if (keccak256(abi.encodePacked(symbol)) == keccak256(abi.encodePacked(symbols[j]))) {
found = true;
break;
}
}
if (!found) {
symbols[i] = symbol;
numSymbols += 1;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment