Last active
August 5, 2020 00:50
-
-
Save dryruner/f39845c47564c9ff98085749bd542d44 to your computer and use it in GitHub Desktop.
CoinbaseEthUsd Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
*Submitted for verification at Etherscan.io on 2020-07-03 | |
*/ | |
pragma solidity ^0.5.0; | |
/** | |
* @title Ownable | |
* @dev The Ownable contract has an owner address, and provides basic authorization control | |
* functions, this simplifies the implementation of "user permissions". | |
*/ | |
contract Ownable { | |
address private _owner; | |
event OwnershipRenounced(address indexed previousOwner); | |
event OwnershipTransferred( | |
address indexed previousOwner, | |
address indexed newOwner | |
); | |
/** | |
* @dev The Ownable constructor sets the original `owner` of the contract to the sender | |
* account. | |
*/ | |
constructor() public { | |
_owner = msg.sender; | |
} | |
/** | |
* @return the address of the owner. | |
*/ | |
function owner() public view returns(address) { | |
return _owner; | |
} | |
/** | |
* @dev Throws if called by any account other than the owner. | |
*/ | |
modifier onlyOwner() { | |
require(isOwner()); | |
_; | |
} | |
/** | |
* @return true if `msg.sender` is the owner of the contract. | |
*/ | |
function isOwner() public view returns(bool) { | |
return msg.sender == _owner; | |
} | |
/** | |
* @dev Allows the current owner to relinquish control of the contract. | |
* @notice Renouncing to ownership will leave the contract without an owner. | |
* It will not be possible to call the functions with the `onlyOwner` | |
* modifier anymore. | |
*/ | |
function renounceOwnership() public onlyOwner { | |
emit OwnershipRenounced(_owner); | |
_owner = address(0); | |
} | |
/** | |
* @dev Allows the current owner to transfer control of the contract to a newOwner. | |
* @param newOwner The address to transfer ownership to. | |
*/ | |
function transferOwnership(address newOwner) public onlyOwner { | |
_transferOwnership(newOwner); | |
} | |
/** | |
* @dev Transfers control of the contract to a newOwner. | |
* @param newOwner The address to transfer ownership to. | |
*/ | |
function _transferOwnership(address newOwner) internal { | |
require(newOwner != address(0)); | |
emit OwnershipTransferred(_owner, newOwner); | |
_owner = newOwner; | |
} | |
} | |
// Util functions imported in OnChainSDK for caller to use. | |
library utils { | |
uint constant UINT256MAX = ~uint(0); | |
// A decimal byte to uint. Return value of 10 indicating invalid input. | |
function byte2Uint(byte b) internal pure returns(uint8) { | |
if (b >= '0' && b <= '9') { | |
return uint8(b) - 48; // '0' | |
} | |
// Indicating invalid input. | |
return 10; | |
} | |
// Return value of 16 indicating invalid input. | |
function hexByte2Uint(byte b) internal pure returns(uint8) { | |
if (b >= '0' && b <= '9') { | |
return uint8(b) - 48; // '0' | |
} else if (b >= 'A' && b <= 'F') { | |
return uint8(b) - 55; | |
} else if (b >= 'a' && b <= 'f') { | |
return uint8(b) - 87; | |
} | |
// Indicating invalid input. | |
return 16; | |
} | |
/// StringToXXX helpers. | |
// A decimal string (charset c in [0-9]) to uint. Like atoi(), | |
// 1. processing stops once encountering character not in charset c. | |
// 2. returns UINT256MAX when overflow. | |
function str2Uint(string memory a) internal pure returns(uint) { | |
bytes memory b = bytes(a); | |
uint res = 0; | |
for (uint i = 0; i < b.length; i++) { | |
uint8 tmp = byte2Uint(b[i]); | |
if (tmp >= 10) { | |
return res; | |
} else { | |
// Overflow. | |
if (res >= UINT256MAX / 10) { | |
return UINT256MAX; | |
} | |
res = res * 10 + tmp; | |
} | |
} | |
return res; | |
} | |
// Hex string (charset c in [0-9A-Za-z]) to uint. Like atoi(), | |
// 1. processing stops once encountering character not in charset c. | |
// 2. returns UINT256MAX when overflow. | |
function hexStr2Uint(string memory a) internal pure returns(uint) { | |
bytes memory b = bytes(a); | |
uint res = 0; | |
uint i = 0; | |
if (b.length >= 2 && b[0] == '0' && (b[1] == 'x' || b[1] == 'X')) { | |
i += 2; | |
} | |
for (; i < b.length; i++) { | |
uint tmp = hexByte2Uint(b[i]); | |
if (tmp >= 16) { | |
return res; | |
} else { | |
// Overflow. | |
if (res >= UINT256MAX / 16) { | |
return UINT256MAX; | |
} | |
res = res * 16 + tmp; | |
} | |
} | |
return res; | |
} | |
// Input: 20-byte hex string without or with 0x/0X prefix (40 characters or 42 characters) | |
// Example: '0x0e7ad63d2a305a7b9f46541c386aafbd2af6b263' => address(0x0e7ad63d2a305a7b9f46541c386aafbd2af6b263) | |
// address is of uint160. | |
function str2Addr(string memory a) internal pure returns(address) { | |
bytes memory b = bytes(a); | |
require(b.length == 40 || b.length == 42, "Invalid input, should be 20-byte hex string"); | |
uint i = 0; | |
if (b.length == 42) { | |
i += 2; | |
} | |
uint160 res = 0; | |
for (; i < b.length; i += 2) { | |
res *= 256; | |
uint160 b1 = uint160(hexByte2Uint(b[i])); | |
uint160 b2 = uint160(hexByte2Uint(b[i+1])); | |
require(b1 < 16 && b2 < 16, "address string with invalid character"); | |
res += b1 * 16 + b2; | |
} | |
return address(res); | |
} | |
/// XXXToString() helpers. | |
// Example: 12 -> 'c' (without 0x/0X prefix). | |
function uint2HexStr(uint x) internal pure returns(string memory) { | |
if (x == 0) return '0'; | |
uint j = x; | |
uint len; | |
while (j != 0) { | |
len++; | |
j /= 16; | |
} | |
bytes memory b = new bytes(len); | |
uint k = len - 1; | |
while (x != 0) { | |
uint8 curr = uint8(x & 0xf); | |
b[k--] = curr > 9 ? byte(55 + curr) : byte(48 + curr); | |
x /= 16; | |
} | |
return string(b); | |
} | |
// Example: 12 -> "12" | |
function uint2Str(uint x) internal pure returns(string memory) { | |
if (x == 0) return '0'; | |
uint j = x; | |
uint len; | |
while (j != 0) { | |
len++; | |
j /= 10; | |
} | |
bytes memory b = new bytes(len); | |
uint k = len - 1; | |
while (x != 0) { | |
b[k--] = byte(uint8(48 + x % 10)); | |
x /= 10; | |
} | |
return string(b); | |
} | |
// Example: address(0x0e7ad63d2a305a7b9f46541c386aafbd2af6b263) => '0x0e7ad63d2a305a7b9f46541c386aafbd2af6b263' | |
function addr2Str(address _addr) internal pure returns(string memory) { | |
bytes32 value = bytes32(uint256(_addr)); | |
bytes memory charset = "0123456789abcdef"; | |
bytes memory str = new bytes(42); | |
str[0] = '0'; | |
str[1] = 'x'; | |
for (uint i = 0; i < 20; i++) { | |
str[2+i*2] = charset[uint8(value[i + 12] >> 4)]; | |
str[3+i*2] = charset[uint8(value[i + 12] & 0x0f)]; | |
} | |
return string(str); | |
} | |
/// bytes/string helpers. | |
function bytesConcat(bytes memory a, bytes memory b) internal pure returns(bytes memory) { | |
bytes memory concated = new bytes(a.length + b.length); | |
uint i = 0; | |
uint k = 0; | |
while (i < a.length) { concated[k++] = a[i++]; } | |
i = 0; | |
while(i < b.length) { concated[k++] = b[i++]; } | |
return concated; | |
} | |
function strConcat(string memory a, string memory b) internal pure returns(string memory) { | |
bytes memory aa = bytes(a); | |
bytes memory bb = bytes(b); | |
return string(bytesConcat(aa, bb)); | |
} | |
function bytesCompare(bytes memory a, bytes memory b) internal pure returns(int) { | |
uint len = a.length < b.length ? a.length : b.length; | |
for (uint i = 0; i < len; i++) { | |
if (a[i] < b[i]) { | |
return -1; | |
} else if (a[i] > b[i]) { | |
return 1; | |
} | |
} | |
if (a.length < b.length) { | |
return -1; | |
} else if (a.length > b.length) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
// "abd" > "abcde" | |
function strCompare(string memory a, string memory b) internal pure returns(int) { | |
bytes memory aa = bytes(a); | |
bytes memory bb = bytes(b); | |
return bytesCompare(aa, bb); | |
} | |
function bytesEqual(bytes memory a, bytes memory b) internal pure returns(bool) { | |
return (a.length == b.length) && (bytesCompare(a, b) == 0); | |
} | |
function strEqual(string memory a, string memory b) internal pure returns(bool) { | |
bytes memory aa = bytes(a); | |
bytes memory bb = bytes(b); | |
return bytesEqual(aa, bb); | |
} | |
// Return the index of needle's first occurrance in haystack. Return value | |
// of -1 indicating no occurrance. | |
// Useful in case of parsing float string "123.45". | |
// Example: | |
// indexOf('123', '') => 0 | |
// indexOf('', '45') => -1 | |
// indexOf('123', '1234') => -1 | |
// indexOf('123.45', '.') => 3 | |
function indexOf(string memory haystack, string memory needle) internal pure returns(int) { | |
bytes memory b_haystack = bytes(haystack); | |
bytes memory b_needle = bytes(needle); | |
return indexOf(b_haystack, b_needle); | |
} | |
function indexOf(bytes memory haystack, bytes memory needle) internal pure returns(int) { | |
if (needle.length == 0) { | |
return 0; | |
} else if (haystack.length < needle.length) { | |
return -1; | |
} | |
// Instead of O(haystack.length x needle.length), saving gas using KMP: | |
// O(haystack.length + needle.length) | |
uint[] memory pi = new uint[](needle.length + 1); | |
pi[1] = 0; | |
uint k = 0; | |
uint q = 0; | |
// KMP pre-processing | |
for(q = 2; q <= needle.length; q++) { | |
while(k > 0 && needle[k] != needle[q-1]) { | |
k = pi[k]; | |
} | |
if(needle[k] == needle[q-1]) { | |
k++; | |
} | |
pi[q] = k; | |
} | |
// KMP matching | |
q = 0; | |
for(uint i = 0; i < haystack.length; i++) { | |
while(q > 0 && needle[q] != haystack[i]) { | |
q = pi[q]; | |
} | |
if(needle[q] == haystack[i]) { | |
q++; | |
} | |
// Match | |
if(q == needle.length) { | |
return int(i - q + 1); | |
} | |
} | |
// No match | |
return -1; | |
} | |
// subStr("1234567890", 2, 5) => "34567" | |
// [start, start + len), index starting from 0. | |
function subStr(bytes memory a, uint start, uint len) internal pure returns(bytes memory) { | |
require(start < a.length && start + len > start && start + len <= a.length, | |
"Invalid start index or length out of range"); | |
bytes memory res = new bytes(len); | |
for (uint i = 0; i < len; i++) { | |
res[i] = a[start + i]; | |
} | |
return res; | |
} | |
// string num = "123.4567"; | |
// subStr(num, indexOf(num, '.') + 1) => "4567" | |
function subStr(bytes memory a, uint start) internal pure returns(bytes memory) { | |
require(start < a.length, "Invalid start index out of range"); | |
return subStr(a, start, a.length - start); | |
} | |
function subStr(string memory a, uint start, uint len) internal pure returns(string memory) { | |
bytes memory aa = bytes(a); | |
return string(subStr(aa, start, len)); | |
} | |
function subStr(string memory a, uint start) internal pure returns(string memory) { | |
bytes memory aa = bytes(a); | |
return string(subStr(aa, start)); | |
} | |
} | |
contract DOSProxyInterface { | |
function query(address, uint, string memory, string memory) public returns (uint); | |
function requestRandom(address, uint) public returns (uint); | |
} | |
contract DOSPaymentInterface { | |
function setPaymentMethod(address payer, address tokenAddr) public; | |
function defaultTokenAddr() public returns(address); | |
} | |
contract DOSAddressBridgeInterface { | |
function getProxyAddress() public view returns (address); | |
function getPaymentAddress() public view returns (address); | |
} | |
contract ERC20I { | |
function balanceOf(address who) public view returns (uint); | |
function transfer(address to, uint value) public returns (bool); | |
function approve(address spender, uint value) public returns (bool); | |
} | |
contract DOSOnChainSDK is Ownable { | |
// Comment out utils library if you don't need it to save gas. (L4 and L30) | |
// using utils for *; | |
DOSProxyInterface dosProxy; | |
DOSAddressBridgeInterface dosAddrBridge = | |
DOSAddressBridgeInterface(0x98A0E7026778840Aacd28B9c03137D32e06F5ff1); | |
modifier resolveAddress { | |
dosProxy = DOSProxyInterface(dosAddrBridge.getProxyAddress()); | |
_; | |
} | |
modifier auth { | |
// Filter out malicious __callback__ caller. | |
require(msg.sender == dosAddrBridge.getProxyAddress(), "Unauthenticated response"); | |
_; | |
} | |
// @dev: call setup function first and transfer DOS tokens into deployed contract as oracle fees. | |
function DOSSetup() public onlyOwner { | |
address paymentAddr = dosAddrBridge.getPaymentAddress(); | |
address defaultToken = DOSPaymentInterface(dosAddrBridge.getPaymentAddress()).defaultTokenAddr(); | |
ERC20I(defaultToken).approve(paymentAddr, uint(-1)); | |
DOSPaymentInterface(dosAddrBridge.getPaymentAddress()).setPaymentMethod(address(this), defaultToken); | |
} | |
// @dev: refund all unused fees to caller. | |
function DOSRefund() public onlyOwner { | |
address token = DOSPaymentInterface(dosAddrBridge.getPaymentAddress()).defaultTokenAddr(); | |
uint amount = ERC20I(token).balanceOf(address(this)); | |
ERC20I(token).transfer(msg.sender, amount); | |
} | |
// @dev: Call this function to get a unique queryId to differentiate | |
// parallel requests. A return value of 0x0 stands for error and a | |
// related event would be emitted. | |
// @timeout: Estimated timeout in seconds specified by caller; e.g. 15. | |
// Response is not guaranteed if processing time exceeds this. | |
// @dataSource: Data source destination specified by caller. | |
// E.g.: 'https://api.coinbase.com/v2/prices/ETH-USD/spot' | |
// @selector: A selector expression provided by caller to filter out | |
// specific data fields out of the raw response. The response | |
// data format (json, xml/html, and more) is identified from | |
// the selector expression. | |
// E.g. Use "$.data.amount" to extract "194.22" out. | |
// { | |
// "data":{ | |
// "base":"ETH", | |
// "currency":"USD", | |
// "amount":"194.22" | |
// } | |
// } | |
// Check below documentation for details. | |
// (https://dosnetwork.github.io/docs/#/contents/blockchains/ethereum?id=selector). | |
function DOSQuery(uint timeout, string memory dataSource, string memory selector) | |
internal | |
resolveAddress | |
returns (uint) | |
{ | |
return dosProxy.query(address(this), timeout, dataSource, selector); | |
} | |
// @dev: Must override __callback__ to process a corresponding response. A | |
// user-defined event could be added to notify the Dapp frontend that | |
// the response is ready. | |
// @queryId: A unique queryId returned by DOSQuery() for callers to | |
// differentiate parallel responses. | |
// @result: Response for the specified queryId. | |
function __callback__(uint queryId, bytes calldata result) external { | |
// To be overridden in the caller contract. | |
} | |
// @dev: Call this function to request either a fast but insecure random | |
// number or a safe and secure random number delivered back | |
// asynchronously through the __callback__ function. | |
// Depending on the mode, the return value would be a random number | |
// (for fast mode) or a requestId (for safe mode). | |
// @seed: Optional random seed provided by caller. | |
function DOSRandom(uint seed) | |
internal | |
resolveAddress | |
returns (uint) | |
{ | |
return dosProxy.requestRandom(address(this), seed); | |
} | |
// @dev: Must override __callback__ to process a corresponding random | |
// number. A user-defined event could be added to notify the Dapp | |
// frontend that a new secure random number is generated. | |
// @requestId: A unique requestId returned by DOSRandom() for requester to | |
// differentiate random numbers generated concurrently. | |
// @generatedRandom: Generated secure random number for the specific | |
// requestId. | |
function __callback__(uint requestId, uint generatedRandom) external auth { | |
// To be overridden in the caller contract. | |
} | |
} | |
// An example get latest ETH-USD price from Coinbase | |
contract CoinbaseEthPriceFeed is DOSOnChainSDK { | |
using utils for *; | |
// Struct to hold parsed floating string "123.45" | |
struct ethusd { | |
uint integral; | |
uint fractional; | |
} | |
uint queryId; | |
string public price_str; | |
ethusd public price; | |
event GetPrice(uint integral, uint fractional); | |
constructor() public { | |
// @dev: setup and then transfer DOS tokens into deployed contract | |
// as oracle fees. | |
// Unused fees can be reclaimed by calling DOSRefund() in the SDK. | |
super.DOSSetup(); | |
} | |
function getEthUsdPrice() public { | |
queryId = DOSQuery(30, "https://api.coinbase.com/v2/prices/ETH-USD/spot", "$.data.amount"); | |
} | |
function __callback__(uint id, bytes calldata result) external auth { | |
require(queryId == id, "Unmatched response"); | |
price_str = string(result); | |
price.integral = price_str.subStr(1).str2Uint(); | |
int delimit_idx = price_str.indexOf('.'); | |
if (delimit_idx != -1) { | |
price.fractional = price_str.subStr(uint(delimit_idx + 1)).str2Uint(); | |
} | |
emit GetPrice(price.integral, price.fractional); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment